【问题标题】:Monads, "container" exercise from sigfpe's blogMonads,来自 sigfpe 博客的“容器”练习
【发布时间】:2014-10-13 01:45:51
【问题描述】:

我正在学习“Learn You a Haskell”,现在正在阅读关于 monad 的章节。在其他与 Haskell 相关的帖子中,我看到很多人推荐 sigfpe 关于 monads 的博文;强烈建议 Haskell 学生通过他/她自己的各种练习来“发明”/“发现”单子的概念。

我在容器练习的最后一步遇到问题。我认为链接到他的网站会违反论坛规则,所以我会尽力在这里描述它。 (如果我在这方面错了,Lmk。)作为提醒,我对练习的描述可能并不完全连贯,因此最好使用简单的谷歌搜索:)

概览:

  • 每组练习都会引导读者完成构建不同类型的 monad 的步骤。大致步骤如下:

    • 我们考虑两个函数 - f'g' - 它们具有相同的类型声明
    • 我们构造了一个函数 (bind),它允许我们组合 fg 以使输出有意义(一旦您看到下面的具体练习,可能会更有意义)。直接组合不起作用,因为 fg 的返回类型与它们的参数类型不匹配。
    • 接下来,我们定义一个恒等函数 (unit),使得以下为真:
      (bind unit . f') == (bind f' . unit)
    • 在上一步的基础上,我们定义了一个lift函数,使得lift f = unit . f
    • 在最后一步中,我们被要求证明以下情况成立:
      bind (lift f) (lift g) == lift . bind f g

这是我正在做的一组练习:

  • 我们被要求考虑两个函数 sqrt'cbrt',它们计算复数的平方根和立方根(即 a + bi 形式的数字,其中 a 和 b 是实数,i 是平方根负一)。基础数学并不重要。重要的是可能的第 n 个根的数量是 n。换句话说,复数(即 a + bi 的形式)具有复数的两个平方根、复数的三个立方根等。
  • 鉴于复根的性质,sqrt'cbrt' 都接受 Complex 类型的参数并返回 [Complex] 类型是有意义的。我们被要求构建一个bind 函数,它可以让我们计算复数的六次方根,同时利用我们已经拥有 sqrt' 和 cbrt' 的事实。 (直接合成 obv 不起作用)

    bind :: (Complex -> [Complex) -> [Complex] -> [Complex] bind f = (concat . map f)

  • 接下来,我们构造unitlift

    unit :: Complex -> [Complex] unit x = [x]

    lift :: (Complex -> [Complex]) -> Complex -> [Complex] lift f = unit . f

  • 在最后一步(这是我遇到的问题),我们被要求显示以下内容:

    bind (lift f) (lift g) == lift . bind f g

首先,这个等式的左边不是有一个类型不匹配的lift 不能接受[Complex] 类型的参数吗?退后一步,我不确定我们为什么还要费心定义unitlift。 (我天真地认为bind 的定义解决了手头的问题,因此转到下一个问题。)如果有人能帮助我理解为什么我们定义这两个函数然后寻求证明最后一个函数,我将不胜感激平等。


作为参考,下面附上我的代码。请注意,bindunitlift 函数具有通用类型声明。

bind :: (a1 -> [a]) -> [a1] -> [a]
bind f' = (concat . map f')

unit :: t -> [t]
unit x = [x]

lift :: (a -> b) -> a -> [b]
lift f = unit . f

--Definitions of cbRootC and sqRootC
data Complex = Complex Float Float deriving (Show)

cbrt' = rootC 3
sqrt' = rootC 2

rootC :: Float -> Complex -> [Complex]
rootC n (Complex a b) = zipWith Complex r i
            where r = map (* (mod ** (1/n) )) $ map cos $ map arg [0..n-1]
                  i = map (* (mod ** (1/n) )) $ map sin $ map arg [0..n-1]
                  arg  = ( * (2*pi / n) )
                  mod = sqrt (a*a + b*b)

【问题讨论】:

    标签: haskell monads


    【解决方案1】:

    您的第一个误解是lift 的类型。在问题描述中,您将其列为以下第一项,但在您的代码中,您拥有第二项。

    lift :: (Complex -> [Complex]) -> Complex -> [Complex]
    lift :: (a       -> b        ) -> a       -> [b]
    

    注意第二个定义如何在第一个参数的返回类型周围不包含[]。第二个是正确的。 lift 将采用一个普通函数 Complex -> Complex 并从中产生一个“多值”计算 Complex -> [Complex],对于多个值仅返回从普通函数返回的单个值。

    您的第二个误解与*.sigfpe's article 中的使用方式有关。 * 用于将“多值”计算组合在一起; f * g = bind f . g.用于普通函数组合。

    不要求您展示这一点

    bind (lift f) (lift g) == lift . bind f g
    

    您是正确的,这不会进行类型检查。让我们试试运动吧。 bind 应用于两个参数返回 [a]lift 应用于单个参数返回 a1 -> [b]

    bind (lift f) (lift g) == lift . bind f g
    [a]                    ~  a1 -> [b]
    

    没有选择aa1b,这将使列表[]和函数->具有相同的类型。

    而是要求您显示以下内容。注意*. 的不同符号。我们将* 替换为bind.

          lift f  *  lift g  == lift (f . g)
    bind (lift f) . (lift g) == lift (f . g)
    

    剩下的练习留给你。

    unitlift 有用的原因是它们允许你重用你已经拥有的普通类型的东西。 lift 将普通函数转化为“多值”计算,unit 将普通值转化为“多值”计算的结果。

    【讨论】:

    • 感谢您的详细审查和回复。正如您所指出的,我确实没有看到*. 之间的区别。我不确定我是否理解新平等的 RHS,假设 fgcbrt'sqrt' 表示(根据初始容器练习)。特别是,我认为我们构建bind 函数的真正动机是因为我们无法直接使用. 运算符组合fg。换句话说,当fg 都具有Complex -> [Complex] 类型时,我们如何在等式的RHS 中有(f . g)
    • fg 不能有这些类型。让我们为fg 补充类型:f :: c -> dg :: a -> b。从f . g 我们可以找出两件事:b ~ c(f . g) :: a -> dlift (f . g) 的类型为 a -> [d]。如果我们查看 LHS,我们可以立即看到 (lift g) :: a -> [b](lift f) :: c -> [d]bind (lift f) 则有[c] -> [d] 类型,因为b ~ c 可以与a -> [b] 组合得到a -> [d],与RHS 的类型相同。
    • 简答:fg 的类型都是 Complex -> Complex
    • 最后相等的证明是有道理的。再次感谢!我的最后一个问题(我保证!):这个证明如何与我们进行这个特定练习的动机联系起来,即 sqrt' 和 cbrt' 如何适应最后一个相等? (我在问,因为这是一个重要的联系,因为这个想法是基于 sqrt'/cbrt' 问题得出一个 monad。)
    • @DipakC 确实有一种方法可以像这样编写函数,是的,如果你愿意,你也可以从中派生 Monad - Kleisli-Operators
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    • 1970-01-01
    • 2011-10-11
    • 1970-01-01
    • 2013-08-31
    相关资源
    最近更新 更多