İksir'de neden iki tür işlev vardır?


279

İksir öğreniyorum ve neden iki tür fonksiyon tanımı olduğunu merak ediyorum:

  • bir modülde tanımlanan fonksiyonlar defkullanılarak adlandırılır,myfunction(param1, param2)
  • anonim işlevler ile tanımlanmış fn,myfn.(param1, param2)

Sadece ikinci tür fonksiyon birinci sınıf bir nesne gibi görünmektedir ve diğer fonksiyonlara parametre olarak aktarılabilir. Bir modülde tanımlanan bir fonksiyonun a fn. otherfunction(myfunction(&1, &2))Bunu kolaylaştırmak için benzeyen sözdizimsel şeker var , ama neden ilk etapta gerekli? Neden sadece yapamıyoruz otherfunction(myfunction))? Yalnızca Ruby'deki gibi parantez olmadan çağrı modülü işlevlerine izin vermek mi? Bu özelliği modül fonksiyonları ve eğlenceleri de olan Erlang'dan miras almış gibi görünüyor, bu yüzden aslında Erlang VM'nin dahili olarak nasıl çalıştığıyla mı geliyor?

İki tür fonksiyona sahip olmanın ve bunları diğer fonksiyonlara geçirebilmek için bir türden diğerine dönüştürmenin herhangi bir faydası var mı? İşlevleri çağırmak için iki farklı gösterime sahip olmanın bir faydası var mı?

Yanıtlar:


386

Sadece isimlendirmeyi netleştirmek için her ikisi de işlevlerdir. Biri adlandırılmış bir işlev ve diğeri anonim bir işlevdir. Ama haklısın, biraz farklı çalışıyorlar ve neden böyle çalıştıklarını göstereceğim.

İkincisi ile başlayalım fn. fn, lambdaRuby'deki a'ya benzer bir kapaktır . Aşağıdaki gibi oluşturabiliriz:

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

Bir fonksiyonun birden fazla cümlesi olabilir:

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

Şimdi farklı bir şey deneyelim. Farklı sayıda argüman bekleyen farklı maddeleri tanımlamaya çalışalım:

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

Oh hayır! Bir hata alıyoruz! Farklı sayıda argüman bekleyen cümleleri karıştıramayız. Bir fonksiyon her zaman sabit bir düzene sahiptir.

Şimdi, adlandırılmış işlevler hakkında konuşalım:

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

Beklendiği gibi, bir isimleri var ve ayrıca bazı argümanlar da alabilirler. Ancak, bunlar kapanış değildir:

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

Her gördüğünüzde defboş bir değişken kapsamı aldığınız için bu kod derlenemez . Bunlar arasında önemli bir fark var. Özellikle her adlandırılmış işlevin temiz bir sayfa ile başlamasını ve farklı kapsamların değişkenlerini bir arada karıştırmamanızı seviyorum. Net bir sınırın var.

Yukarıda adı geçen merhaba işlevini anonim bir işlev olarak alabiliriz. Siz kendinizden bahsettiniz:

other_function(&hello(&1))

Ve sonra sordunuz, neden hellodiğer dillerde olduğu gibi iletemiyorum? Çünkü İksir'deki fonksiyonlar isim ve arity ile tanımlanır . Dolayısıyla, iki bağımsız değişken bekleyen bir işlev, aynı ada sahip olsalar bile, üç bağımsız değişkenten farklı bir işlevdir. Eğer basitçe geçersek hello, helloaslında kastettiğiniz hakkında hiçbir fikrimiz olmazdı . İki, üç veya dört argümanı olan? Bu, farklı varlıklara sahip maddelerle anonim bir işlev oluşturamamamızın tam olarak aynı nedenidir.

Elixir v0.10.1'den beri, adlandırılmış işlevleri yakalamak için bir sözdizimimiz var:

&hello/1

Bu, yerel adlandırılmış işlev merhaba 1 ile yakalanır. Dil ve belgeleri boyunca, bu hello/1sözdizimindeki işlevleri tanımlamak çok yaygındır .

Bu nedenle Elixir, anonim işlevleri çağırmak için bir nokta kullanır. Basitçe hellobir işlev olarak dolaşamadığınız için, bunun yerine açıkça yakalamanız gerekir, adlandırılmış ve anonim işlevler arasında doğal bir ayrım vardır ve her birini çağırmak için ayrı bir sözdizimi her şeyi biraz daha açık hale getirir (Lispers buna aşina olur) Lisp 1'e karşı Lisp 2 tartışması nedeniyle).

Genel olarak, iki fonksiyona sahip olmamızın nedenleri ve neden farklı davrandıkları bunlar.


30
Ben de İksir öğreniyorum ve karşılaştığım ilk duraklama bana verdi, bu konuda bir şey tutarsız görünüyordu. Büyük açıklama, ancak açık olmak gerekirse… bu bir uygulama sorununun sonucu mu, yoksa işlevlerin kullanımı ve geçişiyle ilgili daha derin bir bilgeliği mi yansıtıyor? Anonim işlevler, bağımsız değişken değerlerine dayalı olarak eşleşebildiğinden, bağımsız değişkenlerin sayısıyla da eşleşmenin yararlı olabileceği anlaşılmaktadır (ve başka bir yerde işlev modeli eşleşmesiyle tutarlı).
Siklon

17
Aynı zamanda f()(nokta olmadan) da çalışabileceği anlamında bir uygulama kısıtlaması değildir .
José Valim

6
is_function/2Bir nöbetçi kullanarak argüman sayısını eşleştirebilirsiniz . is_function(f, 2)2 arity vardır denetler. :)
José Valim

20
Hem adlandırılmış hem de anonim işlev için nokta olmadan işlev çağırmalarını tercih ederdim. Bazen kafa karıştırıcı olur ve belirli bir işlevin anonim veya adlandırılmış olduğunu unutursunuz. Körük söz konusu olduğunda daha fazla gürültü var.
CMCDragonkai

28
Erlang'da, anonim işlev çağrıları ve normal işlevler zaten sözdizimsel olarak farklıdır: SomeFun()ve some_fun(). İksir'de, noktayı kaldırırsak bunlar aynı olur some_fun()ve some_fun()değişkenler işlev adlarıyla aynı tanımlayıcıyı kullandığından. Dolayısıyla nokta.
José Valim

17

Bunun kimseye ne kadar yararlı olacağını bilmiyorum, ama sonunda kafamı kavramın etrafına sarma şeklim, iksir işlevlerinin İşlevler olmadığını anlamaktı.

İksirdeki her şey bir ifadedir. Yani

MyModule.my_function(foo) 

bir işlev değil, içindeki kod çalıştırılarak döndürülen ifade my_function. Aslında bir argüman olarak iletebileceğiniz bir "Fonksiyon" elde etmenin tek bir yolu vardır ve bu isimsiz fonksiyon gösterimini kullanmaktır.

İşlev işaretçisi olarak fn veya & notasyonuna başvurmak cazip gelebilir, ancak aslında çok daha fazlasıdır. Çevredeki ortamın kapanması.

Kendinize sorarsanız:

Bu noktada bir yürütme ortamına veya veri değerine ihtiyacım var mı?

Ve yürütme gerekiyorsa fn kullanın, o zaman zorlukların çoğu daha açık hale gelir.


2
Bunun MyModule.my_function(foo)bir ifade olduğu açıktır, ancak MyModule.my_function"olabilir" işlevi "nesne" döndüren bir ifade olabilir. Ama birliğe söylemeniz gerektiğinden, MyModule.my_function/1bunun gibi bir şeye ihtiyacınız olacak . Ve sanırım bunun &MyModule.my_function(&1) yerine bir ifadeyi kullanmanın daha iyi olduğuna karar verdiler (ve başka amaçlara hizmet ediyorlar). Henüz belli bir var neden ()adlandırılmış fonksiyonlar için operatör ve bir .()adsız fonksiyonları için operatör
RubenLaguna

İstediğinizi düşünüyorum: &MyModule.my_function/1bu bir işlev olarak onu nasıl iletebilirsiniz.
jnmandal

14

Bunun açıklamalarının neden bu kadar karmaşık olduğunu hiç anlamadım.

Gerçekten sadece Ruby tarzı "parens olmadan fonksiyon yürütme" gerçekleri ile birlikte son derece küçük bir ayrım.

Karşılaştırmak:

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

Kime:

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

Her ikisi de sadece tanımlayıcı olsa da ...

  • fun1ile tanımlanmış adlandırılmış bir işlevi açıklayan bir tanımlayıcıdır def.
  • fun2 değişkeni tanımlayan bir tanımlayıcıdır (işleve başvuru içerir).

Gördüğünüzde fun1veya fun2başka bir ifadede bunun ne anlama geldiğini düşünün. Bu ifadeyi değerlendirirken, başvurulan işlevi mi çağırıyorsunuz yoksa sadece bellekte olmayan bir değere mi başvuruyorsunuz?

Derleme zamanında bilmenin iyi bir yolu yoktur. Ruby, değişken bağlamanın belirli bir zamanda bir işlevi gölgelediğini öğrenmek için değişken ad alanını içselleştirme lüksüne sahiptir. İksir, derlenerek, bunu gerçekten yapamaz. Nokta gösteriminin yaptığı budur, Elixir'e bir işlev referansı içermesi ve çağrılması gerektiğini söyler.

Ve bu gerçekten zor. Nokta gösterimi olmadığını düşünün. Bu kodu düşünün:

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?

Yukarıdaki kod göz önüne alındığında, ben neden Elixir ipucu vermek zorunda oldukça açık olduğunu düşünüyorum. Her değişken referansının bir işlevi kontrol etmesi gerekip gerekmediğini düşünün. Alternatif olarak, değişken dereference'ın bir işlev kullandığını her zaman çıkarmak için hangi kahramanların gerekli olacağını hayal edin?


12

Kimseden bahsetmediğim için yanılmış olabilirim, ama bunun nedeninin aynı zamanda parantezsiz işlevleri çağırabilmenin yakut mirası olduğu izlenimindeydim.

Arity açıkça buna katılıyor, ancak bir süreliğine kenara bırakalım ve argüman olmadan fonksiyonları kullanalım. Köşeli parantezlerin zorunlu olduğu javascript gibi bir dilde, bir işlevi bağımsız değişken olarak geçirmek ile işlevi çağırmak arasında fark yaratmak kolaydır. Sadece parantez kullandığınızda çağırırsınız.

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

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

Gördüğünüz gibi, adlandırmak ya da adlandırmamak büyük bir fark yaratmıyor. Ancak iksir ve yakut, parantez olmadan işlevleri çağırmanıza izin verir. Bu, kişisel olarak sevdiğim bir tasarım seçimidir ancak bu yan etkiye sahiptir, sadece adı parantez olmadan kullanamazsınız, çünkü işlevi çağırmak istediğiniz anlamına gelebilir. Bunun için budur &. Arity appart'ı bir saniyeliğine bırakırsanız, işlev adınızı önceden eklemek, &bu işlevi döndürdüğü işlevi değil, bu işlevi açıkça bir bağımsız değişken olarak kullanmak istediğiniz anlamına gelir.

Artık anonim işlev, esas olarak bir argüman olarak kullanılması bakımından biraz farklıdır. Yine bu bir tasarım tercihi olmakla birlikte, bunun arkasındaki rasyonel, çoğunlukla yineleyiciler tarafından işlevleri argüman olarak alan işlevlerin kullanılmasıdır. Açıkçası kullanmanız gerekmez, &çünkü bunlar zaten varsayılan olarak argümanlar olarak kabul edilir. Amaçları bu.

Şimdi son sorun bazen onları kodunuzda çağırmanız gerektiğidir, çünkü bunlar her zaman bir yineleyici tür işleviyle kullanılmaz veya bir yineleyiciyi kendiniz kodluyor olabilirsiniz. Küçük hikaye için, yakut nesne yönelimli olduğundan, bunu yapmanın ana yolu call, nesneyi yöntemi kullanmaktı . Bu şekilde, zorunlu olmayan parantez davranışını tutarlı tutabilirsiniz.

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

Şimdi birisi temelde adı olmayan bir yönteme benzeyen bir kısayol buldu.

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

Yine, bu bir tasarım seçimidir. Şimdi iksir nesne yönelimli değildir ve bu nedenle ilk formu kesin olarak kullanmamayı çağırın. José için konuşamam ama ikinci form iksirde kullanılmış gibi görünüyor çünkü hala ekstra karakterli bir işlev çağrısı gibi görünüyor. Bir işlev çağrısına yeterince yakın.

Tüm artılarını ve eksilerini düşünmedim, ancak anonim işlevler için parantezleri zorunlu tuttuğunuz sürece her iki dilde de parantezlerden kurtulabilirsiniz. Öyle görünüyor:

Zorunlu parantezler VS Biraz farklı gösterim

Her iki durumda da bir istisna yaparsınız çünkü ikisinin de farklı davranmasını sağlarsınız. Bir fark olduğu için, bunu açık hale getirebilir ve farklı gösterime gidebilirsiniz. Zorunlu parantezler çoğu durumda doğal görünecek, ancak işler planlandığı gibi gitmediğinde çok kafa karıştırıcı olacaktır.

Hadi bakalım. Şimdi bu dünyanın en iyi açıklaması olmayabilir çünkü detayların çoğunu basitleştirdim. Ayrıca bunların çoğu tasarım seçimleridir ve onları yargılamadan onlara bir sebep vermeye çalıştım. İksiri seviyorum, yakutu seviyorum, fonksiyon çağrıları parantezsiz hoşuma gidiyor, ama sizin gibi, arada sırada sonuçları oldukça yanlış yönlendirici buluyorum.

Ve iksirde, sadece bu ekstra nokta, oysa yakutta bunun üstünde bloklar var. Bloklar şaşırtıcı ve sadece bloklarla ne kadar yapabileceğinize şaşırdım, ancak sadece son argüman olan anonim bir işleve ihtiyacınız olduğunda çalışırlar. O zaman diğer senaryolarla başa çıkabilmeniz gerektiğinden, tüm yöntem / lambda / proc / blok karışıklığı geliyor.

Her neyse ... bu kapsam dışı.


9

Bu davranış hakkında mükemmel bir blog yazısı var: link

İki tür işlev

Bir modül bunu içeriyorsa:

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

Bunu kesip kabuğa yapıştıramaz ve aynı sonucu elde edemezsiniz.

Erlang'da bir hata olduğu için. Erlang'daki modüller FORMS dizileridir . Erlang kabuğu bir dizi EXPRESSIONS değerini değerlendirir . Erlang'da FORMS İFADELER değildir .

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

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

İki aynı değildir. Bu sersemlik sonsuza dek Erlang oldu ama fark etmedik ve onunla yaşamayı öğrendik.

Arama sırasında nokta fn

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

Okulda f. (10) değil f (10) yazarak işlevleri çağırmayı öğrendim. dolaylı olarak f (10) olarak adlandırılmalıdır.

Eğer böyle bırakırsanız, hayatınızın önümüzdeki yirmi yılını nedenini açıklamayı bekleyebilirsiniz.


OP sorusunu doğrudan cevaplamanın yararlılığından emin değilim, ancak sağlanan bağlantı (yorumlar bölümü dahil) aslında sorunun hedef kitlesi için harika bir okuma IMO'su, yani Elixir + Erlang için yeni & öğrenenlerimiz .

Bağlantı şimdi öldü :(
AnilRedshift

2

İksir 0 fonksiyona sahip fonksiyonlar dahil olmak üzere fonksiyonlar için isteğe bağlı desteklere sahiptir. Ayrı bir çağrı sözdizimini neden önemli kıldığına bir örnek görelim:

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

2 tür işlev arasında bir fark yaratmadan, ne Insanity.diveanlama geldiğini söyleyemeyiz : bir işlevin kendisini almak, çağırmak veya sonuçta ortaya çıkan anonim işlevi çağırmak.


1

fn ->sözdizimi anonim işlevleri kullanmak içindir. Var. () Yapmak sadece iksiri anlatıyor ve bu fonksiyonu bir func ile almanızı ve var fonksiyonunu tutan bir şey olarak var etmek yerine onu çalıştırmanızı istiyorum.

İksir, bir şeyin nasıl yürütülmesi gerektiğini görmek için bir fonksiyonun içinde mantığa sahip olmak yerine, ne tür bir girdiye sahip olduğumuza bağlı olarak farklı fonksiyonlarla eşleştiğimiz bu ortak bir desene sahiptir. Sanırım bu yüzden, bu function_name/1anlamda bir şeyden, iktisattan söz ediyoruz .

Steno işlev tanımları (func (& 1), vb.) Yapmaya alışmak biraz garip, ancak kodunuzu kısaltmaya veya tutmaya çalışırken kullanışlıdır.


0

Sadece ikinci tür fonksiyon birinci sınıf bir nesne gibi görünmektedir ve diğer fonksiyonlara parametre olarak aktarılabilir. Modülde tanımlanan bir işlevin bir fn içine sarılması gerekir. otherfunction(myfunction(&1, &2))Bunu kolaylaştırmak için benzeyen sözdizimsel şeker var , ama neden ilk etapta gerekli? Neden sadece yapamıyoruz otherfunction(myfunction))?

Yapabilirsin otherfunction(&myfunction/2)

İksir fonksiyonlar parantez (örn. myfunction) Olmadan otherfunction(myfunction))çalışabildiğinden, çalıştırmayı dener myfunction/0.

Bu nedenle, aynı ada sahip farklı işlevlere sahip olabileceğiniz için, yakalama işlecini kullanmanız ve arity dahil işlevi belirtmeniz gerekir. Böylece &myfunction/2,.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.