join
与bind
一起定义,以将组合数据结构展平为单一结构。
从类型系统视图中,(+) 7 :: Num a => a -> a
可以被视为Functor
,(+) :: Num a => a -> a -> a
可以被视为Functor
Functor
,如何获得一些关于它的直觉而不仅仅依赖于类型系统?为什么join (+) 7 === 14
?
即使可以通过函数绑定过程手动步进来获得最终结果,但如果给出一些直觉,那将是很好的。
这来自NICTA练习。
-- | Binds a function on the reader ((->) t).
--
-- >>> ((*) =<< (+10)) 7
-- 119
instance Bind ((->) t) where
(=<<) ::
(a -> ((->) t b))
-> ((->) t a)
-> ((->) t b)
(f =<< a) t =
f (a t) t
-- | Flattens a combined structure to a single structure.
--
-- >>> join (+) 7
-- 14
join ::
Bind f =>
f (f a)
-> f a
join f =
id =<< f
*Course.State> :t join (+)
join (+) :: Num a => a -> a
*Course.State> :t join
join :: Bind f => f (f a) -> f a
*Course.State> :t (+)
(+) :: Num a => a -> a -> a
答案 0 :(得分:10)
如何获得一些关于它的直觉而不仅仅依赖于类型系统?
我宁愿说依赖类型系统是构建特定直觉的好方法。 join
的类型为:
join :: Monad m => m (m a) -> m a
专门针对(->) r
,它变为:
(r -> (r -> a)) -> (r -> a)
现在让我们尝试为函数定义join
:
-- join :: (r -> (r -> a)) -> (r -> a)
join f = -- etc.
我们知道结果必须是r -> a
函数:
join f = \x -> -- etc.
但是,我们对r
和a
类型的内容一无所知,因此我们对f :: r -> (r -> a)
和x :: r
一无所知。我们的无知意味着我们可以用它们做一件事:将x
作为参数传递给f
和f x
:
join f = \x -> f x x
因此,函数的join
两次传递相同的参数,因为这是唯一可能的实现。当然,这种实现只是一个正确的monadic join
,因为它遵循monad法则:
join . fmap join = join . join
join . fmap return = id
join . return = id
验证这可能是另一项不错的练习。
答案 1 :(得分:7)
将monad的传统类比作为计算的上下文,join
是一种组合上下文的方法。让我们从你的例子开始吧。 join (+) 7
。使用函数作为monad意味着读者monad。 (+ 1)
是一个读者monad,它接受环境并添加一个环境。因此,(+)
将是读者monad中的读者monad。外部读者monad获取环境n
并返回(n +)
形式的读者,这将采用新环境。 join
只是简单地组合了两个环境,以便您提供一次,并将给定的参数应用两次。 join (+) === \x -> (+) x x
。
现在,更一般地说,让我们看看其他一些例子。 Maybe
monad代表潜在的失败。值Nothing
是失败的计算,而Just x
是成功的。 Maybe
中的Maybe
是一个可能失败两次的计算。值Just (Just x)
显然是成功的,因此加入Just x
会产生Nothing
。 Just Nothing
或Nothing
表示某些时候失败,因此加入可能的失败应表明计算失败,即join
。
可以对列表monad进行类似的类比,其中concat
仅为<>
,即编写器monad,它使用monoidal运算符join
来组合所讨论的输出值或任何其他monad。
join
是monad的一个基本属性,它的操作使得它比functor或applicative functor强大得多。可以映射函数,应用程序可以是序列,monad可以组合。分类地,monad通常被定义为return
和return
。碰巧在Haskell中我们发现用(>>=)
,fmap
和{{1}}来定义它更方便,但这两个定义已被证明是同义词。
答案 2 :(得分:2)
关于join
的直觉是将 2个容器压缩成一个容器。 .e.g
join [[1]] => [1]
join (Just (Just 1)) => 1
join (a christmas tree decorated with small cristmas tree) => a cristmas tree
等等......
现在,你如何加入职能?实际上功能,可以看作是一个容器。
例如,如果你看一个哈希表。你给了一把钥匙,你得到一个价值(或不是)。这是一个函数key -> value
(或者如果你更喜欢key -> Maybe value
)。
那你怎么加入2个HashMap?
假设我有(以python风格)h={"a": {"a": 1, "b": 2}, "b" : {"a" : 10, "b" : 20 }}
我该如何加入它,或者如果你更喜欢它呢?
给定"a"
我应该获得哪个值? h["a"]
给了我{"a":1, "b":2}
。我唯一可以做的就是在这个新值中再次找到“a”,这给了我1
。
因此join h
等于{"a":1, "b":20}
。
功能相同。