如何在PureScript中实现“finally tagless”类型类的Monadic实例?
我正在使用“finally tagless”风格在PureScript中实现嵌入式DSL。回购可在https://github.com/afcondon/purescript-finally-tagless-ex如何在PureScript中实现“finally tagless”类型类的Monadic实例?
这是问题所在。给予了非常简化的文件系统的抽象定义:
class (Monad m) <= MonadFileSystem m where
cd :: FilePath -> m Unit
ls :: m (Array (Tuple FilePath FileType))
cat :: Array FilePath -> m String
人们可以很容易提供一个实现如可用于作为嵌入式语言和解释这一个(https://github.com/afcondon/purescript-finally-tagless-ex/blob/master/MonadicEx/src/FakeFileSystem.purs)(或运行)来评估作为一个字符串(或者你可以对结构进行静态分析,而不是把它变成一个字符串)。
原则上,也可以有一个副作用的例子,它实际上会与文件系统交互,但它可以“完全解释”完全相同的嵌入式语言。我想为此使用purescript-node-fs
,这意味着要在某处接受Eff (fs :: FS, err :: EXCEPTION | eff)
。
我的问题是 - 如何实际实现“真实”,有效的实例?您是否必须更改cd
,ls
和cat
的签名?或者你可以以某种方式评估Eff
中的整个monad,以便这些函数不需要在其签名中携带Eff
?
因为你想为Eff
创建一个实例,所以这里有一点小问题,因为我们需要在该类型中包含该行,但正如你可能发现的那样,编译器在这种情况下会抱怨实例头。
一种选择是使用newtype
:
import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Exception (EXCEPTION)
import Data.Tuple (Tuple)
import Node.FS (FS)
import Node.Path (FilePath)
type FileType = String
class (Monad m) <= MonadFileSystem m where
cd :: FilePath -> m Unit
ls :: m (Array (Tuple FilePath FileType))
cat :: Array FilePath -> m String
newtype FSEff eff a = FSEff (Eff (fs :: FS, err :: EXCEPTION | eff) a)
runFSEff :: forall eff a. FSEff eff a -> Eff (fs :: FS, err :: EXCEPTION | eff) a
runFSEff (FSEff fse) = fse
derive newtype instance functorFSEff :: Functor (FSEff eff)
derive newtype instance applyFSEff :: Apply (FSEff eff)
derive newtype instance applicativeFSEff :: Applicative (FSEff eff)
derive newtype instance bindFSEff :: Bind (FSEff eff)
derive newtype instance monadFSEff :: Monad (FSEff eff)
instance monadFileSystemFSEff :: MonadFileSystem (FSEff eff) where
cd _ = pure unit
ls = pure []
cat _ = pure "meow"
但也有一些挂羊头卖狗肉,可以使用函数依赖,在那里你可以指定一个像等式约束的行,而不是来完成。这将编译,但使用这种技术真正的但我还没有尝试,所以我不能保证它在更大范围内明确工作:
import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Exception (EXCEPTION)
import Data.Tuple (Tuple)
import Node.FS (FS)
import Node.Path (FilePath)
type FileType = String
class (Monad m) <= MonadFileSystem m where
cd :: FilePath -> m Unit
ls :: m (Array (Tuple FilePath FileType))
cat :: Array FilePath -> m String
instance monadFileSystemEff :: EffectRowEquals r (fs :: FS, err :: EXCEPTION | eff) => MonadFileSystem (Eff r) where
cd _ = pure unit
ls = pure []
cat _ = pure "meow"
-- This should probably go in `purescript-type-equality`:
class EffectRowEquals (a ∷ # !) (b ∷ # !) | a → b, b → a where
toER ∷ ∀ r. r a → r b
fromER ∷ ∀ r. r b → r a
instance reflER ∷ EffectRowEquals r r where
toER er = er
fromER er = er
非常感谢真正详细的解答(S)。前者当然适用于仅有副作用的情况,但我仍然很难以这样的方式来制定它,使得它具有您在许多情况下需要的当前工作目录的背景。我尝试了类似于FakeFS实例的类似于Zipper示例的黑客攻击,但破坏了所有漂亮的newtype派生。 第二种解决方案非常可爱且富有表现力,但似乎会生成(虚假?)孤立实例错误,除非与AbstractFileSystem同位置? –
确实,你需要定义后一个以及MonadFileSystem来避免孤儿错误(对于它的另一个非孤儿位置将在模块中使用'Eff',显然不是一个选项!),但是在这里给出你的后续问题,听起来你可能需要'Eff'周围的'StateT'来维护cwd等等 - 无论如何,我可能会去'newtype'路由,这将避免孤儿问题太。 –