Aby wyjaśnić nazewnictwo, obie są funkcjami. Jedna jest funkcją nazwaną, a druga anonimową. Ale masz rację, działają one nieco inaczej i zamierzam zilustrować, dlaczego tak działają.
Zacznijmy drugim fn
. fn
jest zamknięciem, podobnym do lambda
w Ruby. Możemy go utworzyć w następujący sposób:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Funkcja może mieć także wiele klauzul:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
A teraz spróbujmy czegoś innego. Spróbujmy zdefiniować różne klauzule, oczekując innej liczby argumentów:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
O nie! Otrzymujemy błąd! Nie możemy mieszać klauzul, które oczekują innej liczby argumentów. Funkcja zawsze ma ustaloną aranżację.
Porozmawiajmy teraz o nazwanych funkcjach:
def hello(x, y) do
x + y
end
Zgodnie z oczekiwaniami mają nazwę i mogą również otrzymać pewne argumenty. Nie są to jednak zamknięcia:
x = 1
def hello(y) do
x + y
end
Ten kod się nie skompiluje, ponieważ za każdym razem, gdy widzisz a def
, otrzymujesz pusty zakres zmiennej. To ważna różnica między nimi. Szczególnie podoba mi się to, że każda nazwana funkcja zaczyna się od czystej tablicy i nie miesza się zmiennych różnych zakresów. Masz wyraźną granicę.
Mogliśmy pobrać powyższą funkcję hello jako funkcję anonimową. Sam o tym wspomniałeś:
other_function(&hello(&1))
A potem zapytałeś, dlaczego nie mogę tak po prostu przekazać, hello
jak w innych językach? Wynika to z faktu, że funkcje w Elixir są identyfikowane według nazwy i pochodzenia. Tak więc funkcja, która oczekuje dwóch argumentów, jest inną funkcją niż ta, która oczekuje trzech, nawet jeśli miałyby tę samą nazwę. Gdybyśmy po prostu zdali hello
, nie mielibyśmy pojęcia, co hello
naprawdę miałeś na myśli. Ten z dwoma, trzema lub czterema argumentami? To jest dokładnie ten sam powód, dla którego nie możemy stworzyć anonimowej funkcji z klauzulami o różnych aracjach.
Od Elixir v0.10.1 mamy składnię do przechwytywania nazwanych funkcji:
&hello/1
To uchwyci lokalną funkcję o nazwie hello z arity 1. W całym języku i jego dokumentacji bardzo często identyfikuje się funkcje w tej hello/1
składni.
Z tego powodu Elixir używa kropki do wywoływania funkcji anonimowych. Ponieważ nie możesz po prostu przekazać hello
jako funkcji, zamiast tego musisz jawnie ją uchwycić, istnieje naturalne rozróżnienie między funkcjami nazwanymi i anonimowymi, a odrębna składnia wywoływania każdej z nich sprawia, że wszystko jest nieco bardziej wyraźne (Lispers byłby z tym zaznajomiony z powodu dyskusji Lisp 1 vs. Lisp 2).
Ogólnie są to powody, dla których mamy dwie funkcje i dlaczego zachowują się inaczej.
f()
(bez kropki).is_function/2
osłony.is_function(f, 2)
sprawdza, czy ma arity 2. :)SomeFun()
isome_fun()
. W Elixir, jeśli usunęliśmy kropkę, byłyby takie samesome_fun()
isome_fun()
dlatego zmienne używać tego samego identyfikatora jako nazwy funkcji. Stąd kropka.Nie wiem, jak użyteczne będzie to dla kogokolwiek innego, ale sposób, w jaki ostatecznie opuściłem głowę, to uświadomienie sobie, że funkcje eliksiru nie są funkcjami.
Wszystko w eliksir jest wyrazem. Więc
nie jest funkcją, ale wyrażenie zwracane przez wykonanie kodu w
my_function
. W rzeczywistości istnieje tylko jeden sposób na uzyskanie „funkcji”, którą można przekazać jako argument, a mianowicie użycie anonimowej notacji funkcji.Kuszące jest odwoływanie się do fn lub notacji jako wskaźnika funkcji, ale tak naprawdę jest to znacznie więcej. To zamknięcie otaczającego środowiska.
Jeśli zadajesz sobie pytanie:
Czy potrzebuję środowiska wykonawczego lub wartości danych w tym miejscu?
A jeśli potrzebujesz wykonania, użyj fn, wówczas większość trudności staje się znacznie wyraźniejsza.
źródło
MyModule.my_function(foo)
jest to wyrażenie, aleMyModule.my_function
„mogło” być wyrażeniem, które zwraca funkcję „obiekt”. Ale ponieważ musisz powiedzieć arsenowi, potrzebujesz czegoś takiegoMyModule.my_function/1
. I myślę, że zdecydowali, że lepiej jest użyć&MyModule.my_function(&1)
składni, która pozwala wyrazić arity (i służy również innym celom). Nadal niejasne, dlaczego istnieje()
operator dla nazwanych funkcji i.()
operator dla nienazwanych funkcji&MyModule.my_function/1
sposób możesz to przekazać jako funkcję.Nigdy nie zrozumiałem, dlaczego wyjaśnienia tego są tak skomplikowane.
To naprawdę tylko wyjątkowo małe rozróżnienie w połączeniu z realiami „wykonywania funkcji bez parenów” w stylu Rubiego.
Porównać:
Do:
Oba są tylko identyfikatorami ...
fun1
to identyfikator opisujący nazwaną funkcję zdefiniowaną za pomocądef
.fun2
to identyfikator opisujący zmienną (która zawiera odniesienie do funkcji).Zastanów się, co to oznacza, kiedy widzisz
fun1
lubfun2
w innym wyrażeniu? Czy podczas oceny tego wyrażenia wywołujesz funkcję, do której się odwołujesz, czy po prostu odwołujesz się do wartości z pamięci?Nie ma dobrego sposobu na poznanie w czasie kompilacji. Ruby ma luksus introspekcji przestrzeni nazw zmiennych, aby dowiedzieć się, czy powiązanie zmiennej przesłaniało funkcję w pewnym momencie. Kompilowany eliksir tak naprawdę nie może tego zrobić. To właśnie robi notacja kropkowa, mówi Elixirowi, że powinien zawierać odwołanie do funkcji i że powinien zostać wywołany.
I to jest naprawdę trudne. Wyobraź sobie, że nie było notacji kropkowej. Rozważ ten kod:
Biorąc pod uwagę powyższy kod, myślę, że jest całkiem jasne, dlaczego musisz dać Elixirowi podpowiedź. Wyobraź sobie, że każde odwołanie do zmiennej musiało sprawdzać funkcję? Alternatywnie, wyobraź sobie, jaka heroika byłaby konieczna, aby zawsze wywnioskować, że zmienne dereferencje używały funkcji?
źródło
Mogę się mylić, ponieważ nikt o tym nie wspominał, ale miałem również wrażenie, że powodem tego jest także rubinowe dziedzictwo możliwości wywoływania funkcji bez nawiasów.
Arity jest oczywiście zaangażowane, ale odłóżmy to na chwilę i używaj funkcji bez argumentów. W języku takim jak javascript, w którym nawiasy są obowiązkowe, łatwo jest odróżnić przekazanie funkcji jako argumentu od wywołania funkcji. Nazywasz to tylko wtedy, gdy używasz nawiasów.
Jak widać, nazywanie go lub nie ma większego znaczenia. Ale eliksir i rubin pozwalają wywoływać funkcje bez nawiasów. Jest to wybór projektowy, który osobiście mi się podoba, ale ma taki efekt uboczny, że nie można użyć samej nazwy bez nawiasów, ponieważ może to oznaczać, że chcesz wywołać funkcję. Po to
&
jest. Jeśli pozostawisz arity appart na sekundę, dodanie nazwy funkcji&
oznacza, że jawnie chcesz użyć tej funkcji jako argumentu, a nie tego, co ta funkcja zwraca.Teraz funkcja anonimowa różni się tym, że jest używana głównie jako argument. Ponownie jest to wybór projektowy, ale uzasadnia to fakt, że jest używany głównie przez funkcje iteracyjne, które przyjmują funkcje jako argumenty. Więc oczywiście nie musisz używać,
&
ponieważ są już domyślnie uważane za argumenty. To jest ich cel.Ostatni problem polega na tym, że czasami trzeba wywoływać je w kodzie, ponieważ nie zawsze są one używane z funkcją typu iteratora lub może sam kodujesz iterator. W przypadku małej historii, ponieważ ruby jest zorientowany obiektowo, głównym sposobem na to było użycie
call
metody na obiekcie. W ten sposób można zachować spójność zachowania nieobowiązkowych nawiasów.Teraz ktoś wymyślił skrót, który w zasadzie wygląda jak metoda bez nazwy.
Ponownie jest to wybór projektowy. Teraz eliksir nie jest zorientowany obiektowo i dlatego na pewno nie używa pierwszej formy. Nie mogę mówić za José, ale wygląda na to, że w eliksiru zastosowano drugą formę, ponieważ nadal wygląda jak wywołanie funkcji z dodatkową postacią. Jest wystarczająco blisko do wywołania funkcji.
Nie myślałem o wszystkich zaletach i wadach, ale wygląda na to, że w obu językach można uzyskać tylko nawiasy, pod warunkiem, że nawiasy są obowiązkowe dla funkcji anonimowych. Wygląda na to, że to:
Nawiasy obowiązkowe VS Nieznacznie inna notacja
W obu przypadkach robisz wyjątek, ponieważ oba zachowują się inaczej. Ponieważ istnieje różnica, równie dobrze możesz to uczynić oczywistym i wybrać inną notację. Obowiązkowe nawiasy klamrowe wyglądałyby naturalnie w większości przypadków, ale były bardzo mylące, gdy rzeczy nie potoczyły się zgodnie z planem.
Proszę bardzo. To może nie być najlepsze wytłumaczenie na świecie, ponieważ uprościłem większość szczegółów. Większość z nich to wybory projektowe i starałem się podać ich powód, nie oceniając ich. Uwielbiam eliksir, uwielbiam rubin, lubię wywołania funkcji bez nawiasów, ale podobnie jak ty, konsekwencje są czasem mylące.
A w eliksirze jest to tylko ta dodatkowa kropka, podczas gdy w rubinie masz na sobie bloki. Bloki są niesamowite i jestem zaskoczony, jak wiele możesz zrobić za pomocą samych bloków, ale działają one tylko wtedy, gdy potrzebujesz tylko jednej anonimowej funkcji, która jest ostatnim argumentem. Następnie, ponieważ powinieneś być w stanie poradzić sobie z innymi scenariuszami, oto cała metoda / zamieszanie / lambda / proc / block.
W każdym razie ... to jest poza zakresem.
źródło
Istnieje doskonały post na blogu na temat tego zachowania: link
Dwa rodzaje funkcji
Dot w dzwonieniu
fn
źródło
Eliksir ma opcjonalne nawiasy klamrowe dla funkcji, w tym funkcji o wartości 0. Zobaczmy przykład, dlaczego ważna jest osobna składnia wywoływania:
Nie czyniąc różnicy między 2 typami funkcji, nie możemy powiedzieć, co
Insanity.dive
oznacza: uzyskanie samej funkcji, wywołanie jej, czy też wywołanie wynikowej funkcji anonimowej.źródło
fn ->
składnia służy do korzystania z funkcji anonimowych. Wykonanie var. () Po prostu mówi eliksirowi, że chcę, abyś wziął ten var z func i uruchomił go zamiast odwoływać się do var jako czegoś, co po prostu pełni tę funkcję.Eliksir ma ten wspólny wzorzec, w którym zamiast logiki wewnątrz funkcji, aby zobaczyć, jak coś powinno się wykonać, wzorujemy dopasowywanie różnych funkcji w zależności od tego, jaki rodzaj danych wejściowych mamy. Zakładam, że właśnie dlatego odnosimy się do rzeczy ze względu na podobieństwo w
function_name/1
sensie .Dziwnie jest przyzwyczaić się do tworzenia skrótowych definicji funkcji (func (i 1) itp.), Ale jest to przydatne, gdy próbujesz potokować lub zachować zwięzłość kodu.
źródło
Możesz to zrobić
otherfunction(&myfunction/2)
Ponieważ eliksir może wykonywać funkcje bez nawiasów kwadratowych (jak
myfunction
), użycieotherfunction(myfunction))
go spróbuje wykonaćmyfunction/0
.Musisz więc użyć operatora przechwytywania i określić funkcję, w tym arity, ponieważ możesz mieć różne funkcje o tej samej nazwie. Tak więc
&myfunction/2
.źródło