我是一名学习 Haskell 的 Scala 程序员,通过“Haskell Programming from First Principles”来为我在 Scala 中使用的函数概念提供更多连贯性。
我非常了解 curry 的概念。在 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
我不理解推理和整个 curry /非 curry 过程。编辑 1
从到目前为止给出的所有回复中,我现在明白了。我想我现在正试图让 Haskell 变得聪明。
以 curry 为例:
如果我在 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
和 b
是不受限制的值(除了作为 f
函数的参数),这意味着我们可以给它们类型,比如说 a
和 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
,并且由于 f
被调用 a
然后 b
, 它必须有某种类型 a -> b -> c
.所以,类型签名看起来像:uncurry :: (a -> b -> c) -> (a, b) -> c
或者,像以前一样用括号括起来:uncurry :: (a -> b -> c) -> ((a, b) -> c)
顾名思义,这与 curry 相反。它接受一个函数 已经接受 2 个参数,然后返回一个函数,该函数接受一个带有两个参数的元组。看uncurry
在一个用例中:λ> f = uncurry (+)
λ> :t f
f :: Num c => (c, c) -> c
λ> f (1, 2)
3
λ> (+) 1 2
3
另外,curry (uncurry f)
应该是 f
, 对于任何 f
接受两个或更多参数。
关于scala - 解释 Haskell 中的柯里化(Currying)实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65549698/