了解Scala中的咖喱 [英] Understanding Currying in Scala

查看:79
本文介绍了了解Scala中的咖喱的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在理解currying概念或至少是SCALA currying符号时遇到了问题.

维基百科表示,柯里化是一种翻译函数求值的技术,将多个参数用于评估一系列函数,每个函数都有一个参数.

按照此说明,scala的下两行是否相同?

def addCurr(a: String)(b: String): String = {a + " " + b}
def add(a:String): String => String = {b => a + " " + b}

我用相同的字符串a和b运行了两行,得到相同的结果,但是我不知道它们在后台是否不同

我对addCurr(以及自己计算自身)的思考方式是,它是一个接收字符串参数a的函数,并返回另一个也接收字符串参数b并返回字符串a +" + b的函数? /p>

所以,如果我说对了,addCurr只是add函数的语法糖,而两者都是库里函数?

根据前面的示例,接下来的功能也等同于scala吗?

def add(a: String)(b: String)(c: String):String = { a + " " + b + " " + c}

def add1(a: String)(b: String): String => String = {c => a + " " + b + " " + c}

def add2(a:String): (String => (String => String)) = {b => (c => a + " " + b + " " + c)}

解决方案

它们的语义有些不同,但是它们的用例在实际上和在代码中的外观上都是相同的.

咖喱

从数学意义上讲,在Scala中使用函数非常简单:

val function = (x: Int, y: Int, z: Int) => 0
// function: (Int, Int, Int) => Int = <function3>
function.curried
// res0: Int => (Int => (Int => Int)) = <function1>

功能和方法

您似乎对以下事实感到困惑:在Scala中,(=>)功能与(def)方法不同.方法不是一流的对象,而函数是(即,它具有curriedtupled方法,而Function1具有更多的优点).

但是,可以通过称为eta扩展的操作将方法提升为功能.有关某些详细信息,请参见此SO答案.您可以通过编写methodName _手动触发它,或者如果您将方法提供给期望的函数类型,则将隐式完成它.

def sumAndAdd4(i: Int, j: Int) = i + j + 4
// sumAndAdd4.curried // <- won't compile

val asFunction = sumAndAdd4 _ // trigger eta expansion
// asFunction: (Int, Int) => Int = <function2>
val asFunction2: (Int, Int) => Int = sumAndAdd4
// asFunction2: (Int, Int) => Int = <function2>
val asFunction3 = sumAndAdd4: (Int, Int) => Int
// asFunction3: (Int, Int) => Int = <function2>


asFunction.curried
// res0: Int => (Int => Int) = <function1>
asFunction2.curried
// res1: Int => (Int => Int) = <function1>
asFunction3.curried
// res2: Int => (Int => Int) = <function1>
{sumAndAdd4 _}.tupled // you can do it inline too
// res3: Int => (Int => Int) = <function1>

多个参数列表的eta扩展

就像您可能期望的那样,eta扩展将每个参数列表提升为自己的函数

def singleArgumentList(x: Int, y: Int) = x + y
def twoArgumentLists(x: Int)(y: Int) = x + y

singleArgumentList _ // (Int, Int) => Int
twoArgumentLists _ // Int => (Int => Int) - curried!

val testSubject = List(1, 2, 3)

testSubject.reduce(singleArgumentList) // Int (6)
testSubject.map(twoArgumentLists) // List[Int => Int]

// testSubject.map(singleArgumentList) // does not compile, map needs Int => A
// testSubject.reduce(twoArgumentLists) // does not compile, reduce needs (Int, Int) => Int

但是从数学意义上讲并不是那么容易:

def hmm(i: Int, j: Int)(s: String, t: String) = s"$i, $j; $s - $t"

{hmm _} // (Int, Int) => (String, String) => String

在这里,我们得到一个包含两个参数的函数,返回另一个包含两个参数的函数.

仅指定其某些参数并不是那么简单

val function = hmm(5, 6) _ // <- still need that underscore!

与函数一样,您可以轻松获得函数:

val alreadyFunction = (i: Int, j: Int) => (k: Int) => i + j + k

val f = alreadyFunction(4, 5) // Int => Int


按照您喜欢的方式进行操作-Scala在很多事情上都没有意见.我个人更喜欢多个参数列表,因为我经常需要部分应用一个函数,然后将其传递到将给出其余参数的某个地方,因此,我不需要显式地进行eta扩展,并且我可以在方法定义站点上享受Terser语法.

I'm getting problems to understand the currying concept, or at least the SCALA currying notation.

wikipedia says that currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument.

Following this explanation, are the two next lines the same for scala?

def addCurr(a: String)(b: String): String = {a + " " + b}
def add(a:String): String => String = {b => a + " " + b}

I've run both lines with the same strings a and b getting the same result, but I don't know if they are different under the hood

My way of thinking about addCurr (and currying itself) is that it is a function that receives a string parameter a, and returns another function that also receives a string parameter b and returns the string a + " " + b?

So if I'm getting right, addCurr is only syntactic sugar of the function add and both are curryed functions?

According to the previous example, the next functions are also equivalent for scala?

def add(a: String)(b: String)(c: String):String = { a + " " + b + " " + c}

def add1(a: String)(b: String): String => String = {c => a + " " + b + " " + c}

def add2(a:String): (String => (String => String)) = {b => (c => a + " " + b + " " + c)}

解决方案

They have a bit different semantics, but their use-cases are mostly the same, both practically and how it looks in the code.

Currying

Currying a function in Scala in that mathematical sense is a very straightforward:

val function = (x: Int, y: Int, z: Int) => 0
// function: (Int, Int, Int) => Int = <function3>
function.curried
// res0: Int => (Int => (Int => Int)) = <function1>

Functions & methods

You seem to be confused by the fact that in Scala, (=>) functions are not the same as (def) methods. Method isn't a first-class object, while function is (i.e. it has curried and tupled methods, and Function1 has even more goodness).

Methods, however, can be lifted to functions by an operation known as eta expansion. See this SO answer for some details. You can trigger it manually by writing methodName _, or it will be done implicitly if you give a method to where a function type is expected.

def sumAndAdd4(i: Int, j: Int) = i + j + 4
// sumAndAdd4.curried // <- won't compile

val asFunction = sumAndAdd4 _ // trigger eta expansion
// asFunction: (Int, Int) => Int = <function2>
val asFunction2: (Int, Int) => Int = sumAndAdd4
// asFunction2: (Int, Int) => Int = <function2>
val asFunction3 = sumAndAdd4: (Int, Int) => Int
// asFunction3: (Int, Int) => Int = <function2>


asFunction.curried
// res0: Int => (Int => Int) = <function1>
asFunction2.curried
// res1: Int => (Int => Int) = <function1>
asFunction3.curried
// res2: Int => (Int => Int) = <function1>
{sumAndAdd4 _}.tupled // you can do it inline too
// res3: Int => (Int => Int) = <function1>

Eta expansion of multiple parameter list

Like you might expect, eta expansion lifts every parameter list to its own function

def singleArgumentList(x: Int, y: Int) = x + y
def twoArgumentLists(x: Int)(y: Int) = x + y

singleArgumentList _ // (Int, Int) => Int
twoArgumentLists _ // Int => (Int => Int) - curried!

val testSubject = List(1, 2, 3)

testSubject.reduce(singleArgumentList) // Int (6)
testSubject.map(twoArgumentLists) // List[Int => Int]

// testSubject.map(singleArgumentList) // does not compile, map needs Int => A
// testSubject.reduce(twoArgumentLists) // does not compile, reduce needs (Int, Int) => Int

But it's not that currying in mathematical sense:

def hmm(i: Int, j: Int)(s: String, t: String) = s"$i, $j; $s - $t"

{hmm _} // (Int, Int) => (String, String) => String

Here, we get a function of two arguments, returning another function of two arguments.

And it's not that straightforward to specify only some of its argume

val function = hmm(5, 6) _ // <- still need that underscore!

Where as with functions, you get back a function without any fuss:

val alreadyFunction = (i: Int, j: Int) => (k: Int) => i + j + k

val f = alreadyFunction(4, 5) // Int => Int


Do which way you like it - Scala is fairly un-opinionated about many things. I prefer multiple parameter lists, personally, because more often than not I'll need to partially apply a function and then pass it somewhere, where the remaining parameters will be given, so I don't need to explicitly do eta-expansion, and I get to enjoy a terser syntax at method definition site.

这篇关于了解Scala中的咖喱的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆