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


82

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

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).

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


부분적으로 적용하면 메모리에서 커리 버전과 비 커리 버전을 나타내는 비용 이 다를 있으므로 런타임 성능도 영향을받을 수 있습니다. (즉, 옵티마이 저가 두 경우 모두에서 최적의 표현을 선택할만큼 똑똑하지 않다면.)하지만 정확한 차이점이 무엇인지 말할만큼 스칼라에 익숙하지 않습니다.


:이 설명은 매우 매우 유용합니다, 그는 모두 하나 개의 게시물에, 일부 기능을 부분적으로 적용 기능과 태닝에 대해 설명 발견 stackoverflow.com/a/8650639/1287554
스티 그 로브

훌륭한 링크, @PlastyGrove. 감사합니다!
Eric

그리고 귀하의 링크에 대해 @Utaal에게 감사드립니다. Martin Odersky 자신의 답변은 매우 중요합니다. 나는 이러한 개념이 이제 클릭되기 시작했다고 생각합니다.
에릭

답변:


88

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

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

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

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

scala> def modNCurried(n: Int)(x: Int): Boolean = ((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): 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

참고 : 예제 println(filter(nums, modN(2))가 밑줄없이 작동 하는 이유 modN(2)는 Scala 컴파일러가 단순히 프로그래머의 편의를 위해 밑줄을 가정하기 때문인 것 같습니다.


추가 : @asflierl이 올바르게 지적했듯이 Scala는 "일반"함수를 부분적으로 적용 할 때 유형을 추론 할 수없는 것 같습니다.

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

이 정보는 다중 매개 변수 목록 표기법을 사용하여 작성된 함수에 사용할 수 있습니다.

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

이 답변 은 이것이 얼마나 유용 할 수 있는지 보여줍니다.


한 가지 중요한 차이점은 유형 추론이 다르게 작동한다는 것입니다. gist.github.com/4529020

감사합니다, 나는 당신의 코멘트 : 대한 메모를 추가
fresskoma을

19

커링은 튜플과 관련 이 있습니다. 튜플 인수를받는 함수를 n 개의 개별 인수를받는 함수로 바꾸고 그 반대의 경우도 마찬가지 입니다. 이것이 카레를 완전히 지원하지 않는 언어에서도 카레와 부분 적용을 구별하는 열쇠임을 기억하십시오.

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.

부분 적용은 일부 인수에 함수적용하여 나머지 인수에 대한 새 함수를 생성하는 기능입니다 .

커링이 튜플과 관련된 변형이라고 생각하면 기억하기 쉽습니다.

기본적으로 카레 처리되는 언어 (예 : Haskell)에서는 차이점이 분명합니다. 실제로 튜플에서 인수를 전달하려면 무언가를해야합니다. 그러나 Scala를 포함한 대부분의 다른 언어는 기본적으로 uncurried입니다. 모든 arg는 튜플로 전달되므로 curry / uncurry는 훨씬 덜 유용하고 덜 명확합니다. 그리고 사람들은 카레 기능을 쉽게 표현할 수 없기 때문에 부분 적용과 카레가 같은 것이라고 생각하게됩니다!


난 전적으로 동의합니다. 스칼라 용어에서 단어의 원래 의미에서 "currying"은 하나의 매개 변수 목록이있는 함수를 여러 매개 변수 목록이있는 함수로 "변환하는 과정"입니다. Scala에서이 변환은 ".curried"를 사용하여 실행할 수 있습니다. 불행히도 스칼라는 원래 ".curried"가 아닌 ".curry"로 불리기 때문에 단어의 의미를 약간 오버로드 한 것 같습니다.
Fatih Coşkun

2

다 변수 기능 :

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

카레 (또는 카레 기능) :

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

따라서 카레에 필적하는 부분적으로 적용된 기능이 아닙니다. 다 변수 함수입니다. 부분적으로 적용된 함수와 비교할 수있는 것은 부분적으로 적용된 함수와 동일한 매개 변수 목록이있는 함수 인 curried 함수의 호출 결과입니다.


0

마지막 요점을 명확히하기 위해

추가 : @asflierl이 올바르게 지적했듯이 Scala는 "일반"함수를 부분적으로 적용 할 때 유형을 추론 할 수없는 것 같습니다.

Scala는 모든 매개 변수가 와일드 카드 인 경우 유형을 추론 할 수 있지만 일부는 지정되고 일부는 지정되지 않은 경우에는 그렇지 않습니다.

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,_)
              ^

0

지금까지 찾을 수있는 가장 좋은 설명 : https://dzone.com/articles/difference-between-currying-amp-partially-applied

Currying : 여러 인수가있는 함수를 단일 인수 함수 체인으로 분해합니다. Scala는 함수를 다른 함수에 인수로 전달할 수 있습니다.

함수의 부분적 적용 : 선언에있는 것보다 적은 인수를 함수에 전달합니다. Scala는 함수에 더 적은 인수를 제공 할 때 예외를 발생시키지 않고 단순히 적용하고 전달해야하는 나머지 인수가있는 새 함수를 반환합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.