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。:我不确定我的语言是否非常精确(特别是使用“效果”这个词时),所以请随意纠正我。
答案 0 :(得分:13)
Stream
monad与Reader Natural
(Natural
:自然数字)同构,这意味着Stream
和Reader Natural
之间存在双射,保留了它们monadic结构。
可以将Stream a
和Reader Natural a
(Natural -> a
)视为表示由整数编制索引的a
的无限集合。
fStream = Cons a0 (Cons a1 (Cons a2 ...))
fReader = \i -> case i of
0 -> a0
1 -> a1
2 -> a2
...
他们的Applicative
和Monad
个实例都以索引方式组成元素。显示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)
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)]
>
此处Applicative
和Monad
ic计算之间的差异与其他monad类似:应用操作具有静态结构,因为a <*> b
中的每个结果仅取决于a
和b
中相应元素的值,与它们如何适应更大的结构(即它们在序列中的位置)无关;相反,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 -> b
,f2 :: 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对应用实例没有任何补充。