TL; DR: Potrzebuję pomocy w ustaleniu, jak wygenerować kod, który zwróci jedną z niewielkiej liczby typów danych (prawdopodobnie tylko Double i Bool) z różnych dziedzin na różnych rekordach.Jak zbudować DSL do wyszukiwania pól z rekordu w Haskell
Długa forma: Przy założeniu następujących typów danych
data Circle = Circle { radius :: Integer, origin :: Point }
data Square = Square { side :: Integer }
i jakiś standardowy kod
circle = Circle 3 (Point 0 0)
square = Square 5
Buduję małą DSL, a użytkownik chce się napisać coś jak po
circle.origin
square.side
i będzie Kod energetyczny podobny do
origin . circle
side . square
Podczas parsowania tego, mam na przykład łańcuchy "koło" i "pochodzenie". Muszę teraz przekształcić je w wywołania funkcji. Mogłem oczywiście mieć coś takiego:
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
i mają jedną funkcję wyszukiwania dla każdego zwróconego typu danych. Posiadanie jednej funkcji na typ danych jest praktyczne z punktu widzenia DSL, ponieważ będzie to naprawdę dotyczyć tylko 2 lub 3 typów danych. Jednak nie wydaje się to szczególnie skutecznym sposobem robienia rzeczy. Czy istnieje lepszy sposób (z pewnością) na robienie tego? Jeśli nie, czy istnieje sposób, w jaki mogę wygenerować kod dla różnych funkcji wyszukiwania z różnych rekordów, z których chcę móc wyszukiwać pola?
Po drugie, jest jeszcze kwestia przeanalizowany "circle"
lub "square"
potrzeby wezwać odpowiednie circle
lub square
funkcję. Gdybym zaimplementować to za pomocą klas typu, mógłby zrobić coś takiego:
instance Lookup Circle where
lookupF2I "radius" = Just $ IntegerE radius
lookupF2I _ = Nothing
lookupF2P "origin" = Just $ PointE origin
lookupF2P _ = Nothing
ale potem, że zostawia mnie z konieczności, aby dowiedzieć się, jaki rodzaj wymusić na funkcji przeglądowej, i gorszym konieczności pisania ręcznego wystąpienia dla każdego (z wielu) rekordów, które chcę użyć.
Uwaga: fakt, że Circle
i Square
może być reprezentowany przy użyciu pojedynczego ADT, jest dodatkowy w stosunku do mojego pytania, ponieważ jest to przykładowy przykład. Rzeczywisty kod będzie zawierał różne bardzo różne zapisy, z których jedyną wspólną cechą jest posiadanie pól tego samego rodzaju.
Spojrzałbym na pakiet Lens: http://hackage.haskell.org/package/lens-3.1, a także na Vinyl https://github.com/jonsterling/Vinyl – ErikR
+1 dla "obiektywu". Posiada wsparcie dla szablonu Haskell, dzięki czemu można automatycznie budować obiektywy z typów danych. Następnie Twój kod parsujący po prostu tłumaczy łańcuchy na odpowiadające im soczewki. –
Wygląda na to, że osadzasz swoje DSL za pomocą jakiegoś HOAS (składnia abstrakcyjnego wyższego rzędu), która korzysta z funkcji meta-języka (Haskell) w języku osadzonym. Jeśli chcesz przeczytać kilka uwag na temat korzystania z HOAS, spójrz na artykuł "Unembedding" autorstwa Roberta Atkey'a i współautorów https://personal.cis.strath.ac.uk/robert.atkey/unembedding.html. Jeśli HOAS nic dla ciebie nie znaczy, to prawdopodobnie najlepiej będzie, jeśli użyjesz zwykłej składni abstrakcyjnej pierwszego rzędu - reprezentujesz wbudowane funkcje z identyfikatorem i ocenisz w środowisku zawierającym odnośnik do funkcji pierwotnych. –