作为Monad的实例函数

9
我很难理解函数如何成为单子。
根据在 Control.Monad.Instances 中的声明,函数 (->) r 是一个单子。
instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w  

即使Miran Lipovača在这里所说的也让我感到困惑:
实现" >>= "有点神秘,但其实并不是那样。当我们使用" >>= "将monadic value传递给function时,结果总是一个monadic value。因此,在这种情况下,当我们将一个function提供给另一个function时,结果也是一个function。这就是为什么结果最初以lambda开头的原因。到目前为止,所有" >>= "的实现都以某种方式将结果与monadic value隔离开来,然后将函数f应用于该结果。这里也发生了同样的事情。要从function获取结果,我们必须将其应用于某些内容,这就是为什么我们在这里执行(h w)以从function获取结果,然后我们将f应用于该结果。f返回一个monadic value,在我们的情况下是一个function,因此我们也将其应用于w。
(>>=)的类型签名是这样的: (>>=) :: m a -> (a -> m b) -> m b
所以我认为h的类型为m a,f的类型为(a -> m b)。如果一个function是m a,它会返回一个a类型的值吗?还是返回其他东西,需要一个a类型的值?
如果将h的非monad value提供给f,则我们会得到: f (h w) 看起来不错。由于f是一个function并且已经获取了它的唯一参数,它已经是一个value了,不是吗?由于它是一个monadic function,所以该值也是一个monadic value。为什么它还需要另一个值w?将w提供给f something不会使其变成非monadic吗,即它不再是一个function了吗?我也无法理解为什么f something和h使用相同的参数w并返回不同的值类型(m a和m b)。
2个回答

12

首先,这是 (>>=) 这个类型:

(>>=) :: (Monad m) => m a -> (a -> m b) -> m b

现在,将m特化为((->) r)

(>>=) :: ((->) r) a -> (a -> ((->) r) b) -> ((->) r) b

所有的函数箭头都改为中缀表达式:

(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b)

简化一些多余的括号:

(>>=) :: (r -> a) -> (a -> r -> b) -> r -> b

在这一点上,我们应该更容易看到正在发生的事情:将第三个参数(类型为 r)提供给第一个参数以获得类型为 a 的结果,然后将该结果和第三个参数提供给第二个参数以获得类型为 b 的最终结果。

因此,((->) r) 作为一个 Monad 表示对该单子中每个值的额外函数参数,当单子值被组合时,单个“额外”参数会被复制并分别提供给每个输入值。基本上,这创建了单子值的“只读全局环境”。这种解释是显式地提供的 Reader 单子,它只是 ((->) r) 的包装器。


7
也许通过观察join函数的作用可以更容易地理解这个单子,因为一个单子可以使用fmapjoin代替>>=来等价定义。 join的一般形式具有类型Monad m => m (m b) -> m b,因此它将一个“两层”单子值压缩到一层。
对于函数单子,m ~ (a ->),因此join的类型为(a -> a -> b) -> (a -> b),所以它接受一个带有两个参数的函数,并返回一个只带有一个参数的函数。
join :: (a -> a -> b) -> (a -> b)
join f = \x -> f x x

正如您所看到的,它只是复制参数。

同样地,对于函数的fmap只是函数组合,而return则是const

我认为这种方式比试图理解>>=要容易得多。


虽然这对于概念上理解实例很有用,但您仍然必须了解 >>= 的行为才能理解 do 符号中每一行对该单子所代表的含义。 - Dan Burton
在这种情况下,我认为Applicative实例最容易解释和理解,因为“环境”始终是第一个参数,而Monad实例等效于参数顺序。 - C. A. McCann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接