假設我們要定義一個簡單的DSL定義UI交互,我們可以創建對象,然後選擇它們:摘要結果類型
object TestCommand {
sealed trait EntityType
case object Project extends EntityType
case object Site extends EntityType
sealed trait TestCommand[A, E]
case class Create[A, E](entityType: EntityType, withEntity: E => A) extends TestCommand[A, E]
case class Select[A, E](entity: E, next: A) extends TestCommand[A, E]
}
我的問題是,我不希望指定創建命令的返回類型應該是什麼(上面的E
)。我想把這個決定交給翻譯。例如,如果我們使用異步REST調用創建對象,則E
可以是字符串,也可以是Future
。
如果我嘗試定義使用liftF
通常的方式DSL如下圖所示:
object TestDSL {
def create[E](entityType: EntityType): Free[TestCommand[?, E], E] =
Free.liftF(Create(entityType, identity: E => E): TestCommand[E, E])
def select[E](entity: E): Free[TestCommand[?, E], Unit] =
Free.liftF(Select[Unit, E](entity,()))
}
我收到以下錯誤:
Error:(10, 10) no type parameters for method liftF: (value: S[A])scalaz.Free[S,A] exist so that it can be applied to arguments (dsl.TestCommand.TestCommand[E,E])
--- because ---
argument expression's type is not compatible with formal parameter type;
found : dsl.TestCommand.TestCommand[E,E]
required: ?S[?A]
Free.liftF(Create(entityType, identity: E => E): TestCommand[E, E])
我不明白什麼是在腳麻上面的代碼,但更重要的問題是這是否是抽象出現在免費單子中的類型的正確方法。如果不是,什麼是正確的(功能)方法?
編輯:
在Haskell上述方法效果不會有問題:
{-# LANGUAGE DeriveFunctor #-}
-- |
module TestDSL where
import Control.Monad.Free
data EntityType = Project | Site
data TestCommand e a = Create EntityType (e -> a) | Select e a
deriving Functor
-- | The DSL
create :: EntityType -> Free (TestCommand e) e
create et = liftF $ Create et id
select :: e -> Free (TestCommand e)()
select e = liftF $ Select e()
-- | A sample program:
test :: Free (TestCommand e)()
test = do
p <- create Project
select p
_ <- create Site
return()
-- | A trivial interpreter.
interpTestCommand :: TestCommand String a -> IO a
interpTestCommand (Create Project withEntity) = do
putStrLn $ "Creating a project"
return (withEntity "Project X")
interpTestCommand (Create Site withEntity) = do
putStrLn $ "Creating a site"
return (withEntity "Site 51")
interpTestCommand (Select e next) = do
putStrLn $ "Selecting " ++ e
return next
-- | Running the interpreter
runTest :: IO()
runTest = foldFree interpTestCommand test
運行測試將導致以下的輸出:
λ> runTest
Creating a project
Selecting Project X
Creating a site
感謝您指出的設計缺陷,並在使用GADT的好例子!我看到的問題是,你仍然在免費的monad中決定什麼是「Site」和「Project」。我正在尋找的是將這個決定推遲給翻譯。另一方面,給Scala缺乏對GADT的支持,並且考慮替代方法是編寫DSL OO風格,我不知道這種設計缺陷有多嚴重(Java不會提供我認爲更好的類型安全性)。 –
我想我不明白你的用例。我認爲如果你的應用層('CommandF')知道存在兩個名爲'Site'和'Project'的實體,那麼它可能應該知道它們看起來像什麼 –
嗯,就像我在我的問題中所說的那樣:如果我通過休息調用來實現實體的創建,可能是這些實體('Project'和'Site')是未來的。如果我只是想嘲笑這些行爲,那麼這些實體可能是隨機字符串。這就是爲什麼我想推遲選擇特定的實體。例如,如果我選擇'data Project = Project {uuid :: String}',似乎我做了太多假設(但對於所有實際目的而言,這可能並不實際......)。例如,上面的選擇意味着我無法繞過'Future'或'Try' ... –