엘릭서에는 왜 두 종류의 기능이 있습니까?


279

Elixir를 배우고 있는데 왜 두 가지 유형의 함수 정의가 있는지 궁금합니다.

  • 모듈로 함수로 정의 def하여 호출,myfunction(param1, param2)
  • 익명의 함수로 정의 fn하여 호출,myfn.(param1, param2)

두 번째 종류의 함수 만 일류 객체 인 것으로 보이며 다른 함수에 매개 변수로 전달할 수 있습니다. 모듈에 정의 된 함수는에 싸야합니다 fn. otherfunction(myfunction(&1, &2))그것을 쉽게하기 위해 보이는 구문 설탕이 있지만 처음에는 왜 필요합니까? 우리는 왜 그냥 할 수 otherfunction(myfunction))없습니까? 루비처럼 괄호없이 모듈 함수를 호출하는 것만 허용합니까? 모듈 기능과 재미를 가진 Erlang의 이러한 특성을 물려받은 것 같습니다. 실제로 Erlang VM이 내부적으로 작동하는 방식에서 비롯된 것입니까?

두 가지 유형의 기능이 있고 다른 기능으로 전달하기 위해 한 유형에서 다른 유형으로 변환하는 이점이 있습니까? 함수를 호출하는 데 다른 두 가지 표기법이있는 이점이 있습니까?

답변:


386

이름을 명확히하기 위해 두 기능입니다. 하나는 명명 된 함수이고 다른 하나는 익명의 함수입니다. 그러나 당신은 옳습니다. 그들은 약간 다르게 작동하며 왜 그들이 그렇게 작동하는지 설명하려고합니다.

두 번째부터 시작하겠습니다 fn. in in Ruby fn와 비슷한 클로저 lambda입니다. 다음과 같이 만들 수 있습니다.

x = 1
fun = fn y -> x + y end
fun.(2) #=> 3

함수는 여러 절을 가질 수 있습니다.

x = 1
fun = fn
  y when y < 0 -> x - y
  y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3

이제 다른 것을 시도해 봅시다. 다른 수의 인수를 기대하는 다른 절을 정의 해 봅시다.

fn
  x, y -> x + y
  x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition

아뇨! 오류가 발생했습니다! 다른 개수의 인수를 예상하는 절을 혼합 할 수 없습니다. 함수는 항상 고정 된 arity를 ​​갖습니다.

이제 명명 된 함수에 대해 이야기 해 보자.

def hello(x, y) do
  x + y
end

예상대로 이름이 있으며 일부 인수를받을 수도 있습니다. 그러나 폐쇄는 아닙니다.

x = 1
def hello(y) do
  x + y
end

이 코드는을 볼 때마다 def빈 변수 범위를 얻으 므로 컴파일에 실패합니다 . 그것은 그들 사이의 중요한 차이점입니다. 특히 명명 된 각 함수가 깨끗한 슬레이트로 시작하고 서로 다른 범위의 변수가 모두 혼합되어 있지 않다는 사실이 특히 좋습니다. 명확한 경계가 있습니다.

위의 명명 된 hello 함수를 익명 함수로 검색 할 수 있습니다. 당신은 그것을 스스로 언급했습니다.

other_function(&hello(&1))

그리고 당신은 왜 hello다른 언어 에서처럼 단순히 그것을 통과 할 수 없는지 물었습니다 . Elixir의 기능은 이름 과 특성 으로 식별되기 때문 입니다. 따라서 두 개의 인수를 예상하는 함수는 이름이 같더라도 세 개의 인수를 기대하는 함수와 다릅니다. 그래서 우리가 단순히 통과했다면 hello, 우리는 hello당신이 실제로 무엇을 의미 했는지 전혀 모릅니다 . 두 개, 세 개 또는 네 개의 논쟁이있는 사람? 이것은 우리가 다른 민족을 가진 절로 익명 함수를 만들 수없는 이유와 정확히 같은 이유입니다.

Elixir v0.10.1부터는 명명 된 함수를 캡처하는 구문이 있습니다.

&hello/1

로컬로 명명 된 함수 hello를 arity 1로 캡처합니다. 언어 및 문서 전체에서이 hello/1구문 에서 함수를 식별하는 것이 매우 일반적 입니다.

이것은 또한 Elixir가 익명 함수 호출을 위해 점을 사용하는 이유입니다. 단순히 hello함수로 전달할 수 없으므로 명시 적으로 캡처해야합니다. 명명 된 함수와 익명 함수 사이에는 자연스러운 차이가 있으며 각각을 호출하기위한 고유 한 구문은 모든 것을 조금 더 명확하게 만듭니다 (Lispers는 이것에 익숙 할 것입니다) Lisp 1 vs. Lisp 2 토론으로 인해).

전반적으로, 이것이 우리가 두 가지 기능을하는 이유와 다르게 행동하는 이유입니다.


30
나는 또한 Elixir를 배우고 있는데, 이것이 내가 처음으로 겪은 문제로 잠시 멈추었습니다. 훌륭한 설명이지만 분명합니다… 이것은 구현 문제의 결과입니까, 아니면 함수의 사용과 전달에 관한 더 깊은 지혜를 반영합니까? 익명 함수는 인수 값을 기준으로 일치시킬 수 있으므로 인수 수를 일치시킬 수 있고 다른 곳에서 함수 패턴 일치와 일치하는 것이 유용 할 것 같습니다.
사이클론

17
f()점 없이도 작동 할 수 있다는 의미에서 구현 제약이 아닙니다 .
José Valim

6
is_function/2가드 를 사용하여 인수 수를 일치시킬 수 있습니다 . is_function(f, 2):)
José Valim

20
명명 된 함수와 익명 함수 모두에 점이없는 함수 호출을 선호합니다. 때로는 혼란스럽고 특정 기능이 익명인지 또는 이름이 지정되었는지 잊어 버립니다. 카레에 관해서는 더 많은 소음이 있습니다.
CMCDragonkai

28
얼랑에서, 익명 함수 호출 및 일반 기능은 이미 구문이 다릅니다 : SomeFun()some_fun(). Elixir에서 점을 제거하면 점이 동일 some_fun()하고 some_fun()변수가 함수 이름과 동일한 식별자를 사용하기 때문에 동일합니다. 따라서 점.
José Valim

17

나는 이것이 다른 사람에게 얼마나 유용한 지 모르겠지만, 마침내 개념을 머릿속으로 감싸는 방법은 엘릭서 함수가 함수가 아니라는 것을 깨달았습니다.

엘릭서의 모든 것은 표현입니다. 그래서

MyModule.my_function(foo) 

는 함수가 아니라의 코드를 실행하여 반환되는 표현식입니다 my_function. 실제로 "함수"를 인수로 전달할 수있는 유일한 방법은 익명 함수 표기법을 사용하는 것입니다.

함수 포인터로 fn 또는 & 표기법을 언급하고 싶지만 실제로는 훨씬 더 많습니다. 주변 환경의 폐쇄입니다.

스스로에게 묻는다면 :

이 지점에서 실행 환경이나 데이터 값이 필요합니까?

그리고 fn을 사용하여 실행해야하는 경우 대부분의 어려움이 훨씬 명확 해집니다.


2
그것은 그 분명 MyModule.my_function(foo)표현은하지만 MyModule.my_function표현이었다 "수"가 반환하는 함수 "개체". 그러나 당신은 arity를 ​​말해야하기 때문에 MyModule.my_function/1대신에 뭔가가 필요할 것 입니다. 그리고 그들은 &MyModule.my_function(&1) arity를 ​​표현할 수 있는 구문 을 사용하는 것이 더 낫다고 결정했다고 생각합니다 (그리고 다른 목적도 제공합니다). 왜 ()명명 된 함수를위한 .()연산자와 명명되지 않은 함수를위한 연산자 가 있는지 확실하지 않습니다
RubenLaguna

나는 당신이 원한다고 생각합니다 : &MyModule.my_function/1그것이 함수로서 그것을 전달할 수있는 방법입니다.
jnmandal

14

나는 이것에 대한 설명이 왜 그렇게 복잡한 지 이해하지 못했습니다.

루비 스타일의 "패런없는 함수 실행"의 현실과 결합 된 매우 작은 차이입니다.

비교:

def fun1(x, y) do
  x + y
end

에:

fun2 = fn
  x, y -> x + y
end

둘 다 식별자 일 뿐이지 만 ...

  • fun1 로 정의 된 명명 된 함수를 설명하는 식별자입니다. def .
  • fun2 변수를 설명하는 식별자입니다 (함수에 대한 참조를 포함 함).

당신이 볼 때 그 의미를 고려하십시오 fun1 또는 fun2다른 표현으로 . 해당 표현식을 평가할 때 참조 된 함수를 호출합니까 아니면 메모리에서 값을 참조합니까?

컴파일 타임에 알 수있는 좋은 방법은 없습니다. 루비는 변수 바인딩이 어떤 시점에서 함수를 그림자로 만들 었는지 알아 내기 위해 변수 네임 스페이스를 조사하는 고급 스러움을 가지고 있습니다. 엘릭서는 컴파일되어 실제로 할 수 없습니다. 그것은 점 표기법이하는 일이며, Elixir에게 함수 참조를 포함하고 호출되어야한다고 지시합니다.

그리고 이것은 정말 어렵습니다. 점 표기법이 없다고 상상해보십시오. 이 코드를 고려하십시오.

val = 5

if :rand.uniform < 0.5 do
  val = fn -> 5 end
end

IO.puts val     # Does this work?
IO.puts val.()  # Or maybe this?

위의 코드를 감안할 때, 왜 Elixir에 힌트를 주어야하는지는 분명합니다. 모든 변수 역 참조가 함수를 검사해야한다고 상상해보십시오. 또는 변수 역 참조가 함수를 사용한다고 항상 추론하기 위해 어떤 영웅이 필요한지 상상해보십시오.


12

아무도 언급하지 않았기 때문에 잘못되었을 수도 있지만, 그 이유는 대괄호없이 함수를 호출 할 수있는 루비 유산이기도하다는 인상을 받았습니다.

Arity는 분명히 관련되어 있지만 잠시 동안 제쳐두고 인수없이 함수를 사용할 수 있습니다. 대괄호가 필수 인 자바 스크립트와 같은 언어에서는 함수를 인수로 전달하는 것과 함수를 호출하는 것 사이를 쉽게 구분할 수 있습니다. 대괄호를 사용할 때만 호출합니다.

my_function // argument
(function() {}) // argument

my_function() // function is called
(function() {})() // function is called

보시다시피, 이름을 지정하거나 지정하지 않아도 큰 차이는 없습니다. 그러나 엘릭서와 루비는 대괄호없이 함수를 호출 할 수 있습니다. 이것은 개인적으로 좋아하는 디자인 선택이지만이 부작용이 있으므로 대괄호없이 이름을 사용할 수 없으므로 함수를 호출 할 수 있기 때문입니다. 이것이 바로 이것입니다 &. arity appart를 1 초 동안 남겨두면 함수 이름 앞에 접두어가 있으면 &이 함수가 반환하는 것이 아니라이 함수를 인수로 명시 적으로 사용하려는 것입니다.

이제 익명 함수는 주로 인수로 사용된다는 점에서 약간 다릅니다. 다시 이것은 디자인 선택이지만 그 뒤에 합리적인 이유는 반복자로 함수를 인수로 취하는 일종의 함수에 의해 주로 사용된다는 것입니다. 따라서 &기본적으로 이미 인수로 간주되기 때문에 사용할 필요가 없습니다 . 그들의 목적입니다.

이제 마지막 문제는 코드에서 호출해야 할 때가 있습니다. 반복자 유형의 함수와 함께 항상 사용되는 것은 아니거나 반복자를 직접 코딩 할 수 있기 때문입니다. 작은 이야기의 경우, 루비는 객체 지향이기 때문에 객체의 call메소드 를 사용하는 것이 주된 방법 입니다. 이렇게하면 필수 대괄호 동작을 일관되게 유지할 수 있습니다.

my_lambda.call
my_lambda.call()
my_lambda_with_arguments.call :h2g2, 42
my_lambda_with_arguments.call(:h2g2, 42)

이제 누군가 기본적으로 이름이없는 방법처럼 보이는 바로 가기를 만들었습니다.

my_lambda.()
my_lambda_with_arguments.(:h2g2, 42)

다시, 이것은 디자인 선택입니다. 이제 엘릭서는 객체 지향적이 아니므로 첫 번째 양식을 사용하지 마십시오. José를 말할 수는 없지만 두 번째 양식은 엘릭서에서 사용 된 것처럼 보입니다. 여전히 추가 문자가있는 함수 호출처럼 보입니다. 함수 호출에 충분히 가깝습니다.

나는 모든 장단점을 생각하지는 않았지만 익명 기능을 위해 대괄호를 필수로 만들면 두 언어 모두 대괄호로 벗어날 수있는 것처럼 보입니다. 다음과 같습니다.

필수 대괄호 VS 약간 다른 표기법

두 경우 모두 다르게 행동하기 때문에 예외가 발생합니다. 차이가 있기 때문에 명확하게하고 다른 표기법으로 갈 수 있습니다. 필수 대괄호는 대부분 자연스럽게 보이지만 계획대로 진행되지 않으면 매우 혼란 스러울 수 있습니다.

여기 요 이제는 대부분의 세부 사항을 단순화했기 때문에 이것이 세계에서 가장 잘 설명되지 않을 수 있습니다. 또한 대부분은 디자인 선택이며 심사하지 않고 이유를 제시하려고했습니다. 나는 엘릭서를 사랑하고, 루비를 좋아하고, 대괄호없이 함수 호출을 좋아하지만, 당신처럼, 결과는 때때로 잘못 오해하고 있습니다.

그리고 엘릭시르에서는이 여분의 점에 불과하지만 루비에서는 그 위에 블록이 있습니다. 블록은 놀랍고 블록으로 얼마나 많은 일을 할 수 있는지에 놀랐지 만 마지막 인수 인 익명 함수가 하나만 필요할 때만 작동합니다. 그런 다음 다른 시나리오를 처리 할 수 ​​있으므로 전체 방법 / 람다 / 절차 / 블록 혼란이 있습니다.

어쨌든 ...이 범위를 벗어났습니다.


9

이 동작에 대한 훌륭한 블로그 게시물이 있습니다 : 링크

두 가지 유형의 기능

모듈에 다음이 포함 된 경우 :

fac(0) when N > 0 -> 1;
fac(N)            -> N* fac(N-1).

이것을 잘라서 껍질에 붙여 넣고 같은 결과를 얻을 수는 없습니다.

Erlang에 버그가 있기 때문입니다. Erlang의 모듈은 FORMS 시퀀스입니다 . Erlang 쉘은 일련의 EXPRESSIONS를 평가합니다 . Erlang에서 FORMS표현 이 아닙니다 .

double(X) -> 2*X.            in an Erlang module is a FORM

Double = fun(X) -> 2*X end.  in the shell is an EXPRESSION

둘은 동일하지 않습니다. 이 어리 석음은 얼랭이 였지만 우리는 그 사실을 알지 못했고 함께 사는 법을 배웠습니다.

전화 점 fn

iex> f = fn(x) -> 2 * x end
#Function<erl_eval.6.17052888>
iex> f.(10)
20

학교에서 f (10)가 아닌 f (10)을 작성하여 함수를 호출하는 법을 배웠습니다. 이것은“실제로”Shell.f (10)과 같은 이름의 함수입니다 (쉘에 정의 된 함수입니다). 암시 적이므로 f (10)이라고합니다.

당신이 이렇게 떠나면 다음 20 년 동안 당신의 삶을 설명 할 수있을 것입니다.


나는 OP 질문에 직접 대답하는 데 유용하다는 것을 확신하지 못하지만 제공된 링크 (주석 섹션 포함)는 실제로 질문의 대상 독자, 즉 Elixir + Erlang을 처음 접하고 배우는 사람들에게 훌륭한 읽기 IMO입니다. .

링크는 이제 죽었다 :(
AnilRedshift

2

Elixir에는 arity가 0 인 함수를 포함하여 함수에 대한 선택적 버팀대가 있습니다. 왜 별도의 호출 구문을 중요하게 만드는지에 대한 예를 보자.

defmodule Insanity do
  def dive(), do: fn() -> 1 end
end

Insanity.dive
# #Function<0.16121902/0 in Insanity.dive/0>

Insanity.dive() 
# #Function<0.16121902/0 in Insanity.dive/0>

Insanity.dive.()
# 1

Insanity.dive().()
# 1

두 가지 유형의 함수를 구별하지 않으면 Insanity.dive함수 자체를 얻거나 호출하거나 결과 익명 함수를 호출하는 것의 의미를 알 수 없습니다 .


1

fn ->구문은 익명 함수를 사용하기위한 것입니다. var. ()하는 것은 엘릭서에게 함수를 가지고있는 var를 func로 가져 와서 그 함수를 보유하고있는 것으로서 var를 참조하는 대신 실행하고 싶다고 말하고 있습니다.

Elixir는이 공통 패턴을 가지고 있는데, 함수 안에 로직을 사용하여 무언가를 어떻게 실행해야 하는지를 보는 대신, 우리는 어떤 종류의 입력에 따라 다른 함수와 패턴을 일치시킵니다. 나는 이것이 우리가 의미에서 arity로 사물을 언급하는 이유라고 생각합니다 function_name/1.

속기 함수 정의 (func (& 1) 등)에 익숙해지는 것은 이상하지만, 코드를 간결하게 만들거나 간결하게 만들 때 편리합니다.


0

두 번째 종류의 함수 만 일류 객체 인 것으로 보이며 다른 함수에 매개 변수로 전달할 수 있습니다. 모듈에 정의 된 함수는 fn에 싸야합니다. otherfunction(myfunction(&1, &2))그것을 쉽게하기 위해 보이는 구문 설탕이 있지만 처음에는 왜 필요합니까? 우리는 왜 그냥 할 수 otherfunction(myfunction))없습니까?

넌 할 수있어 otherfunction(&myfunction/2)

elixir는 괄호 (예 : 등 myfunction) 없이 함수를 실행할 수 있으므로 otherfunction(myfunction))이를 사용하여 실행을 시도합니다 myfunction/0.

따라서 동일한 이름의 다른 기능을 가질 수 있으므로 캡처 연산자를 사용하고 arity를 ​​포함한 기능을 지정해야합니다. 따라서 &myfunction/2.

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