(从诸如此类的帖子中)我了解到,这是将模型与数据访问分开。它与众所周知的存储库模式有何不同?他们似乎具有相同的动机。
#1 楼
实际上,实际模式比仅数据访问要普遍得多。这是一种轻量级的方法,它可以创建一种特定于域的语言,为您提供AST,然后让一个或多个解释器根据您的喜好“执行” AST。免费的monad部分非常方便一种可以使用Haskell的标准monad工具(例如do-notation)进行汇编的AST的方法,而无需编写大量的自定义代码。这也确保了DSL是可组合的:您可以将DSL分为几部分进行定义,然后以结构化的方式将各部分组合在一起,从而充分利用Haskell的正常抽象,例如函数。
使用免费的monad可以您是可组合DSL的结构;您要做的就是指定零件。您只需编写一种数据类型,其中包含DSL中的所有操作。这些动作可以做任何事情,而不仅仅是数据访问。但是,如果将所有数据访问都指定为操作,则将获得AST,该AST指定对数据存储区的所有查询和命令。然后,您可以按自己喜欢的方式进行解释:对实时数据库运行它,对模拟运行它,只需记录调试命令,甚至尝试优化查询。
让我们来看一个非常简单的示例例如,键值存储。现在,我们将键和值都视为字符串,但是您可以花一点精力来添加类型。
data DSL next = Get String (String -> next)
| Set String String next
| End
next
参数使我们可以组合动作。我们可以使用它来编写一个程序,该程序获取“ foo”并设置具有该值的“ bar”:不幸的是,对于有意义的DSL来说,这还不够。由于我们使用next
进行合成,因此p1
的类型与程序的长度相同(即3个命令):p1 = Get "foo" $ \ foo -> Set "bar" foo End
在这个特定的示例中,像这样使用
next
似乎有点奇怪,但是如果我们希望我们的动作具有不同的类型变量,则这一点很重要。例如,我们可能需要键入get
和set
。请注意,每个动作的
next
字段如何不同。这暗示我们可以使用它使DSL
成为函子:p1 :: DSL (DSL (DSL next))
实际上,这是使其成为函子的唯一有效方法,因此我们可以使用
deriving
通过启用DeriveFunctor
扩展名自动创建实例。下一步是
Free
类型本身。那就是我们用来表示我们的AST结构的基础,它建立在DSL
类型的基础上。您可以将其视为类型级别的列表,其中“ cons”只是嵌套函子,如DSL
:instance Functor DSL where
fmap f (Get name k) = Get name (f . k)
fmap f (Set name value next) = Set name value (f next)
fmap f End = End
所以我们可以使用
Free DSL next
来提供程序不同大小的相同类型:-- compare the two types:
data Free f a = Free (f (Free f a)) | Return a
data List a = Cons a (List a) | Nil
哪个类型更好:
p2 = Free (Get "foo" $ \ foo -> Free (Set "bar" foo (Free End)))
实际的表达式及其所有构造函数仍然很难使用!这就是monad的一部分。正如名称“ free monad”所暗示的,
Free
是monad,只要f
(在本例中为DSL
)是一个函子即可:p2 :: Free DSL a
现在到了:可以使用
do
表示法使DSL表达式更好。唯一的问题是next
应该放入什么?好吧,我们的想法是使用Free
结构进行合成,因此我们只需将Return
放入每个下一个字段,然后使用do表示法完成所有检查:>这更好,但是仍然有点尴尬。我们到处都有
Free
和Return
。令人高兴的是,有一种可以利用的模式:将DSL操作“提升”到Free
的方式始终相同-我们将其包装在Free
中,然后将Return
应用于next
:instance Functor f => Monad (Free f) where
return = Return
Free a >>= f = Free (fmap (>>= f) a)
Return a >>= f = f a
现在,使用此代码,我们可以为每个命令编写漂亮的版本并拥有完整的DSL:
p3 = do foo <- Free (Get "foo" Return)
Free (Set "bar" foo (Return ()))
Free End
使用此代码,我们可以编写程序:
liftFree :: Functor f => f a -> Free f a
liftFree action = Free (fmap Return action)
巧妙的窍门是,虽然
p4
看起来像一个命令式程序,但实际上它是一个具有值的表达式get key = liftFree (Get key id)
set key value = liftFree (Set key value ())
end = liftFree End
因此,该模式的免费monad部分使我们获得了DSL,该DSL可以生成语法漂亮的语法树。我们也可以不使用
End
来编写可组合的子树;例如,我们可以让follow
取得一个键,获取它的值,然后将其用作键本身:p4 :: Free DSL a
p4 = do foo <- get "foo"
set "bar" foo
end
现在
follow
可以在我们的程序中使用了像get
或set
一样:Free (Get "foo" $ \ foo -> Free (Set "bar" foo (Free End)))
所以我们也为DSL获得了很好的合成和抽象。
现在我们有了一棵树,我们进入模式的后半部分:解释器。我们可以通过只对它进行模式匹配来解释它,但是我们喜欢它。这将使我们能够针对
IO
中的实际数据存储以及其他内容编写代码。这是一个假设的数据存储示例:follow :: String -> Free DSL String
follow key = do key' <- get key
get key'
这将很高兴地评估任何
DSL
片段,甚至没有以end
结尾的片段。幸运的是,通过将输入类型签名设置为end
,我们可以使该函数的“安全”版本仅接受以(forall a. Free DSL a) -> IO ()
关闭的程序。虽然旧签名接受任何Free DSL a
的a
(例如Free DSL String
,Free DSL Int
等),但此版本仅接受适用于所有可能的Free DSL a
的a
-我们只能使用end
来创建。这保证了我们完成操作后不会忘记关闭连接。p5 = do foo <- follow "foo"
set "bar" foo
end
(我们不能仅仅通过给
runIO
这种类型来开始,因为它对于我们的递归调用将无法正常工作。但是,我们可以将runIO
的定义移至where
中的safeRunIO
块中,并获得相同的效果而无需公开两个版本的函数。)在
IO
中运行我们的代码并不是我们唯一能做的。为了进行测试,我们可能想针对纯State Map
运行它。编写代码是一个不错的练习。所以这是免费的monad +解释器模式。我们利用免费的monad结构来制作所有的管道,从而创建DSL。我们可以在DSL中使用do-notation和标准monad功能。然后,要实际使用它,我们必须以某种方式对其进行解释。由于树最终只是一个数据结构,因此我们可以根据不同的目的对其进行解释。
当我们使用它来管理对外部数据存储的访问时,它的确类似于Repository模式。它介于我们的数据存储和代码之间,将两者分开。但是,在某些方面,它更具体:“存储库”始终是带有显式AST的DSL,我们可以根据需要使用它。
但是模式本身比这更笼统。它可以用于很多事情,而不必涉及外部数据库或存储。无论您希望对DSL的效果或多个目标进行精细控制,它都是有意义的。
#2 楼
一个免费的monad基本上是一个monad,它以与计算相同的“形状”构建数据结构,而不是执行任何更复杂的操作。 (有一些示例可以在网上找到。)然后将该数据结构传递给一段代码,该代码结构将使用它并执行操作。*我并不完全熟悉存储库模式,但是从我阅读的内容看来作为更高级别的架构,可以使用免费的monad +解释器来实现它。另一方面,免费的monad +解释器也可以用于实现完全不同的事物,例如解析器。*值得注意的是,该模式并非monad独有,实际上可以使用免费的应用程序或免费的箭头生成更有效的代码。 (解析器是另一个例子。)
评论
抱歉,我应该对存储库更加清楚。 (我忘记了并不是每个人都有业务系统/ OO / DDD背景!)存储库基本上封装了数据访问并为您重新绑定域对象。它通常与依赖倒置一起使用-您可以“插入”回购的不同实现(用于测试,或者在需要切换数据库或ORM时有用)。域代码仅在不知道从何处获取域对象的情况下调用repository.Get()。
–本杰明·霍奇森(Benjamin Hodgson)
2014年6月2日在21:24
评论
为什么称其为“免费”单子?
–本杰明·霍奇森(Benjamin Hodgson)
2014年6月3日20:13在
“免费”名称来自类别理论:ncatlab.org/nlab/show/free+object,但这有点意味着它是“最小” monad -仅对其有效的操作是monad操作,因为它具有“忘记了”这是其他结构。
–Boyd Stephen Smith Jr.
2014年6月3日在21:04
@BenjaminHodgson:Boyd是完全正确的。除非您只是好奇,否则我不会担心太多。丹·皮波尼(Dan Piponi)很好地谈论了“免费”对BayHac的意义,值得一看。请尝试跟随他的幻灯片,因为视频中的视觉效果完全没用。
– Tikhon Jelvis
2014年6月3日21:56
@sacundim:您能详细说明一下吗?尤其是句子“ Free monads也是规范化的程序表示形式,这使解释程序无法区分do-notation不同但实际上”均值相同”的程序。
–乔治
15年1月2日在22:17
只需重新阅读此答案,我就会有一个问题:Free会给您带来什么,而使用显式递归数据类型是您无法获得的?我的粗略理解是Free将类型的递归结构与类型本身分开(有点像Fix)。它使用Free DSL填充下一个类型参数。通过忽略该类型参数并自己编写Monad实例,我们错过了这项技术的优势吗?类似于数据DSL a =获取字符串(字符串->(DSL a))|设置字符串字符串(DSL a)|返回一个
–本杰明·霍奇森(Benjamin Hodgson)
2015年4月6日19:24