我正在探索Haskell中的选项,这些选项使我可以将业务逻辑与基础系统的技术实现区分开。例如,在Web服务器的上下文中,将Web服务器如何处理其接收的信息与从数据库中读取和写入数据库的方式分开。为此,有很多选择,但有两个特别引起我的注意:“免费Monad”和将能力记录作为参数传递。我很难看到一个相对于另一个的利弊。
一个代码片段来说明我在说什么:
module Lib where
import qualified Control.Monad.Free as FreeMonad
data MyGadt x
= Read (String -> x)
| Write String
x
instance Functor MyGadt where
fmap f (Read g) = Read (f . g)
fmap f (Write str x) = Write str (f x)
programWithFreeMonad :: FreeMonad.Free MyGadt ()
programWithFreeMonad = do
msg <- FreeMonad.liftF $ Read id
FreeMonad.liftF $ Write msg ()
ioInterpreter :: FreeMonad.Free MyGadt x -> IO x
ioInterpreter (FreeMonad.Pure x) = return x
ioInterpreter (FreeMonad.Free (Read f)) = getLine >>= (ioInterpreter . f)
ioInterpreter (FreeMonad.Free (Write str x)) = putStrLn str >> ioInterpreter x
runProgramWithFreeMonad :: IO ()
runProgramWithFreeMonad = ioInterpreter programWithFreeMonad
data Capabilities m = Capabilities
{ myRead :: m String
, myWrite :: String -> m ()
}
programWithCapabilities :: Monad m => Capabilities m -> m ()
programWithCapabilities capabilities = do
msg <- myRead capabilities
myWrite capabilities msg
runProgramWithCapabilities :: IO ()
runProgramWithCapabilities =
programWithCapabilities $ Capabilities {myRead = getLine, myWrite = putStrLn}
Run Code Online (Sandbox Code Playgroud)
这两种解决方案的写法不同,所以我认为许多人对它的外观和偏好有一种看法。但是我想知道是否有人对一种解决方案相对于另一种解决方案的利弊有所了解。
即使我们限制自己在免费的monad和功能记录之间进行选择(省略涉及monad变压器堆栈和类似MTL的类型类的解决方案),也有很多争论正在进行,但尚未解决。
传统上,简单的免费monad曾遭受两个缺陷的困扰:运行时效率低下(可能很重要,取决于比较器解释程序的运行速度)和缺乏可扩展性(如何将程序提升为功能更丰富的程序)集效果?)。
“单点数据类型”首先尝试解决可扩展性问题。后来,发表了“更自由的Monad,更可扩展的效果”论文,该论文提出了一种更复杂的自由类型以提高Monadic绑定的效率,并且提供了一种定义操作集的可扩展方式。实现此方法的主要库是:
freer-simple最简单的方法,显然也是最慢的方法。括号类型的操作似乎有一些限制。
fused-effects更有效的库,允许使用括号类型的操作。但是类型也更复杂。
polysemy相对较新的库,旨在快速并支持方括号类型的操作,同时保留简单类型。
这些库的一个吸引人的方面是,它们使您可以零碎地解释效果,挑选出一种效果,而其余效果则不解释。您也可以将抽象效果解释为其他抽象效果,而不必IO立即进行操作。
至于功能记录方法。诸如此类的程序programWithCapabilities在基本monad上具有多态性,并且记录了monad参数化的功能,这些程序在概念上与所谓的van Laarhoven Free Monad相关:
-- (ops m) is required to be isomorphic to (? n. i_n -> m j_n)
newtype VLMonad ops a = VLMonad { runVLMonad :: forall m. Monad m => ops m -> m a }
instance Monad (VLMonad ops) where
return a = VLMonad (\_ -> return a)
m >>= f = VLMonad (\ops -> runVLMonad m ops >>= f' ops)
where
f' ops a = runVLMonad (f a) ops
Run Code Online (Sandbox Code Playgroud)
从链接的帖子中:
Swierstra指出,通过将代表原始I / O操作的函子加在一起并取其总和的单价,我们可以使用多个I / O功能集产生值。可以将在特征子集上定义的值提升为总和生成的自由单子。可以通过获取原始操作的记录乘积来使用van Laarhoven自由单核执行等效的过程。通过将van Laarhoven自由单调组成适当的投影函数,可以挑选出必要的基本运算,从而提升在特征子集中定义的值。
似乎不存在(?)库为您提供预制VLMonad类型。存在的是记录功能记录的库,否则会工作IO,如RIO。人们仍然可以按照自己的逻辑抽象基本monad,然后RIO在运行逻辑时使用。或者更喜欢简单性,撕开隐藏IO逻辑的多面纱。
功能记录的方法可能具有易于掌握的优点,这是直接从事工作的增量IO。它还更类似于进行依赖注入的面向对象的方式。
处理记录本身的人体工程学成为中心。现在,通常使用“经典镜头”来使程序逻辑独立于具体的记录类型并促进程序的编写。也许也可以使用一天的可扩展记录(例如在自由方法中使用可扩展总和类型)。