【问题标题】:What effects are modeled by the stream (infinite list) monad?流(无限列表)monad 模拟了哪些效果?
【发布时间】:2018-07-25 00:28:46
【问题描述】:

monad 的各种实例模拟不同类型的效果:例如,Maybe 模拟偏心,List 非确定性,Reader 只读状态。我想知道对于流数据类型(或无限列表或共同列表)的 monad 实例是否有如此直观的解释,data Stream a = Cons a (Stream a)(见下文其 monad 实例定义)。我偶然发现了 few different occasions 上的流单子,我想更好地了解它的用途。

data Stream a = Cons a (Stream a)

instance Functor Stream where
    fmap f (Cons x xs) = Cons (f x) (fmap f xs)

instance Applicative Stream where
    pure a                      = Cons a (pure a)
    (Cons f fs) <*> (Cons a as) = Cons (f a) (fs <*> as)

instance Monad Stream where
    xs >>= f = diag (fmap f xs)

diag :: Stream (Stream a) -> Stream a
diag (Cons xs xss) = Cons (hd xs) (diag (fmap tl xss))
    where
        hd (Cons a _ ) = a
        tl (Cons _ as) = as

P.S.:我不确定我的语言是否非常精确(尤其是在使用“效果”这个词时),请随时纠正我。

【问题讨论】:

  • 它模拟无限计算,您可以在其中观察计算的各个步骤。

标签: list haskell stream monads


【解决方案1】:

Stream monad 与 Reader Natural 同构(Natural:自然数),这意味着 StreamReader Natural 之间存在双射,它保留了它们的单子结构。

直观

Stream aReader Natural a (Natural -&gt; a) 都可以看作是由整数索引的a 的无限集合。

fStream = Cons a0 (Cons a1 (Cons a2 ...))

fReader = \i -> case i of
  0 -> a0
  1 -> a1
  2 -> a2
  ...

他们的ApplicativeMonad 实例都按索引组成元素。更容易展示Applicative 的直觉。下面,我们展示了a0, a1, ...Ab0, b1, ...B,以及它们的组合AB = liftA2 (+) A B,以及函数的等效演示。

fStreamA  = Cons  a0     (Cons  a1     ...)
fStreamB  = Cons     b0  (Cons     b1  ...)
fStreamAB = Cons (a0+b0) (Cons (a1+b1) ...)
fStreamAB = liftA2 (+) fStreamA fStreamB

-- lambda case "\case" is sugar for "\x -> case x of"
fReaderA = \case 0 -> a0    ; 1 -> a1    ; ...
fReaderB = \case 0 ->    b0 ; 1 -> b1    ; ...
fReaderC = \case 0 -> a0+b0 ; 1 -> a1+b1 ; ...
fReaderC = liftA2 (+) fReaderA fReaderB = \i -> fReaderA i + fReaderB i

正式

双射:

import Numeric.Natural  -- in the base library

-- It could also be Integer, there is a bijection Integer <-> Natural
type ReaderN a = Natural -> a

tailReader :: ReaderN a -> ReaderN a
tailReader r = \i -> r (i+1)

toStream :: ReaderN a -> Stream a
toStream r = Cons (r 0) (toStream (tailReader r))

fromStream :: Stream a -> ReaderN a
fromStream (Cons a s) = \i -> case i of
  0 -> a
  i -> fromStream s (i-1)

toStreamfromStream 是双射,这意味着它们满足这些方程:

toStream (fromStream s) = s :: Stream a
fromStream (toStream r) = r :: ReaderN a

“同构”是一个一般概念;两件事同构通常意味着存在满足某些方程的双射,这取决于所考虑的结构或界面。在这种情况下,我们讨论的是 monad 的结构,如果存在满足这些方程的双射,我们说两个 monad 是同构的:

toStream (return a) = return a
toStream (u >>= k) = toStream u >>= (toStream . k)

这个想法是,无论我们在双射“之前或之后”应用函数return(&gt;&gt;=),我们都会得到相同的结果。 (使用fromStream 的类似方程可以从这两个方程和上面的其他两个方程推导出来)。

【讨论】:

  • 你能详细说明一下吗?我很难理解你到底是怎么看到这里定义的ask' :: a
  • Reader 是函数类型的另一个名称(我应该从一开始就这么说):type Reader r a = r -&gt; a。我添加了对这些类型同构的含义的解释。
【解决方案2】:

@Li-yao Xia 的回答几乎涵盖了它,但如果它有助于您的直觉,请将 Stream monad 视为对无限序列的并行计算进行建模。 Stream 值本身是一个(无限)值序列,我可以使用Functor 实例将相同的函数并行应用于序列中的所有值; Applicative 实例将给定函数的 sequence 应用于一系列值,逐点将每个函数应用于相应的值;和 Monad 实例对序列中的每个值应用计算,结果可能取决于值及其在序列中的位置。

作为一些典型操作的示例,这里有一些示例序列和一个 Show-instance

instance (Show a) => Show (Stream a) where
  show = show . take 10 . toList
nat = go 1 where go x = Cons x (go (x+1))
odds = go 1 where go x = Cons x (go (x+2))

给予:

> odds
[1,3,5,7,9,11,13,15,17,19]
> -- apply same function to all values
> let evens = fmap (1+) odds
> evens
[2,4,6,8,10,12,14,16,18,20]
> -- pointwise application of functions to values
> (+) <$> odds <*> evens
[3,7,11,15,19,23,27,31,35,39]
> -- computation that depends on value and structure (position)
> odds >>= \val -> fmap (\pos -> (pos,val)) nat
[(1,1),(2,3),(3,5),(4,7),(5,9),(6,11),(7,13),(8,15),(9,17),(10,19)]
> 

ApplicativeMonadic 计算之间的区别与其他 monad 类似:applicative 操作具有静态结构,因为每个结果 a &lt;*&gt; b 仅取决于相应元素的值在ab 中,与它们如何适应更大的结构(即它们在序列中的位置)无关;相反,monadic 操作可以具有依赖于基础值的结构,因此在表达式as &gt;&gt;= f 中,对于as 中的给定值a,相应的结果可以取决于具体值@987654337 @ 和结构上它在序列中的位置(因为这将确定序列中的哪个元素 f a 将提供结果)。

事实证明,在这种情况下,一元计算明显的额外普遍性并没有转化为任何实际额外的普遍性,正如您可以看到的那样,上面的最后一个示例等同于纯应用操作:

(,) <$> nat <*> odds

更一般地说,给定一个单子动作f :: a -&gt; Stream b,它总是可以写成:

f a = Cons (f1 a) (Cons (f2 a) ...))

对于适当定义的f1 :: a -&gt; bf2 :: a -&gt; b等,之后我们就可以将一元动作表示为应用动作:

as >>= f = (Cons f1 (Cons f2 ...)) <*> as

将此与List monad 中发生的情况进行对比:鉴于f :: a -&gt; List b如果我们可以这样写:

f a = [f1 a, f2 a, ..., fn a]

(特别是结果中的元素数量将由f单独决定,而不管a的值如何),那么我们就会遇到同样的情况:

as >>= f = as <**> [f1,...,fn]

而且每个单子列表操作基本上都是应用操作。

因此,并非所有有限列表的长度都相同这一事实使得List monad 比它的应用程序更强大,但是因为所有(无限)序列都是相同的长度,@987654352 @monad 在应用实例上没有添加任何内容。

【讨论】:

  • 我发现您的观察结果是,MonadApplicativeStream 实例具有相似的功能,这非常有启发性。谢谢!
  • 是您的“Applicative 实例应用[ies] 一系列给定 函数[强调我的] 到一系列值”,但是xs = liftA2 (+) (Cons 0 (Cons 1 xs)) (Cons 1 xs) 呢?那里没有(完全)给定的函数,即尽管 Applicative 它本质上是 sequential 计算。也许可以辩论什么是“完全给定的函数”,但我认为该计算的基本顺序性质是完全不言而喻的。 (背景见this)。你能澄清一下吗?
猜你喜欢
  • 1970-01-01
  • 2020-05-15
  • 1970-01-01
  • 2017-02-06
  • 1970-01-01
  • 1970-01-01
  • 2018-10-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多