카레의 장점은 무엇입니까?


154

나는 단지 카레에 대해 배웠고, 나는 개념을 이해한다고 생각하지만 그것을 사용하는 데 큰 이점을 보지 못했습니다.

간단한 예제로 두 가지 값을 추가하는 함수를 사용합니다 (ML로 작성). 카레없는 버전은

fun add(x, y) = x + y

로 불릴 것입니다

add(3, 5)

카레 버전은

fun add x y = x + y 
(* short for val add = fn x => fn y=> x + y *)

로 불릴 것입니다

add 3 5

그것은 함수를 정의하고 호출하는 것에서 하나의 괄호를 제거하는 구문 설탕 인 것 같습니다. 나는 카레가 기능적 언어의 중요한 기능 중 하나로 열거 된 것을 보았고, 현재로서는 약간의 압박을 받고 있습니다. 튜플을 취하는 함수 대신 각각의 단일 매개 변수를 소비하는 함수 체인을 만드는 개념은 간단한 구문 변경에 사용하기가 다소 복잡해 보입니다.

약간 더 간단한 구문이 카레에 대한 유일한 동기입니까? 아니면 간단한 예에서 분명하지 않은 다른 이점이 누락 되었습니까? 카레는 단지 구문 설탕입니까?


54
커리만으로는 본질적으로 쓸모가 없지만 기본적으로 모든 기능을 커리하면 많은 다른 기능을 사용하는 것이 훨씬 더 편리합니다. 실제로 기능 언어를 사용하기 전까지는 이것을 이해하기가 어렵습니다.
CA McCann 2013

4
JoelEtherton의 답변에 대한 의견에서 delnan이 전달할 때 언급되었지만 명시 적으로 언급 할 것이라고 생각한 것은 (적어도 Haskell에서는) 함수뿐만 아니라 유형 생성자로 부분적으로 적용 할 수 있다는 것입니다. 능숙한; 이것은 생각할 수도 있습니다.
paul

모두 하스켈의 예를 들었습니다. 카레가 Haskell에서만 유용하다는 것을 궁금해 할 것입니다.
Manoj R

@ManojR 모두 Haskell에서 예제를 제공 하지 않았습니다 .
phwd

1
이 질문은 Reddit에 대해 상당히 흥미로운 토론을 일으켰습니다 .
yannis

답변:


126

커리 기능을 사용하면 전문화되기 때문에 더 추상적 인 기능을 쉽게 재사용 할 수 있습니다. 추가 기능이 있다고 가정 해 봅시다.

add x y = x + y

목록의 모든 멤버에 2를 추가하려고합니다. Haskell에서는 다음을 수행합니다.

map (add 2) [1, 2, 3] -- gives [3, 4, 5]
-- actually one could just do: map (2+) [1, 2, 3], but that may be Haskell specific

함수를 작성해야 할 때보 다 구문이 가벼워집니다. add2

add2 y = add 2 y
map add2 [1, 2, 3]

또는 익명의 람다 함수를 만들어야하는 경우 :

map (\y -> 2 + y) [1, 2, 3]

또한 다른 구현에서 분리 할 수 ​​있습니다. 두 개의 조회 기능이 있다고 가정 해 봅시다. 하나는 키 / 값 쌍의 목록에서 하나이고 키는 값으로, 다른 하나는 키에서 값으로, 키에서 값으로의 맵입니다.

lookup1 :: [(Key, Value)] -> Key -> Value -- or perhaps it should be Maybe Value
lookup2 :: Map Key Value -> Key -> Value

그런 다음 키에서 값으로 조회 기능을 허용하는 기능을 만들 수 있습니다. 위의 조회 기능 중 하나를 목록 또는 맵에 각각 부분적으로 적용하여 전달할 수 있습니다.

myFunc :: (Key -> Value) -> .....

결론 : 당신이 부분적으로 / 전문 경량 구문을 사용하여 기능을 적용하고 다음과 같은 고차 기능에 주변이 부분적으로 적용 기능을 전달할 수 있기 때문에 태닝은, 좋은 mapfilter. 고차 함수 (함수를 매개 변수로 취하거나 결과로 산출 함)는 함수형 프로그래밍의 빵과 버터이며, 카레 및 부분적으로 적용되는 함수를 통해 고차 함수를 훨씬 더 효과적이고 간결하게 사용할 수 있습니다.


31
이 때문에 Haskell의 함수에 사용되는 인수 순서는 부분 적용 가능성에 따라 결정되므로 더 많은 상황에서 위에서 설명한 이점이 적용됩니다 (ha, ha). 기본적으로 카레는 여기서와 같은 특정 예에서 알 수있는 것보다 훨씬 유리합니다.
CA McCann 2013

왓. "키 / 값 쌍의 목록에서 하나와 값으로의 키 및 키에서 값으로, 키에서 값으로의 맵에서 다른 하나"
Mateen Ulhaq

@MateenUlhaq 이전 문장의 연속입니다. 여기서 우리는 키를 기반으로 가치를 얻고 싶다고 가정하고 두 가지 방법이 있습니다. 이 문장은이 두 가지 방법을 열거합니다. 첫 번째 방법으로 키 / 값 쌍의 목록과 값을 찾고자하는 키가 제공되며, 다른 방법으로 적절한 맵이 제공되고 다시 키가 제공됩니다. 문장 바로 다음에 나오는 코드를 보는 것이 도움이 될 수 있습니다.
Boris

53

실질적인 답변은 커링을 통해 익명 함수를 훨씬 쉽게 만들 수 있다는 것입니다. 최소한의 람다 구문으로도이기는 것입니다. 비교:

map (add 1) [1..10]
map (\ x -> add 1 x) [1..10]

못생긴 람다 구문이 있다면 더 나빠집니다. (저는 JavaScript, Scheme 및 Python을보고 있습니다.)

점점 더 많은 고차 함수를 사용할수록 점점 유용 해집니다. 내가 사용하는 동안 다른 언어에 비해 하스켈에서 고차원의 기능을, 나는 실제로 람다 구문을 사용 찾은 적은 시간의 3 분의 2와 같은 일이, 람다 단지 부분적으로 적용 기능 것이기 때문에. (그리고 다른 많은 시간에는 이름이 지정된 함수로 추출합니다.)

더 근본적으로, 어떤 버전의 함수가 "정식"인지 항상 명확하지는 않습니다. 예를 들어 보자 map. 의 유형은 map두 가지 방법으로 쓸 수 있습니다.

map :: (a -> b) -> [a] -> [b]
map :: (a -> b) -> ([a] -> [b])

어느 것이 "올바른"것입니까? 실제로 말하기는 어렵습니다. 실제로 대부분의 언어는 첫 번째 언어를 사용합니다. 맵은 함수와 목록을 가져 와서 목록을 반환합니다. 그러나 기본적으로 실제로 맵이하는 것은 함수를 나열하고 함수를 반환하는 일반 함수를 매핑하는 것입니다. 지도가 커리면이 질문에 대답 할 필요가 없습니다. 두 가지 모두 매우 우아하게 수행됩니다.

maplist 이외의 유형으로 일반화하면 특히 중요 합니다.

또한 카레는 실제로 그렇게 복잡하지 않습니다. 실제로 대부분의 언어가 사용하는 모델을 약간 단순화 한 것입니다. 언어에 구운 여러 인수의 함수에 대한 개념이 필요하지 않습니다. 이것은 또한 기본 람다 미적분을 더 밀접하게 반영합니다.

물론 ML 스타일 언어는 여러 가지 인수에 대한 개념을 카레 또는 비 카리 형식으로 갖지 않습니다. f(a, b, c)구문은 실제로 튜플 전달에 해당하는 (a, b, c)f, 그래서 f여전히 인수를 취한다. 이것은 실제로 다른 언어가 가질 수있는 매우 유용한 차이점입니다. 왜냐하면 다음과 같은 것을 작성하는 것이 매우 자연 스럽기 때문입니다.

map f [(1,2,3), (4,5,6), (7, 8, 9)]

여러 가지 주장에 대한 아이디어가있는 언어로는이 작업을 쉽게 수행 할 수 없습니다!


1
"ML 스타일 언어는 카레 또는 비 커리 형태의 다중 인수 개념을 갖지 않습니다."이런 측면에서 Haskell ML 스타일은 무엇입니까?
Giorgio

1
@Giorgio : 응.
Tikhon Jelvis 2012

1
흥미 롭군 나는 Haskell을 알고 있으며 현재 SML을 배우고 있으므로 두 언어의 차이점과 유사점을 보는 것이 흥미 롭습니다.
Giorgio

큰 대답, 그리고 만약 당신이 여전히 확신이 없다면 람다 스트림과 비슷한 유닉스 파이프 라인에 대해 생각하십시오
Sridhar Sarnobat

"실용적인"답변은 그다지 중요하지 않습니다. 왜냐하면 자세한 내용은 카레가 아닌 부분적 적용에 의해 일반적으로 회피되기 때문 입니다. 그리고 여기에 람다 추상화의 구문 (유형 선언에도 불구하고)이 구문에서 올바르게 구문 분석하기 위해 더 많은 내장 특수 구문 규칙이 필요하기 때문에 (적어도) 그보다 더 추악합니다. 시맨틱 속성에 대해.
FrankHB

24

Currying은 첫 번째 클래스 객체로 전달되는 함수가 있고 코드의 한 곳에서 평가하는 데 필요한 모든 매개 변수를받지 못한 경우 유용 할 수 있습니다. 하나 이상의 매개 변수를 가져 와서 적용하면 더 많은 매개 변수가있는 다른 코드로 결과를 전달하고 평가를 완료 할 수 있습니다.

이 작업을 수행하는 코드는 모든 매개 변수를 먼저 모아야하는 경우보다 간단합니다.

또한 단일 매개 변수를 사용하는 함수 (다른 커리 기능)가 모든 매개 변수와 구체적으로 일치하지 않아도되므로 더 많은 코드를 재사용 할 가능성이 있습니다.


14

카레에 대한 주된 동기는 (적어도 초기에는) 실용적이지 않고 이론적이었습니다. 특히, 카레를 사용하면 실제로 의미를 정의하거나 제품에 대한 의미를 정의하지 않고도 다중 인수 함수를 효과적으로 얻을 수 있습니다. 이것은 다른 복잡한 언어만큼 표현력이 더 단순한 언어로 이어 지므로 바람직합니다.


2
여기서의 동기는 이론적이지만 단순성은 거의 항상 실질적인 이점이라고 생각합니다. 다중 인수 함수에 대해 걱정하지 않아도 시맨틱으로 작업하는 것처럼 프로그래밍 할 때 내 삶이 더 쉬워집니다.
Tikhon Jelvis 2019

2
@TikhonJelvis 프로그래밍 할 때, 커링은 컴파일러가 함수에 너무 적은 인수를 전달했다는 사실을 포착하지 못하거나 그 경우 잘못된 오류 메시지를 얻는 것과 같이 걱정할 다른 것들을 제공합니다. 커리를 사용하지 않으면 오류가 훨씬 더 분명합니다.
Alex R

나는 그런 문제를 겪어 본 적이 없다. 최소한 GHC는 그 점에서 매우 훌륭하다. 컴파일러는 항상 이러한 종류의 문제를 포착하며이 오류에 대한 오류 메시지도 있습니다.
Tikhon Jelvis

1
오류 메시지가 양호하다는 데 동의하지 않습니다. 서비스 가능, 예, 그러나 아직 좋지 않습니다. 또한 유형 오류가 발생하는 경우, 즉 나중에 결과를 함수가 아닌 다른 것으로 사용하려고 시도하는 경우 (또는 주석을 달았지만 읽을 수있는 오류에 의존하면 자체 문제가 있습니다) ); 오류의보고 된 위치는 실제 위치와 이혼합니다.
Alex R

14

(하스켈로 예를 들어 보겠습니다.)

  1. 기능적 언어를 사용할 때 기능을 부분적으로 적용 할 수있는 것이 매우 편리합니다. Haskell과 마찬가지로 인수가 주어진 용어와 같은 경우 (== x)를 반환하는 함수입니다 .Truex

    mem :: Eq a => a -> [a] -> Bool
    mem x lst = any (== x) lst
    

    카레하지 않으면 읽을 수있는 코드가 다소 적습니다.

    mem x lst = any (\y -> y == x) lst
    
  2. 이것은 Tacit 프로그래밍 과 관련이 있습니다 ( Haskell Wiki의 Pointfree 스타일 참조 ). 이 스타일은 변수로 표현 된 값이 아니라 함수 구성 및 함수 체인을 통해 정보가 흐르는 방식에 중점을 둡니다. 예제를 변수를 전혀 사용하지 않는 형식으로 변환 할 수 있습니다.

    mem = any . (==)
    

    여기에서 우리는 볼 ==에서 함수로 aa -> Boolany의 함수로 a -> Bool[a] -> Bool. 간단히 작성하면 결과를 얻을 수 있습니다. 이것은 모두 카레 덕분입니다.

  3. 커리어없는 카레는 일부 상황에서도 유용합니다. 예를 들어, 목록을 10보다 작은 요소와 나머지 두 부분으로 나눈 다음 두 목록을 연결한다고 가정 해 봅시다. 리스트의 분할은 다음과 같이 수행됩니다 (여기서는 curried도 사용합니다 ). 결과는 유형 입니다. 결과를 첫 번째와 두 번째 부분으로 추출 하고을 사용하여 결합하는 대신 다음과 같이 커리를 제거 하여 직접 수행 할 수 있습니다partition (< 10)<([Int],[Int])++++

    uncurry (++) . partition (< 10)
    

실제로로 (uncurry (++) . partition (< 10)) [4,12,11,1]평가됩니다 [4,1,12,11].

중요한 이론적 장점도 있습니다.

  1. 카레는 데이터 유형이없고 람다 미적분 과 같은 함수 만있는 언어에 필수적입니다 . 이러한 언어는 실용적으로는 유용하지 않지만 이론적 인 관점에서 매우 중요합니다.
  2. 이것은 기능적 언어의 필수 속성과 관련이 있습니다. 함수는 일류 객체입니다. 앞에서 살펴본 것처럼에서 (a, b) -> c로 변환 a -> (b -> c)은 후자의 함수 결과가 유형임을 의미합니다 b -> c. 다시 말해 결과는 함수입니다.
  3. (Un) 카레는 직교 폐쇄 범주 와 밀접한 관련이 있으며 , 이는 람다 미적분법을 보는 범주적인 방법입니다.

"훨씬 읽기 어려운 코드"비트의 경우 mem x lst = any (\y -> y == x) lst? (백 슬래시 포함).
stusmith

예, 지적 해 주셔서 감사합니다. 수정하겠습니다.
Petr Pudlák

9

카레는 단순한 구문 설탕이 아닙니다!

add1(uncurried) 및 add2( curried) 의 유형 서명을 고려하십시오 .

add1 : (int * int) -> int
add2 : int -> (int -> int)

두 경우 모두 형식 서명의 괄호는 선택 사항이지만 명확성을 기하기 위해 괄호를 포함 시켰습니다.

add1의 2- 터플을 취하는 함수 intint및를 반환 int. add2를받는 함수입니다 int및 반환 다른 기능 차례에 소요 int과를 반환 int.

함수 응용 프로그램을 명시 적으로 지정하면 둘 사이의 본질적인 차이가 더 잘 나타납니다. 첫 번째 인수를 두 번째 인수에 적용하는 함수 (커리되지 않음)를 정의 해 보겠습니다.

apply(f, b) = f b

이제 우리는 차이 사이에 볼 수 있습니다 add1add2더 명확합니다. add12 튜플로 호출됩니다.

apply(add1, (3, 5))

그러나 add2an으로 호출 된 int 다음 반환 값이 다른 호출됩니다int .

apply(apply(add2, 3), 5)

편집 : 카레의 가장 큰 장점은 부분적으로 무료로 신청할 수 있다는 것입니다. 매개 변수에 5를 추가 한 유형의 함수 int -> int(예 map: 목록에 대한 함수)를 원한다고 가정합니다 . 당신은 쓸 수 addFiveToParam x = x+5있거나 인라인 람다로 동등한 것을 할 수는 있지만 훨씬 더 쉽게 (특히 이보다 덜 사소한 경우) 쓸 수 있습니다 add2 5!


3
필자의 사례에는 배후에 큰 차이가 있음을 이해하지만 그 결과는 간단한 구문 변경으로 보입니다.
Mad Scientist

5
카레는 매우 심오한 개념이 아닙니다. 기본 모델을 단순화하거나 (Lamda Calculus 참조) 어쨌든 튜플이있는 언어는 부분 적용의 구문 편의성에 관한 것입니다. 구문 편의의 중요성을 과소 평가하지 마십시오.
Peaker

9

카레는 단지 구문 설탕이지만 설탕의 작용을 약간 오해하고 있다고 생각합니다. 예를 들어 보면

fun add x y = x + y

실제로 구문 설탕입니다

fun add x = fn y => x + y

즉, (add x)는 인수 y를 사용하고 x를 y에 추가하는 함수를 반환합니다.

fun addTuple (x, y) = x + y

그것은 튜플을 취하고 그 요소를 추가하는 함수입니다. 이 두 기능은 실제로 상당히 다릅니다. 그들은 다른 주장을 취합니다.

목록의 모든 숫자에 2를 추가하려면 다음을 수행하십시오.

(* add 2 to all numbers using the uncurried function *)
map (fn x => addTuple (x, 2)) [1,2,3]
(* using the curried function *)
map (add 2) [1,2,3]

결과는 다음과 같습니다 [3,4,5].

반면에 목록에서 각 튜플을 합산하려면 addTuple 함수가 완벽하게 맞습니다.

(* Sum each tuple using the uncurried function *)
map addTuple [(10,2), (10,3), (10,4)]    
(* sum each tuple using curried function *)
map (fn (a,b) => add a b) [(10,2), (10,3), (10,4)]

결과는 다음과 같습니다 [12,13,14].

커리 기능은 맵, 접기, 앱, 필터와 같은 부분 적용이 유용한 경우 유용합니다. 제공된 목록에서 가장 큰 양수를 반환하거나 양수가 없으면 0을 반환하는이 함수를 고려하십시오.

- val highestPositive = foldr Int.max 0;   
val highestPositive = fn : int list -> int 

1
커리 함수에는 다른 유형 서명이 있으며 실제로 다른 함수를 반환하는 함수라는 것을 이해했습니다. 그래도 부분 적용 부분이 누락되었습니다.
Mad Scientist

9

내가 아직 언급하지 않은 또 다른 것은 카레가 arity에 대해 (제한된) 추상화를 허용한다는 것입니다.

Haskell 라이브러리의 일부인 이러한 기능을 고려하십시오

(.) :: (b -> c) -> (a -> b) -> a -> c
either :: (a -> c) -> (b -> c) -> Either a b -> c
flip :: (a -> b -> c) -> b -> a -> c
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

각각의 경우에 유형 변수 c는 함수 유형이 될 수 있으므로 이러한 함수는 인수 매개 변수 목록의 일부 접 두부에서 작동합니다. 커리가 없다면, 기능 arity를 ​​추상화하기 위해 특별한 언어 기능이 필요하거나 다른 arities에 특화된 이러한 기능의 많은 다른 버전이 있어야합니다.


6

나의 제한된 이해는 다음과 같습니다.

1) 부분 기능 적용

부분 함수 응용 프로그램 은 더 적은 수의 인수를 사용하는 함수를 반환하는 프로세스입니다. 3 개 중 2 개의 인수를 제공하면 3-2 = 1 개의 인수를 취하는 함수가 반환됩니다. 3 개 중 1 개의 인수를 제공하면 3-1 = 2 개의 인수를 취하는 함수가 반환됩니다. 원하는 경우 3 개 중 3 개의 인수를 부분적으로 적용 할 수 있으며 인수가없는 함수를 반환합니다.

따라서 다음 기능이 주어집니다.

f(x,y,z) = x + y + z;

1을 x에 바인딩하고 위의 함수에 부분적으로 적용하면 다음과 같은 f(x,y,z)이점이 있습니다.

f(1,y,z) = f'(y,z);

어디: f'(y,z) = 1 + y + z;

이제 y를 2에 바인딩하고 z를 3에 바인딩하고 부분적으로 적용 f'(y,z)하면 다음을 얻을 수 있습니다.

f'(2,3) = f''();

어디에 : f''() = 1 + 2 + 3;

이제 어떤 시점에서, 당신은 평가하도록 선택할 수 있습니다 f, f'또는 f''. 그래서 할 수 있습니다 :

print(f''()) // and it would return 6;

또는

print(f'(1,1)) // and it would return 3;

2) 카레

반면에 카레 는 함수를 하나의 인수 함수의 중첩 된 체인으로 분할하는 프로세스입니다. 둘 이상의 인수를 제공 할 수 없으며 1 또는 0입니다.

따라서 동일한 기능이 주어집니다.

f(x,y,z) = x + y + z;

당신이 그것을 커리면, 당신은 세 가지 기능의 체인을 얻을 것입니다 :

f'(x) -> f''(y) -> f'''(z)

어디:

f'(x) = x + f''(y);

f''(y) = y + f'''(z);

f'''(z) = z;

이제 전화 f'(x)하면 x = 1:

f'(1) = 1 + f''(y);

새로운 기능이 반환됩니다.

g(y) = 1 + f''(y);

당신이 전화 g(y)하면 y = 2:

g(2) = 1 + 2 + f'''(z);

새로운 기능이 반환됩니다.

h(z) = 1 + 2 + f'''(z);

마지막으로 전화 h(z)하면 z = 3:

h(3) = 1 + 2 + 3;

당신은 반환 6됩니다.

3) 폐쇄

마지막으로 폐쇄 는 함수와 데이터를 단일 단위로 캡처하는 프로세스입니다. 함수 클로저는 0에서 무한한 수의 인수를 취할 수 있지만 전달되지 않은 데이터도 알고 있습니다.

다시 말하지만 동일한 기능이 주어집니다.

f(x,y,z) = x + y + z;

대신 클로저를 작성할 수 있습니다.

f(x) = x + f'(y, z);

어디:

f'(y,z) = x + y + z;

f'에 닫힙니다 x. f'내부의 x 값을 읽을 수 있음을 의미합니다 f.

그래서 만약 당신이 전화를했다 f와 함께 x = 1:

f(1) = 1 + f'(y, z);

당신은 폐쇄를 얻을 것이다 :

closureOfF(y, z) =
                   var x = 1;
                   f'(y, z);

그리고 당신 closureOfFy = 2and로 전화하면 z = 3:

closureOfF(2, 3) = 
                   var x = 1;
                   x + 2 + 3;

어느 것이 돌아 올까 6

결론

카레, 부분 적용 및 클로저는 기능을 더 많은 부분으로 분해한다는 점에서 다소 유사합니다.

Currying은 여러 인수의 함수를 단일 인수의 함수를 반환하는 단일 인수의 중첩 함수로 분해합니다. 이해가되지 않기 때문에 하나 또는 그 이상의 논증의 함수를 카레 할 필요는 없습니다.

부분 응용 프로그램은 여러 인수의 함수를 현재 누락 된 인수가 제공된 값으로 대체 된 더 작은 인수의 함수로 분해합니다.

클로저는 함수를 함수와 함수로 분해하여 전달되지 않은 함수 내부의 변수가 데이터 세트 내부를 조사하여 평가 요청시 바인딩 할 값을 찾을 수있는 데이터 세트로 분해합니다.

이 모든 것들에 대해 혼란스러운 것은 그것들이 각각 다른 것들의 부분 집합을 구현하는데 사용될 수 있다는 것입니다. 본질적으로 그것들은 모두 약간의 구현 세부 사항입니다. 그것들은 모든 값을 미리 모을 필요가 없으며 기능의 일부를 신중한 단위로 분해했기 때문에 재사용 할 수 있다는 점에서 비슷한 가치를 제공합니다.

폭로

나는 결코 주제의 전문가가 아니며, 최근에 이것들에 대해 배우기 시작했으며, 따라서 현재의 이해를 제공하지만 실수로 지적 할 수있는 실수가있을 수 있습니다. 나는 무엇이든 발견한다.


1
대답은 : 카레는 이점이 없습니까?
ceving

1
@ceving 내가 아는 한 맞습니다. 실제로 카레와 부분 적용은 동일한 이점을 제공합니다. 언어로 구현하기위한 선택은 구현상의 이유로 이루어지며, 특정 언어가 주어지면 구현하기가 더 쉬울 수 있습니다.
Didier A.

5

Currying (부분 응용 프로그램)을 사용하면 일부 매개 변수를 수정하여 기존 함수에서 새 함수를 만들 수 있습니다. 익명 함수가 캡처 된 인수를 다른 함수에 전달하는 간단한 래퍼 인 어휘 폐쇄의 특수한 경우입니다. 어휘 클로저를 만들기위한 일반적인 구문을 사용하여이 작업을 수행 할 수도 있지만 부분 응용 프로그램에서는 단순화 된 구문 설탕을 제공합니다.

이것이 기능적 스타일로 작업 할 때 Lisp 프로그래머가 때때로 부분적 적용을 위해 라이브러리를 사용하는 이유 입니다.

대신에 (lambda (x) (+ 3 x))인수에 3을 추가하는 함수를 제공하는 대신에 다음과 같이 작성할 수 있습니다. (op + 3)따라서 일부 목록의 모든 요소에 3을 추가하는 것은 (mapcar (op + 3) some-list)오히려 아닙니다 (mapcar (lambda (x) (+ 3 x)) some-list). 이 op매크로는 인자를 취하고 x y z ...호출 하는 함수를 만듭니다 (+ a x y z ...).

순전히 기능적인 많은 언어에서 부분 응용 프로그램은 구문이 없어서 op연산자 가 없습니다 . 부분 응용 프로그램을 트리거하려면 필요한 것보다 적은 수의 인수로 함수를 호출하면됩니다. "insufficient number of arguments"오류 를 생성하는 대신 결과는 나머지 인수의 함수입니다.


"Currying ...은 일부 매개 변수를 수정하여 새 기능을 만들 수 있습니다."-아니요, 유형의 함수 a -> b -> c에는 매개 변수 s (복수)가 없으며 매개 변수가 하나만 c있습니다. 호출되면 유형의 함수를 반환합니다 a -> b.
Max Heiber

4

기능

fun add(x, y) = x + y

그것은 형태입니다 f': 'a * 'b -> 'c

평가하려면

add(3, 5)
val it = 8 : int

카레 기능

fun add x y = x + y

평가하려면

add 3
val it = fn : int -> int

부분 계산, 특히 (3 + y) 인 경우 계산을 완료 할 수 있습니다

it 5
val it = 8 : int

두 번째 경우에 추가하십시오 형태입니다 f: 'a -> 'b -> 'c

여기서 커리가하는 것은 두 개의 동의를 취하는 함수를 하나의 결과 만 리턴하는 함수로 변환하는 것입니다. 부분 평가

왜 이것이 필요할까요?

x우변에하는 것은 일반 INT, 대신 보강, 술, 2 초, 완료하는 데 시간이 걸립니다 복잡한 계산뿐만 아니라입니다.

x = twoSecondsComputation(z)

이제 함수는 다음과 같습니다

fun add (z:int) (y:int) : int =
    let
        val x = twoSecondsComputation(z)
    in
        x + y
    end;

유형의 add : int * int -> int

이제이 함수를 다양한 숫자에 대해 계산하려고합니다.

val result1 = map (fn x => add (20, x)) [3, 5, 7];

위의 결과 twoSecondsComputation는 매번 평가됩니다. 이것은이 계산에 6 초가 걸린다는 것을 의미합니다.

준비와 카레의 조합을 사용하면 이것을 피할 수 있습니다.

fun add (z:int) : int -> int =
    let
        val x = twoSecondsComputation(z)
    in
        (fn y => x + y)
    end;

카레 양식 add : int -> int -> int

이제 할 수 있습니다

val add' = add 20;
val result2 = map add' [3, 5, 7, 11, 13];

twoSecondsComputation만하면 한 번 평가한다. 스케일을 높이려면 2 초를 15 분 또는 1 시간으로 바꾸고 100 숫자에 대한 맵을 갖습니다.

요약 : 부분 평가 도구로 더 높은 수준의 기능을 위해 다른 방법과 함께 사용하면 카레가 훌륭합니다. 그 목적은 그 자체로는 실제로 입증 될 수 없습니다.


3

카레는 유연한 기능 구성을 허용합니다.

기능 "카레"를 만들었습니다. 이러한 맥락에서, 나는 어떤 종류의 로거를 얻었는지 또는 어디에서 왔는지 상관하지 않습니다. 나는 행동이 무엇인지, 어디에서 왔는지 상관하지 않습니다. 내가 걱정하는 것은 입력을 처리하는 것입니다.

var builder = curry(function(input, logger, action) {
     logger.log("Starting action");
     try {
         action(input);
         logger.log("Success!");
     }
     catch (err) {
         logger.logerror("Boo we failed..", err);
     }
});
var x = "My input.";
goGatherArgs(builder)(x); // Supplies action first, then logger somewhere.

빌더 변수는 작업을 수행하는 입력을받는 함수를 리턴하는 함수를 리턴하는 함수입니다. 이것은 간단한 유용한 예이며 보이지 않는 물체입니다.


2

카레는 함수에 대한 모든 인수가 없을 때 유리합니다. 함수를 완전히 평가하면 큰 차이가 없습니다.

Currying을 통해 아직 필요하지 않은 매개 변수는 언급하지 않아도됩니다. 더 간결하고 범위 내 다른 변수와 충돌하지 않는 매개 변수 이름을 찾을 필요가 없습니다 (가장 선호하는 이점입니다).

예를 들어, 함수를 인수로 취하는 함수를 사용할 때 "입력에 3 추가"또는 "변수 v에 입력 비교"와 같은 함수가 필요한 경우가 종종 있습니다. 태닝,이 기능을 쉽게 기록됩니다 add 3(== v). 카레하지 않고 람다 식을 사용해야합니다 : x => add 3 xx => x == v. 람다 식의 길이는 두 배이며, x이미 x범위 내에 있는 경우 외에 이름을 선택하는 것과 관련하여 소량의 바쁜 작업이 있습니다 .

카레를 기반으로하는 언어의 부수적 인 이점은 함수에 대한 일반 코드를 작성할 때 매개 변수 수에 따라 수백 개의 변형을 갖지 않는다는 것입니다. 예를 들어 C #에서 'curry'방법에는 Func <R>, Func <A, R>, Func <A1, A2, R>, Func <A1, A2, A3, R> 등의 변형이 필요합니다. 영원히. Haskell에서 Func <A1, A2, R>에 해당하는 것은 Func <Tuple <A1, A2>, R> 또는 Func <A1, Func <A2, R >> (및 Func <R> 는 Func <Unit, R>)와 비슷하므로 모든 변형이 단일 Func <A, R> 경우에 해당합니다.


2

내가 생각할 수있는 (그리고이 주제에 대해 전문가가 아닌) 주요한 추론은 그 기능이 사소한 것에서 사소한 것으로 이동함에 따라 그 이점을 보여주기 시작합니다. 이러한 성격에 대한 대부분의 개념을 가진 모든 사소한 경우에는 실제 이점이 없습니다. 그러나 대부분의 기능적 언어는 처리 작업에서 스택을 많이 사용합니다. 이 예로 PostScript 또는 Lisp 을 고려하십시오 . 카레를 사용함으로써 기능을보다 효과적으로 쌓을 수 있으며, 작업이 점점 더 사소 해짐에 따라 이러한 이점이 분명해집니다. 커리 화 된 방식으로, 명령 및 인수는 순서대로 스택에 던져지고 적절한 순서로 실행되도록 필요에 따라 튀어 나올 수 있습니다.


1
더 많은 스택 프레임을 생성해야 더 효율적으로 작업 할 수 있습니까?
메이슨 휠러

1
@MasonWheeler : 나는 기능적 언어에 대한 전문가가 아니거나 구체적으로 카레 링하는 사람이 아니라고 말 했으므로 알 수 없습니다. 그 때문에이 커뮤니티 위키에 특별히 레이블을 붙였습니다.
Joel Etherton

4
@MasonWheeler 당신은이 답변의 문구를 쓸만한 요점을 가지고 있지만 실제로 스택 프레임의 양이 구현에 달려 있다고 말할 수 있습니다. 예를 들어, 척추없는 tagless G 시스템 (STG; GHC가 Haskell을 구현하는 방식)에서 모든 (또는 최소한 필요한 것으로 알고있는) 인수가 누적 될 때까지 실제 평가를 지연시킵니다. 이것이 모든 함수에 대해 수행되는지 아니면 생성자에 대해서만 수행되는지를 기억할 수는 없지만 대부분의 함수에 가능해야한다고 생각 합니다. (다시, "스택 프레임"의 개념은 실제로 STG에 적용되지 않습니다.)

1

카레는 함수를 반환하는 능력에 결정적으로 (결정적으로) 결정됩니다.

이 (구의 된) 의사 코드를 고려하십시오.

var f = (m, x, b) => ... 무언가를 반환합니다 ...

인수가 3 개 미만인 f를 호출하면 함수가 반환된다고 지정합니다.

var g = f (0, 1); // 하나 이상의 인수 (b)를 허용하는 0과 1 (m 및 x)에 바인딩 된 함수를 반환합니다.

var y = g (42); // m과 x에 0과 1을 사용하여 누락 된 세 번째 인수로 g를 호출합니다.

인수를 부분적으로 적용하고 재사용 할 수있는 함수 (제공 한 인수에 바인딩 된)를 다시 얻는 것은 매우 유용합니다 (그리고 DRY).

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