流(无限列表)monad模拟了哪些效果?

时间:2018-02-14 12:19:49

标签: list haskell stream monads

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

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。:我不确定我的语言是否非常精确(特别是使用“效果”这个词时),所以请随意纠正我。

2 个答案:

答案 0 :(得分:13)

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

直观地

可以将Stream aReader Natural aNatural -> a)视为表示由整数编制索引的a的无限集合。

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

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

他们的ApplicativeMonad个实例都以索引方式组成元素。显示Applicative的直觉更容易。下面,我们会显示A的{​​{1}}流,a0, a1, ...的{​​{1}}及其作品B,以及功能的等效表示。

b0, b1, ...

形式上

双射:

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) 是双射,意味着它们满足这些等式:

toStream

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

fromStream

我们的想法是,无论我们在&#34;之前还是之后应用函数toStream (fromStream s) = s :: Stream a fromStream (toStream r) = r :: ReaderN a toStream (return a) = return a toStream (u >>= k) = toStream u >>= (toStream . k) &#34;我们都会得到相同的结果双射。 (然后可以从这两个方程和上面的另外两个方程导出使用return的类似方程式。)

答案 1 :(得分:3)

  • @ Li-yao Xia的答案几乎涵盖了它,但如果它有助于你的直觉,可以将Stream monad想象为无限的并行计算序列。 Stream值本身是一个(无限)值序列,我可以使用Functor实例将相同的函数并行应用于序列中的所有值; Applicative实例将给定函数的序列应用于值序列,逐点将每个函数应用于相应的值;和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)]
    > 
    

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

    事实证明,在这种情况下,monadic计算的明显额外的一般性并没有转化为任何实际的附加的一般性,正如你可以看到上面的最后一个例子相当于纯粹的应用操作:

    (,) <$> nat <*> odds
    

    更一般地说,鉴于monadic动作f :: a -> Stream b,它总是可以写成:

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

    对于适当定义的f1 :: a -> bf2 :: a -> b等,之后我们将能够将monadic动作表示为应用程序操作:

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

    将此与List monad中的情况进行对比:给定f :: a -> List b如果我们可以写:

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

    (特别是意味着结果中元素的数量仅由f确定,无论a的值如何),那么我们都会遇到相同的情况:

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

    每个monadic list操作都是一个基本的应用操作。

    因此,并非所有有限列表都具有相同的长度使List monad比其应用更强大,但因为所有(无限)序列 的长度相同, Stream monad对应用实例没有任何补充。