scala - 解释 Haskell 中的柯里化(Currying)实现

标签 scala haskell

我是一名学习 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)
ab是不受限制的值(除了作为 f 函数的参数),这意味着我们可以给它们类型,比如说 ab (非常有创意的命名,我知道)。
因此,当前签名看起来像(滥用符号):
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/

相关文章:

scala - 避免 shapeless 中两个类型类定义之间发生冲突的最佳方法是什么

scala - 在 scala 中创建元组时类型绑定(bind)错误

haskell - Haskell中函数的尾递归

haskell - QuickCheck 值相等

haskell - 编译器优化后如何分析 Haskell?

scala - Scala 中的 DAO 模式已经过时了吗?

java - 如何创建新文件夹?

java - 使用 hasNext() 和 next() 遍历异步生成的元素流

list - 如何在命令行 ghci 中对 Haskell 中的列表进行排序

haskell - cabal 测试无法链接自己的对象