【问题标题】:Is there any intuition to understand join two functions in Monad?有什么直觉可以理解在 Monad 中加入两个函数吗?
【发布时间】:2015-11-26 05:19:36
【问题描述】:

joinbind 一起定义,以将组合数据结构展平为单个结构。

从类型系统来看,(+) 7 :: Num a => a -> a 可以被认为是Functor(+) :: Num a => a -> a -> a 可以被认为是FunctorFunctor,如何获得一些关于它的直觉而不是仅仅依赖类型系统?为什么是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

【问题讨论】:

  • (+) 7 :: Num a =&gt; a -&gt; a 可以被认为是Functor...”请不要说这样的话。 (7+) 不是函子,就像说碳原子是钻石一样。函子是(Int -&gt;),即接受类型并从数字到该类型生成函数的类型构造函数。
  • 对于函数,join f = \a -&gt; f a a。因此join (+) = \a -&gt; a + a.
  • @leftaroundabout (+) 7 :: Num a =&gt; a -&gt; a 不是 Num 类别的函子吗?
  • @Kamel: Num 作为一个类别?呸,让我们看看。它的对象将是数字类型n, m, ...。态射将是函数n -&gt; m 等。因此,(7+) 采用数字类型并将其映射到...自身,这很公平。但是它以什么方式映射态射呢?不,恐怕这没有任何意义。
  • @Kamel:嗯,你是对的,从数学上讲,这似乎实际上是一个函子。 (有点退化,注意...)无论如何,它不是 function functor。这不会将类型映射到它们自己。关于“Haskell 中的所有函子将类别映射到自身”——你的意思是,它们是 Hask 上的 endofunctors;但这并不意味着他们将对象/类型映射到自己。事实上这是不可能的,因为函子是类型构造函数,而它们的固定点将是“无限类型”。

标签: haskell functional-programming monads category-theory


【解决方案1】:

与将 monad 作为计算上下文的传统类比一样,join 是一种组合上下文的方法。让我们从你的例子开始。 join (+) 7。将函数用作 monad 意味着 reader monad。 (+ 1) 是一个读取器 monad,它获取环境并向其添加一个。因此,(+) 将是阅读器单子中的阅读器单子。外部阅读器 monad 采用环境 n 并返回 (n +) 形式的阅读器,它将采用新环境。 join 只是简单地结合了这两个环境,因此您提供一次它并应用给定参数两次。 join (+) === \x -&gt; (+) x x.

现在,更笼统地说,让我们看一些其他示例。 Maybe monad 代表潜在的失败。 Nothing 的值是计算失败,而 Just x 是成功的。 Maybe 中的 Maybe 是一个可能会失败两次的计算。 Just (Just x) 的值显然是成功的,因此加入会产生 Just xNothingJust Nothing 表示在某些时候失败,因此加入可能的失败应该表示计算失败,即Nothing

可以对列表 monad 进行类似的类比,其中 join 只是 concat,编写器 monad,它使用 monoidal 运算符 &lt;&gt; 组合有问题的输出值,或任何其他 monad。

join 是 monads 的基本属性,并且是使它比函子或应用函子强得多的操作。函子可以被映射,应用程序可以是序列,单子可以组合。明确地说,monad 通常被定义为joinreturn。碰巧在 Haskell 中,我们发现用return(&gt;&gt;=)fmap 来定义它更方便,但这两个定义已被证明是同义词。

【讨论】:

  • + 1 是一个读者单子吗?你的意思是在概念上还是在类型级别?
  • (+ 1) 是一个接受参数并添加一个参数的函数。从概念上讲,它可以被视为一个依赖于它还不知道的只读值的值。 (+ 1) 可以被视为一个整数,在我们给它“环境”之前我们无法检查它。
  • 所以这只是一个概念上的类比,而不是类型级别的事实。
  • @ErikAllik 不仅如此。尽管(-&gt;) rReader r 的字面意思不同,但它们是同构的。 (-&gt;) r 也是 an instance of MonadReader。我认为这是一个“类型级别的事实”。
  • 这个答案可能更精确的一点是在“[使用]一个函数作为一个单子”和“(+)将是一个阅读器单子中的一个阅读器单子”这样的段落中。 (+) 等值不是单子;它们是一元值。像 (-&gt;) r 这样的类型构造函数是 monad。另请参阅 leftaroundabout 对上述问题的 cmets。
【解决方案2】:

如何获得一些关于它的直觉,而不是仅仅依赖类型系统?

我宁愿说依赖类型系统是建立特定直觉的好方法。 join 的类型为:

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

专门针对(-&gt;) r,变成:

(r -> (r -> a)) -> (r -> a)

现在让我们尝试为函数定义join

-- join :: (r -> (r -> a)) -> (r -> a)
join f = -- etc.

我们知道结果必须是r -&gt; a 函数:

join f = \x -> -- etc.

然而,我们对ra 类型是什么一无所知,因此我们对f :: r -&gt; (r -&gt; a)x :: r 一无所知。我们的无知意味着我们实际上只能对它们做一件事:将x 作为参数传递给ff x

join f = \x -> f x x

因此,函数的join 将相同的参数传递两次,因为这是唯一可能的实现。当然,该实现只是一个适当的单子join,因为它遵循单子定律:

join . fmap join = join . join
join . fmap return = id
join . return = id

验证这可能是另一个不错的练习。

【讨论】:

    【解决方案3】:

    关于join 的直觉是挤压 2 个容器合二为一。 .例如

    join [[1]] => [1]
    join (Just (Just 1)) => 1
    join (a christmas tree decorated with small cristmas tree) => a cristmas tree
    

    等等……

    现在,如何加入函数?其实函数,可以看成一个容器。 例如,如果您查看哈希表。你给一个键,你得到一个值(或没有)。这是一个函数key -&gt; value(或者如果你更喜欢key -&gt; 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}

    函数也一样。

    【讨论】:

      猜你喜欢
      • 2015-03-01
      • 1970-01-01
      • 2019-08-26
      • 1970-01-01
      • 1970-01-01
      • 2020-12-07
      • 2021-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多