Как переосмыслить термин DSL в окончательном подходе без тегов?
Добрый день всем.
Наше приложение использует типизированный DSL для описания определенной бизнес-логики. DSL поставляется с несколькими переводчиками без тегов.
Вот как объявляются его условия:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE EmptyDataDecls #-}
class Ctl impl where
-- Lift constants.
cnst :: Show t => t -> impl t
-- Obtain the state.
state :: impl (Maybe Int)
-- Test for equality.
eq :: impl Int -> impl Int -> impl Bool
-- If-then-else.
ite :: impl Bool -> impl t -> impl t -> impl t
-- Processing outcomes.
retry :: impl Outcome
finish :: impl Outcome
-- Require a value.
req :: impl (Maybe t) -> impl t
Бизнес-логика затем описывается с использованием фрагментов кода в этом DSL:
proc1 :: Ctl impl => impl Outcome
proc1 = ite (req state `eq` cnst 5) finish retry
Эти высокоуровневые определения используются с интерпретаторами. У меня есть
интерпретатор текста для получения читаемого текстового описания того, как
бизнес-процессы определено:
newtype TextE t = TextE { evalText :: String }
instance Ctl TextE where
cnst v = TextE $ show v
state = TextE "My current state"
eq v1 v2 = TextE $ concat [evalText v1, " equals ", evalText v2]
ite cond t e =
TextE $
concat ["If ", evalText cond, ", then ", evalText t, ", else ", evalText e]
retry = TextE "Retry processing"
finish = TextE "Finish"
req v = TextE $ concat ["(", evalText v, ")*"]
Интерпретация DSL с помощью TextE дает строку:
λ> (evalText proc1) :: String
"If (My current state)* equals 5, then Finish, else Retry processing"
Такое описание используется в качестве справочного материала для пользователей / аналитиков.
Я также могу оценить термин DSL для метаязыка (Haskell) с помощью другой интерпретатор, который является тем, как приложение на самом деле следует правила:
newtype HaskellE t = HaskellE { evalHaskell :: HaskellType t }
-- Interface between types of DSL and Haskell.
type family HaskellType t
instance Ctl HaskellE where
cnst v = HaskellE v
state = HaskellE dummyState
eq v1 v2 = HaskellE $ evalHaskell v1 == evalHaskell v2
ite cond t e =
HaskellE $
if (evalHaskell cond)
then (evalHaskell t)
else (evalHaskell e)
retry = HaskellE $ print "Retrying..."
finish = HaskellE $ print "Done!"
req term@(HaskellE v) =
case v of
Just v' -> HaskellE v'
Nothing ->
HaskellE (error $
"Could not obtain required value from ") -- ++ evalText term)
-- Dummy implementations so that this post may be evaluated
dummyState = Just 5
type Outcome = IO ()
type instance HaskellType t = t
Этот интерпретатор производит запускаемый код Хаскелла:
λ> (evalHaskell proc1) :: IO ()
"Done!"
Теперь к моей проблеме: я хотел бы использовать текстовый переводчик от HaskellE
переводчик. Для например, я хочу определить отказывающую ветвь
req
способом, включающим текстовое представление вложенного термина
(обычно получается с помощью evalText term
) в сообщении об ошибке. То
соответствующий код закомментирован в реализации req
для HaskellE
выше. Если комментарий отменен, код выглядит следующим образом:
HaskellE (error $
"Could not obtain required value from " ++ evalText term)
Однако система типов не позволяет мне сделать это:
tagless.lhs:90:71: Couldn't match expected type ‘TextE t0’ …
with actual type ‘HaskellE (Maybe t)’
Relevant bindings include
v :: HaskellType (Maybe t)
(bound at /home/dzhus/projects/hs-archive/tagless.lhs:85:22)
term :: HaskellE (Maybe t)
(bound at /home/dzhus/projects/hs-archive/tagless.lhs:85:7)
req :: HaskellE (Maybe t) -> HaskellE t
(bound at /home/dzhus/projects/hs-archive/tagless.lhs:85:3)
In the first argument of ‘evalText’, namely ‘term’
In the second argument of ‘(++)’, namely ‘evalText term’
Compilation failed.
Сообщение в основном говорит о том, что переводчик Хаскелл уже
был выбран, когда переменная типа impl
был создан экземпляр, и я
нельзя использовать текстовый переводчик изнутри Хаскелля.
Чего я не могу понять, так это: как я могу переосмыслить термин из Хаскеллы в текст?
Если я здесь совершенно не прав, как мне изменить свой подход, чтобы я мог на самом деле используйте текстовый переводчик от Haskell one без повторное внедрение его в Хаскелле? Похоже, это вполне осуществимо. с первоначальным подходом вместо окончательного.
Я снял свой фактический DSL и упростил типы и интерпретаторы ради краткости.
1 ответ:
Можно отслеживать как значение, так и информацию о выражении, создавшем значение. Если вы сделаете это, вы потеряете некоторые преимущества производительности вашего окончательного представления без тегов.
data Traced t a = Traced {evalTraced :: HaskellType a, trace :: t a}
Мы ожидаем использовать его с трассировкой
TextE
, поэтому для удобства определим следующееevalTextTraced :: Traced TextE a -> HaskellType a evalTextTraced = evalTraced
Этот класс позволяет нам восстанавливать сообщения об ошибках из
trace
class Show1 f where show1 :: f a -> String instance Show1 TextE where show1 = evalText instance (Show1 t) => Show1 (Traced t) where show1 = show1 . trace
Этот интерпретатор сохраняет след любого другого интерпретатора
Ctl t
, который мы можем восстановить ошибку сообщения от при интерпретации aTraced t
.instance (Show1 t, Ctl t) => Ctl (Traced t) where cnst v = Traced v (cnst v) state = Traced dummyState state eq (Traced v1 t1) (Traced v2 t2) = Traced (v1 == v2) (eq t1 t2) ite (Traced vc tc) (Traced vt tt) (Traced ve te) = Traced (if vc then vt else ve) (ite tc tt te) retry = Traced (print "Retrying...") retry finish = Traced (print "Done!") finish req (Traced v t) = case v of Just v' -> Traced v' rt Nothing -> Traced (error ("Could not obtain required value from " ++ show1 rt)) rt where rt = req t
Ваш пример ведет себя так, как и ожидалось
print . evalText . trace $ proc1 evalTextTraced proc1 "If (My current state)* equals 5, then Finish, else Retry processing" "Done!"
Мы все еще можем
evalText
пример с неудачным требованием, но при попытке его запуска появляется информативное сообщение об ошибкеproc2 :: Ctl impl => impl Outcome proc2 = ite (req (cnst Nothing) `eq` cnst 5) finish retry print . evalText . trace $ proc2 evalTextTraced proc2 "If (Nothing)* equals 5, then Finish, else Retry processing" finaltagless.hs: Could not obtain required value from (Nothing)*