Haskell 哈斯克尔单子咖喱
我现在需要一点大脑训练,我在网上找到了这篇文章 我在练习7的时候遇到了问题。随机函数绑定 为了使问题更易于实验,我将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)) ->
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
应具有两个功能。“初始”类型签名接受一个函数和一个元组,并生成一个元组。这并不适用于这个问题: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'