简体   繁体   English

Free Monads中的抽象结果类型

[英]Abstract result types in Free Monads

Suppose we want to define a simple DSL for defining UI interactions where we can create objects and then select them: 假设我们要定义一个简单的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]

} 

The problem I have is that I wouldn't want to specify what the return type of the creation command should be ( E above). 我遇到的问题是我不想指定创建命令的返回类型应该是什么(上面的E )。 I would like to let this decision up to the interpreter. 我想让这个决定由口译员决定。 For instance, E could be a string, or a Future if we are creating objects with asynchronous REST calls. 例如,如果我们使用异步REST调用创建对象,则E可以是字符串,也可以是Future

If I try to define the DSL in the usual way using liftF as shown below: 如果我尝试使用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, ()))

}

I get the following error: 我收到以下错误:

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])

I cannot understand what is going wrong in the code above, but a more important question is whether this is the right way to abstract over the types appearing in free monads. 我无法理解上面代码中出了什么问题,但更重要的一个问题是,这是否是抽象出现在免费monad中的类型的正确方法。 If not, what is the right (functional) approach? 如果没有,什么是正确的(功能)方法?

EDIT : 编辑

In Haskell the approach described above works without a problem: 在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

Running the test will result in the following output: 运行测试将产生以下输出:

λ> runTest
Creating a project
Selecting Project X
Creating a site

Right now you have test :: Free (TestCommand e) () . 现在你有test :: Free (TestCommand e) () This means that the type of the entity e can be anything the caller wants, but it's fixed throughout the computation. 这意味着实体e的类型可以是调用者想要的任何类型,但它在整个计算过程中是固定的。

But that's not right! 但那不对! In the real world, the type of the entity that's created in response to a Create command depends on the command itself: if you created a Project then e should be Project ; 在现实世界中,为响应Create命令而创建的实体的类型取决于命令本身:如果您创建了一个Project那么e应该是Project ; if you created a Site then e should be Site . 如果你创建了一个Site那么e应该是Site So e shouldn't be fixed over the whole computation (because I might want to create Project s and Site s), and it shouldn't be up to the caller to pick an e . 所以e不应该被固定在整个计算(因为我可能需要创建Project S Site S),并且它不应该是由调用者挑选的e

Here's a solution in which the type of the entity depends on the value of the command. 这是一种解决方案,其中实体的类型取决于命令的值。

data Site = Site { {- ... -} }
data Project = Project { {- ... -} }

data EntityType e where
    SiteTy :: EntityType Site
    ProjectTy :: EntityType Project

The idea here is that pattern-matching on an EntityType e tells you what its e is. 这里的想法是EntityType e上的模式匹配告诉你它的e是什么。 In the Create command we'll existentially package up an entity e along with a bit of GADT evidence of the form EntityType e which you can pattern-match on to learn what e was. Create命令中,我们将存在性地打包实体e以及一些形式为EntityType e的GADT证据,您可以对其进行模式匹配以了解e是什么。

data CommandF r where
    Create :: EntityType e -> (e -> r) -> CommandF r
    Select :: EntityType e -> e -> r -> CommandF r

instance Functor CommandF where
    fmap f (Create t next) = Create t (f . next)
    fmap f (Select t e next) = Select t e (f next)

type Command = Free CommandF

create :: EntityType e -> Command e
create t = Free (Create t Pure)

select :: EntityType e -> e -> Command ()
select t e = Free (Select t e (Pure ()))

myComputation :: Command ()
myComputation = do
    p <- create ProjectTy  -- p :: Project
    select ProjectTy p
    s <- create SiteTy  -- s :: Site
    return ()

When the interpreter reaches a Create instruction, its job is to return an entity of the type that matches the wrapped EntityType . 当解释器到达Create指令时,其作用是返回与包装的EntityType匹配的类型的实体。 It has to inspect the EntityType in order to know what e is and behave appropriately. 它必须检查EntityType以了解e是什么并且行为恰当。

-- assuming createSite :: IO Site and createProject :: IO Project

interp :: CommandF a -> IO a
interp (Create SiteTy next) = do
    site <- createSite
    putStrLn "created a site"
    return (next site)
interp (Create ProjectTy next) = do
    project <- createProject
    putStrLn "created a project"
    return (next project)
-- plus clauses for Select

I don't know how this would translate into Scala exactly, but that's the gist of it in Haskell. 我不知道这会如何完全转化为Scala,但这是Haskell中的主旨。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM