Haskell 哈斯克尔单子咖喱

Haskell 哈斯克尔单子咖喱,haskell,monads,Haskell,Monads,我现在需要一点大脑训练,我在网上找到了这篇文章 我在练习7的时候遇到了问题。随机函数绑定 为了使问题更易于实验,我将StdGen类型替换为未指定的类型。所以不是 bind :: (a -> StdGen -> (b,StdGen)) -> (StdGen -> (a,StdGen)) -> (StdGen -> (b,StdGen)) 我曾经 bind :: (a -> c -> (b,c)) -> (c -> (a,c)) ->

我现在需要一点大脑训练,我在网上找到了这篇文章

我在练习7的时候遇到了问题。随机函数绑定

为了使问题更易于实验,我将
StdGen
类型替换为未指定的类型。所以不是

bind :: (a -> StdGen -> (b,StdGen)) -> (StdGen -> (a,StdGen)) -> (StdGen -> (b,StdGen))
我曾经

bind :: (a -> c -> (b,c)) -> (c -> (a,c)) -> (c -> (b,c))
对于实际的功能推动(直接从练习开始)

以及2个随机函数进行试验:

rndf1 :: (Num a, Num b) => a -> b -> (a,b)
rndf1 a s = (a+1,s+1)

rndf2 :: (Num a, Num b) => a -> b -> (a,b)
rndf2 a s = (a+8,s+2)
所以在Haskell编译器(ghci)中,我得到

:t bind rndf2
bind rndf2 :: (Num a, Num c) => (c -> (a, c)) -> c -> (a, c)
这将匹配以
rndf2
作为第一个参数的绑定

但我不明白的是

:t bind rndf2 . rndf1
突然

bind rndf2 . rndf1 :: (Num a, Num c) => a -> c -> (a, c)
这是我们试图制作的正确的构图类型,因为

bind rndf2 . rndf1
是一个函数,它:

  • 采用与
    rndf1
  • 并从
    rndf1
    获取返回值,并将其作为
    rndf2
    的输入,以返回与
    rndf2
    相同的类型
  • rndf1
    可以接受两个参数
    a->c
    ,并且
    rndf2
    返回
    (a,c)
    ,因此它匹配这些函数的组合应具有以下类型:

    bind rndf2 . rndf1 :: (Num a, Num c) => a -> c -> (a, c)
    
    这与我最初为bind设计的naive类型不匹配

    bind f :: (a -> b -> (c, d)) -> (c, d) -> (e, f)
    
    这里,
    bind
    神秘地接受一个接受两个参数的函数,并生成一个接受元组的函数,以便
    rndf1
    的输出可以馈送到
    rndf2

  • 为什么bind函数需要按原样编码
  • 为什么bind函数没有naive类型

  • bind
    应具有两个功能。“初始”类型签名接受一个函数和一个元组,并生成一个元组。这并不适用于这个问题:
    rndf1
    不是元组,它是一个函数。一旦参数应用于它,只有到那时,
    rndf1 a s
    才会成为元组。因此,
    bind
    需要是一个包含两个函数的函数

    将元组导入一个具有两个参数的函数的神秘力量存在于
    bind
    的定义中

    bind :: (a -> c -> (b,c)) -> (c -> (a,c)) -> (c -> (b,c))
    bind f x seed = let (x',seed') = x seed 
                     in f x' seed'
    
    在此定义中,
    f
    x
    都是函数(如果您将
    x
    重命名为
    g
    ,这可能会更清楚)

    让(x',seed')=x seed
    x种子
    生成一个元组。在等号的左侧,该元组的各个元素被赋予新名称,然后在下一行,这些名称被传递给函数
    f

    因此,它可能有助于分解表达式
    bind rndf2。rndf1

    请记住,所有函数实际上只有一个参数。
    rndf1
    的类型可以最准确地写成
    (Num a,Num b)=>a->(b->(a,b))
    。这对理解接下来的内容非常有帮助

    另一件有帮助的事情是考虑如何将此表达式与参数一起使用。例如:
    (bind rndf2.rndf1)a s

    因为所有函数都有一个参数,所以它们会逐个发送到括号内。首先:
    ((bind rndf2.rndf1)a)s

    现在,您可以在不使用
    操作符的情况下重写表达式:
    (bind rndf2(rndf1 a))s
    a
    被传递给
    rndf1
    ,因为
    就是这样工作的:点右侧的函数接受输入,然后将其输出传递给左侧的函数

    您将看到
    rndf1a
    的类型签名与
    bind
    的第二个参数匹配。因此,将参数
    rndf2
    (rndf1 a)
    应用于
    bind

    bind rndf2 (rndf1 a) seed = let (x', seed') = (rndf1 a) seed
                                 in rndf2 x' seed'
    
    现在,圆括号内有一个函数,它只接受一个
    seed
    参数。因此,您可以使用
    s
    并将其应用于函数:

    bind rndf2 (rndf1 a) s = let (x', seed') = (rndf1 a) s
                              in rndf2 x' seed'
    

    你所说的“我们最终将走向的正确类型”是什么意思?如果你指的是
    单元的类型
    ,那么它就错了,因为
    单元::a->c->(a,c)
    (注意缺少的
    Num
    上下文)。@Vitus-我补充了一些额外的说明,说明了我正在谈论的类型,并澄清了我的实际问题。本质上,我的困惑与Chris Bogart在前几天的评论中对答案的思考是一样的。我想我差不多明白了——当你说记住所有函数实际上只有一个参数时。您正在为
    ()
    操作符获取两个参数吗?明白了!!-非常感谢:-)
    bind rndf2 (rndf1 a) s = let (x', seed') = (rndf1 a) s
                              in rndf2 x' seed'