Haskell ";“读者”;单子

Haskell ";“读者”;单子,haskell,monads,Haskell,Monads,好的,编写器monad允许您将内容写入某种容器,并在最后将该容器取回。在大多数实现中,“容器”实际上可以是任何幺半群 现在,还有一个“阅读器”单子。您可能会认为,这将提供双重操作—增量地从某种容器中读取,一次读取一个项目。事实上,这不是通常的阅读器monad提供的功能。(相反,它只提供了对半全局常数的简单访问。) 要真正写出一个与通常的编写器monad是对偶的monad,我们需要某种与monoid是对偶的结构 有人知道这种双重结构可能是什么吗 有人写过这个单子吗?有一个有名的名字吗 我不完全确定

好的,编写器monad允许您将内容写入某种容器,并在最后将该容器取回。在大多数实现中,“容器”实际上可以是任何幺半群

现在,还有一个“阅读器”单子。您可能会认为,这将提供双重操作—增量地从某种容器中读取,一次读取一个项目。事实上,这不是通常的阅读器monad提供的功能。(相反,它只提供了对半全局常数的简单访问。)

要真正写出一个与通常的编写器monad是对偶的monad,我们需要某种与monoid是对偶的结构

  • 有人知道这种双重结构可能是什么吗
  • 有人写过这个单子吗?有一个有名的名字吗

  • 我不完全确定幺半群的对偶应该是什么,但我认为对偶(可能是错误的)是某物的对立面(仅仅基于一个Comonad是一个Monad的对偶,并且具有所有相同的操作,但方向相反)。与其基于
    mappend
    mempty
    我不如基于:

    fold :: (Foldable f, Monoid m) => f m -> m
    
    如果我们在这里专门列出f,我们会得到:

    fold :: Monoid m => [m] -> m
    
    在我看来,这似乎包含了所有的幺半群类,尤其是

    mempty == fold []
    mappend x y == fold [x, y]
    
    那么,我猜这个不同幺半群类的对偶是:

    unfold :: (Comonoid m) => m -> [m]
    
    这很像我在hackage上看到的幺半群阶乘类

    所以在这个基础上,我认为你描述的“读者”单子应该是一个。supply monad实际上是一个值列表的状态转换器,因此在任何时候我们都可以选择从列表中提供一个项。在这种情况下,列表将是unfold.supply monad的结果


    我应该强调,我不是哈斯凯尔的专家,也不是一个专家理论家。但这就是你的描述让我想到的。

    幺半群的对偶是一个comonoid。回想一下,幺半群被定义为(同构的东西)

    根据这些法律

     combine (create (),x) = x
     combine (x,create ()) = x
     combine (combine (x,y),z) = combine (x,combine (y,z))
    
    因此

    需要一些标准操作

     first :: (a -> b) -> (a,c) -> (b,c)
     second :: (c -> d) -> (a,c) -> (a,d)
    
     idL :: ((),x) -> x
     idR :: (x,()) -> x
    
     assoc :: ((x,y),z) -> (x,(y,z))
    
    有这样的法律

    idL $ first delete $ (split x) = x
    idR $ second delete $ (split x) = x
    assoc $ first split (split x) = second split (split x)
    
    这个typeclass看起来很奇怪是有原因的。它有一个例子

    instance Comonoid m where
       split x = (x,x)
       delete x = ()
    
    在Haskell中,这是唯一的例子。我们可以将reader改写为writer的完全对偶,但因为comonoid只有一个实例,所以我们得到了与标准reader类型同构的东西

    使所有类型都是共面体是“笛卡尔闭范畴”中的“笛卡尔”范畴的原因。“单倍体闭范畴”类似于CCC,但没有此属性,并且与子结构类型系统相关。线性逻辑的吸引力部分在于它的对称性增强,这就是一个例子。然而,拥有子结构类型允许您定义具有更有趣属性的comonoid(支持资源管理之类的东西)。事实上,这为理解C++中的复制构造函数和析构函数的作用提供了框架(尽管C++由于指针的存在而没有强制执行重要的属性)。 编辑:来自科莫类的读取器

    newtype Reader r x = Reader {runReader :: r -> x}
    forget :: Comonoid m => (m,a) -> a
    forget = idL . first delete
    
    instance Comonoid r => Monad (Reader r) where
       return x = Reader $ \r -> forget (r,x)
       m >>= f = \r -> let (r1,r2) = split r in runReader (f (runReader m r1)) r2
    
    ask :: Comonoid r => Reader r r
    ask = Reader id
    

    请注意,在上面的代码中,每个变量在绑定后只使用一次(因此这些变量都将使用线性类型)。单子定律的证明是琐碎的,只需要科摩罗定律就行了。因此,
    读卡器
    实际上与
    写卡器
    是双重的

    供应是基于状态的,这使得它对于某些应用程序来说是次优的。例如,我们可能希望生成一个由提供的值(例如随机数)组成的无限树:

    因为单子定律要求
    f>=return=f
    ,所以这意味着
    r'=r
    (>>=)
    的定义中。。但是,单子定律也要求
    返回x>=f=fx
    ,因此
    r'=r
    。因此,
    Supply
    要成为一个单子,
    splitx=(x,x)
    ,这样您就可以重新获得常规的
    阅读器

    Haskell中使用的许多单子都不是真正的单子——也就是说,它们只满足一些等价关系的定律。例如,如果你按照定律变换,许多不确定的单子将以不同的顺序给出结果。但这没关系,如果您只是想知道某个特定元素是否出现在输出列表中,而不是在哪里,那么这仍然是monad

    如果您允许
    Supply
    成为一个单子,直到某个等价关系,那么您可以得到非平凡的拆分。例如,将构造可拆分实体,该实体将以未指定的顺序从列表中分配唯一的标签(使用
    不安全*
    magic)——因此,值供应的供应单元组将是标签排列的单元组。这是许多应用程序所需要的全部。事实上,有一个函数

    runSupply :: (forall r. Eq r => Supply r a) -> a
    

    它抽象了这个等价关系,给出了一个定义良好的纯接口,因为它允许你对标签做的唯一事情就是看它们是否相等,如果你排列它们,这不会改变。如果这个
    runSupply
    是您在
    Supply
    上允许的唯一观察,那么在一系列独特标签上的
    Supply
    就是一个真正的单子。

    编剧单子的双重身份不是一个共同编剧吗?(只是开玩笑,我真的不理解你的想象。也许是一个
    状态
    消费一个流?@dainelfischer这听起来很疯狂,甚至可能是正确的……第二个问题让我想到了人们提出的无限流处理库(迭代器、管道、导管等)“阅读器”monad通常被称为环境monad(我认为后一个名字可能实际上比前一个名字早——早期参考文献见David Espinosa在90年代中期的论文),因此期望读者是作家的“双重”可能期望过高。我不确定科摩诺人的东西,但是供应的单子看起来确实很像我所想的。这肯定不是唯一一个可能的例子吗?怎么样:
    实例Comonoid[a],其中{delete=const();s
    
    newtype Reader r x = Reader {runReader :: r -> x}
    forget :: Comonoid m => (m,a) -> a
    forget = idL . first delete
    
    instance Comonoid r => Monad (Reader r) where
       return x = Reader $ \r -> forget (r,x)
       m >>= f = \r -> let (r1,r2) = split r in runReader (f (runReader m r1)) r2
    
    ask :: Comonoid r => Reader r r
    ask = Reader id
    
    tree :: (Something r) => Supply r (Tree r)
    tree = Branch <$> supply <*> sequenceA [tree, tree]
    
    newtype Supply r a = Supply { runSupply :: r -> a }
    
    instance (Splittable r) => Monad (Supply r) where
        return = Supply . const
        Supply m >>= f = Supply $ \r ->
            let (r',r'') = split r in
            runSupply (f (m r')) r''
    
    runSupply :: (forall r. Eq r => Supply r a) -> a