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

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

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

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

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



답변

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

두 번째부터 시작하겠습니다 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 토론으로 인해).

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


답변

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

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

MyModule.my_function(foo) 

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

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

스스로에게 묻는다면 :

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

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


답변

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

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

비교:

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에 힌트를 주어야하는지는 분명합니다. 모든 변수 역 참조가 함수를 검사해야한다고 상상해보십시오. 또는 변수 역 참조가 함수를 사용한다고 항상 추론하기 위해 어떤 영웅이 필요한지 상상해보십시오.


답변

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

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 약간 다른 표기법

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

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

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

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


답변

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

두 가지 유형의 기능

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

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 년 동안 당신의 삶을 설명 할 수있을 것입니다.


답변

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함수 자체를 얻거나 호출하거나 결과 익명 함수를 호출하는 것의 의미를 알 수 없습니다 .


답변

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

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

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