TL; DR: Ich brauche Hilfe herausfinden, wie man Code erzeugt, der eine von einer kleinen Anzahl von Datentypen (wahrscheinlich nur Double und Bool) zurückgibt aus verschiedenen Bereichen auf unterschiedlichen Datensätzen.Wie baue ich ein DSL zum Nachschlagen von Feldern aus einem Datensatz in Haskell
Langform: Angenommen, die folgenden Datentypen
data Circle = Circle { radius :: Integer, origin :: Point }
data Square = Square { side :: Integer }
und einige Standardcode
circle = Circle 3 (Point 0 0)
square = Square 5
Ich bin ein kleiner DSL bauen, und wollen, dass der Benutzer etwas wie das schreiben folgende
circle.origin
square.side
und es wird g enerate Code ähnlich
origin . circle
side . square
dies beim Parsen, würde ich die Saiten „Kreis“ hat und „Herkunft“ zum Beispiel. Ich muss diese jetzt zu Funktionsaufrufen machen. Ich könnte natürlich so etwas haben:
data Expr a = IntegerE (a -> Integer)
| PointE (a -> Point)
lookupF2I "side" = Just $ IntegerE side
lookupF2I "radius" = Just $ IntegerE radius
lookupF2I _ = Nothing
lookupF2P "origin" = Just $ PointE origin
lookupF2P _ = Nothing
und haben eine Lookup-Funktion pro zurückgegebenen Datentyp. Eine Funktion pro Datentyp ist aus Sicht von DSL praktisch, da nur zwei oder drei Datentypen behandelt werden. Dies scheint jedoch nicht besonders effektiv zu sein. Gibt es einen besseren Weg (sicher), dies zu tun? Wenn nicht, gibt es eine Möglichkeit, den Code für die verschiedenen Suchfunktionen aus den verschiedenen Datensätzen zu generieren, aus denen ich Felder suchen kann?
Zweitens gibt es immer noch die Frage des analysierten "circle"
oder "square"
benötigen Sie die entsprechende circle
oder square
Funktion aufzurufen. Wenn ich dies mit Typ-Klassen implementieren, könnte ich so etwas wie:
instance Lookup Circle where
lookupF2I "radius" = Just $ IntegerE radius
lookupF2I _ = Nothing
lookupF2P "origin" = Just $ PointE origin
lookupF2P _ = Nothing
aber dann, das mich verläßt mit welcher Art, um herauszufinden, auf der Lookup-Funktion zu erzwingen, und schlechter zu müssen Hand schreiben Instanzen für jeden (von vielen) Datensätzen, für die ich diesen verwenden möchte.
Hinweis: Die Tatsache, dass Circle
und Square
mit einem einzigen ADT dargestellt werden könnte, ist nebenbei meine Frage, dass dies ein künstliches Beispiel ist. Der eigentliche Code wird verschiedene sehr unterschiedliche Datensätze enthalten, von denen die einzigen, die sie gemeinsam haben, Felder desselben Typs sind.
Ich würde auf das Lens-Paket schauen: http://hackage.haskell.org/package/lens-3.1 und auch bei Vinyl https://github.com/jonsterling/Vinyl – ErikR
+1 für "Linse". Es unterstützt Template Haskell, sodass Sie Linsen automatisch aus Datentypen erstellen können. Dann übersetzt Ihr Parsing-Code die Strings einfach in ihre entsprechenden Objektive. –
Es sieht so aus, als ob Sie Ihr DSL mit einer Art HOAS (abstrakte Syntax höherer Ordnung) einbetten, die Funktionen aus der Metasprache (Haskell) in der eingebetteten Sprache verwendet. Wenn Sie ein paar Anmerkungen zur Verwendung von HOAS haben möchten, dann schauen Sie sich das "Unembedding" -Papier von Robert Atkey und Co-Autoren https://personal.cis.strath.ac.uk/robert.atkey/unembedding.html an. Wenn HOAS Ihnen nichts bedeutet, dann sind Sie wahrscheinlich mit der normalen abstrakten Syntax erster Ordnung am besten - stellen Sie eingebaute Funktionen mit einem Bezeichner dar und bewerten Sie sie mit einer Umgebung, die eine Suche nach den primitiven Funktionen enthält. –