scala - 解释 Haskell 中的柯里化实现
问题描述
我是一名学习 Haskell 的 Scala 程序员,通过“Haskell Programming from First Principles”来为我在 Scala 中使用的函数概念提供更多的连贯性。
我非常了解咖喱的概念。在 Scala 中很好地实现了它。
但是我不明白,作者提供的currying和uncurry的实现:
curry f a b = f (a, b)
:t curry
-- curry :: ((t1, t2) -> t) -> t1 -> t2 -> t
:t fst
-- fst :: (a, b) -> a
:t curry fst
-- curry fst :: t -> b -> t
uncurry f (a, b) = f a b
:t uncurry
-- uncurry :: (t1 -> t2 -> t) -> (t1, t2) -> t
(第 134 页)。
这里发生了什么?
在第一种情况下,是不是因为应用f (a, b)
我们推断 f 实际上是((t1, t2) -> t)
,但是究竟是什么把它变成了t1 -> t2 -> t
我不理解推理和整个咖喱/非咖喱过程。
编辑 1
从到目前为止给出的所有回复中,我现在明白了。我想我现在正试图让 Haskell 变得聪明。
以咖喱为例:
如果我在 Scala 中用 Haskell 写出如何解决这个问题的等价物:
myCurry :: ((a,b) -> c) -> a ->b -> c
myCurry f = \a -> \b -> f(a,b)
作为参考,Scala 等价物:
def curry[A,B,C](f: (A, B) => C): A => (B => C) = a => b => f(a, b)
显然,这不是在 Haskell 中解决该问题的惯用方法。从 Haskell 的角度来看,我对那个解决方案的问题很感兴趣,所以我可以开始更好地了解是什么让 Haskell 类型系统如此受人尊敬。
到目前为止,我看到的唯一区别是惯用的 Haskell 解决方案有更多的推理,并且依赖于 curry 的部分应用。Scala 解决方案需要更明确的手册。
不确定通过一些心理体操来理解这么简单的事情是否有用。但为什么不呢。
编辑 2
实际上找到了在 Scala 中做同样事情的方法,尽管一直减去泛型类型。
def fst[A, B](a: A, b:B): A = a
//fst: fst[A,B](val a: A,val b: B) => A
fst[Int, Int] _
//res0: (Int, Int) => Int
def sCurry[A,B,C](f: (A, B) => C) (a: A) (b: B) = f(a,b)
//sCurry: sCurry[A,B,C](val f: (A, B) => C)(val a: A)(val b: B) => C
sCurry[Int, Int, Int] _
//((Int, Int) => Int) => (Int => (Int => Int))
val s = sCurry(fst[Int, Int]) _
//s: Int => (Int => Int)
s (4) (2)
//res1: Int = 4
解决方案
我们可以一次启动一个变量:
curry f a b = f (a, b)
a
andb
是不受限制的值(除了作为函数的参数f
),这意味着我们可以给它们类型,比如说a
and b
(非常有创意的命名,我知道)。
因此,当前签名看起来像(滥用符号):
curry :: (type of f) -> a -> b -> (type of f (a, b))
由于在右侧f
调用,我们可以假设它是一个接受元组并返回一些值的函数,比方说。所以,是,所以是应用一个级别,或者只是。所以,函数的签名是:(a, b)
(a, b)
c
f
(a, b) -> c
f (a, b)
c
curry :: ((a, b) -> c) -> a -> b -> c
现在,我们可以尝试理解这个签名。我发现当用括号括起来作为这个等效表达式时更容易理解:
curry :: ((a, b) -> c) -> (a -> b -> c)
您传入一个函数,该函数接受一个元组并返回一个值,并curry
返回一个接受两个值并返回一个值的函数。本质上,您是在解包参数:从一个接收包含 2 个值的单个元组的函数到一个接收 2 个值的函数。
在 的上下文中看这个curry fst
,fst
是\(a, b) -> a
。因此,curry
将解包参数,使其\a b -> a
. 这基本上是 的定义const
,返回第一个参数并忽略第二个。
现在我们可以看看 uncurry。使用与之前相同的推理,我们可以说参数a
具有类型a
,并且参数b
具有类型b
,并且由于在thenf
上被调用,所以它必须具有某种类型。所以,类型签名看起来像:a
b
a -> b -> c
uncurry :: (a -> b -> c) -> (a, b) -> c
或者,像以前一样用括号括起来:
uncurry :: (a -> b -> c) -> ((a, b) -> c)
顾名思义,这与咖喱相反。它接受一个已经接受 2 个参数的函数,然后返回一个接受两个参数的元组的函数。在uncurry
用例中查看:
λ> f = uncurry (+)
λ> :t f
f :: Num c => (c, c) -> c
λ> f (1, 2)
3
λ> (+) 1 2
3
此外,curry (uncurry f)
应该是f
,对于任何f
接受两个或更多参数的人。
推荐阅读
- javascript - 如何测试复杂的树结构?
- python - 散景滑块不更新数据
- android - 检测与 PC 的 USB 连接的智能手机应用程序。本地人
- wordpress - 通过 Unity WebGL 应用程序(WP + WC)将商品添加到购物车
- java - 我怎样才能得到 java.lang.NullPointerException?
- python - 将方法添加到由另一个类创建的对象的类
- python - 如何在给定位置和日期的情况下查询历史 UTC 时区偏移
- xpath - 如何将以下xpath转换为css?
- typescript - 如何在 TypeScript 的方法上下文中重新定义变量类型?
- python - 如何迭代地附加到文本?