Development Tip

스칼라 커링과 부분적으로 적용된 함수

yourdevel 2020. 10. 5. 21:02
반응형

스칼라 커링과 부분적으로 적용된 함수


여기에 카레와 부분적으로 적용된 기능이 무엇인지 에 대한 몇 가지 질문이 있다는 것을 알고 있지만 어떻게 다른지 묻고 있습니다. 간단한 예로서 짝수를 찾기위한 카레 함수가 있습니다.

def filter(xs: List[Int], p: Int => Boolean): List[Int] =
   if (xs.isEmpty) xs
   else if (p(xs.head)) xs.head :: filter(xs.tail, p)
   else filter(xs.tail, p)

def modN(n: Int)(x: Int) = ((x % n) == 0)

따라서 이것을 사용하기 위해 다음을 작성할 수 있습니다.

val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))

반환 : List(2,4,6,8). 그러나 나는 이런 식으로 똑같은 일을 할 수 있음을 발견했습니다.

def modN(n: Int, x: Int) = ((x % n) == 0)

val p = modN(2, _: Int)
println(filter(nums, p))

또한 반환 : List(2,4,6,8).

그래서 내 질문은, 둘의 주요 차이점은 무엇이며 언제 다른 하나를 사용합니까? 하나가 다른 것보다 사용되는 이유를 보여주기에는 너무 단순한 예제입니까?


의미 론적 차이는 Plasty Grove에 연결된 답변 에서 상당히 잘 설명되었습니다 .

그러나 기능면에서 큰 차이는 없습니다. 이를 확인하기 위해 몇 가지 예를 살펴 보겠습니다. 첫째, 정상적인 기능 :

scala> def modN(n: Int, x: Int) = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>

그래서 우리 는 이미 첫 번째 정수를 주었기 때문에 <function1>를 취하는 부분적으로 적용됩니다 Int. 여태까지는 그런대로 잘됐다. 이제 카레로 :

scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

이 표기법을 사용하면 다음 작업이 순진하게 예상됩니다.

scala> modNCurried(5)
<console>:9: error: missing arguments for method modN;
follow this method with `_' if you want to treat it as a partially applied function
          modNCurried(5)

따라서 다중 매개 변수 목록 표기법은 실제로 즉시 커리 함수를 생성하는 것 같지는 않지만 (불필요한 오버 헤드를 방지하기 위해) 명시 적으로 커리를 원한다고 명시하기를 기다립니다 (표기법에는 다른 장점 도 있습니다).

scala> modNCurried(5) _
res24: Int => Boolean = <function1>

이전에 얻은 것과 똑같기 때문에 표기법을 제외하고는 여기에 차이가 없습니다. 다른 예시:

scala> modN _
res35: (Int, Int) => Boolean = <function2>

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>

"일반"함수를 부분적으로 적용하면 모든 매개 변수를받는 함수가 생성되는 반면, 여러 매개 변수 목록이있는 함수를 부분적으로 적용하면 매개 변수 목록 당 하나씩 함수 체인이 생성 되고 모두 새 함수를 반환하는 방법을 보여줍니다.

scala> def foo(a:Int, b:Int)(x:Int)(y:Int) = a * b + x - y
scala> foo _
res42: (Int, Int) => Int => (Int => Int) = <function2>

scala> res42(5)
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2.
Unspecified value parameter v2.

보시다시피 첫 번째 매개 변수 목록 foo에는 두 개의 매개 변수가 있으므로 커리 체인의 첫 번째 함수에는 두 개의 매개 변수가 있습니다.


요약하자면 부분적으로 적용된 함수는 기능 측면에서 실제로 다른 형태의 커리 함수가 아닙니다. 모든 함수를 카레로 변환 할 수 있다는 점을 고려하면 쉽게 확인할 수 있습니다.

scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>

Post Scriptum

Note: The reason that your example println(filter(nums, modN(2)) works without the underscore after modN(2) seems to be that the Scala compiler simply assumes that underscore as a convenience for the programmer.


Addition: As @asflierl has correctly pointed out, Scala doesn't seem to be able to infer the type when partially applying "normal" functions:

scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))

Whereas that information is available for functions written using multiple parameter list notation:

scala> modNCurried(5) _
res3: Int => Boolean = <function1>

This answers shows how this can be very useful.


Currying is to do with tuples: turning a function that takes a tuple argument into one that takes n separate arguments, and vice versa. Remembering this is the key to distinguishing curry vs partial application, even in languages that don't cleanly support currying.

curry :: ((a, b) -> c) -> a -> b -> c 
   -- curry converts a function that takes all args in a tuple
   -- into one that takes separate arguments

uncurry :: (a -> b -> c) -> (a, b) -> c
   -- uncurry converts a function of separate args into a function on pairs.

Partial application is the ability to apply a function to some arguments, yielding a new function for the remaining arguments.

It is easy to remember if you just think currying is the transformation to do with tuples.

In languages that are curried by default (such as Haskell) the difference is clear -- you have to actually do something to pass arguments in a tuple. But most other languages, including Scala, are uncurried by default -- all args are passed as tuples, so curry/uncurry is far less useful, and less obvious. And people even end up thinking that partial application and currying are the same thing -- just because they can't represent curried functions easily!


Multivariable function:

def modN(n: Int, x: Int) = ((x % n) == 0)

Currying (or the curried function):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

So it's not partially applied function that's comparable to currying. It's the multivariable function. What's comparable to partially applied function is the invocation result of a curried function, which is a function with the same parameter list that the partially applied function has.


Just to clarify on the last point

Addition: As @asflierl has correctly pointed out, Scala doesn't seem to be able to infer the type when partially applying "normal" functions:

Scala can infer types if all parameters are wildcards but not when some of them are specified and some of them not.

scala> modN(_,_)
res38: (Int, Int) => Boolean = <function2>

scala> modN(1,_)
<console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1))
       modN(1,_)
              ^

참고URL : https://stackoverflow.com/questions/14309501/scala-currying-vs-partially-applied-functions

반응형