Körlemenin avantajı nedir?


154

Körilerden yeni öğrendim ve kavramı anladığımı düşünürken, kullanımda büyük bir avantaj görmüyorum.

Önemsiz bir örnek olarak, iki değer ekleyen bir işlev kullanıyorum (ML ile yazılmıştır). Körlemesiz versiyon

fun add(x, y) = x + y

ve denir

add(3, 5)

köri versiyonu ise

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

ve denir

add 3 5

Bana sadece bir parantez kümesini, işlevi tanımlamaktan ve çağırmaktan çıkaran sözdizimsel bir şeker gibi geliyor. İşlevsel dillerin önemli özelliklerinden biri olarak listelenen kıvrılmayı gördüm ve şu anda biraz şaşırdım. Basit bir sözdizimi değişikliği için kullanmak için, her birini tek bir parametre kullanan bir işlev zinciri oluşturma kavramı, bir dizgeyi alan bir işlev yerine, oldukça karmaşık görünmektedir.

Biraz daha basit sözdizimi, körleme için tek motivasyon mu, yoksa çok basit bir örneğimde açık olmayan diğer bazı avantajları mı özlüyorum? Körleme sadece sözdizimsel şeker midir?


54
Tek başına körükleme aslında işe yaramaz, ancak tüm işlevlerin varsayılan olarak körüklenmesi, diğer birçok özelliği de kullanımı çok daha iyi hale getiriyor. Bir süredir işlevsel bir dil kullanana kadar bunu takdir etmek zor.
CA McCann

4
JoelEtherton'ın cevabı üzerine bir yorumda delnan'dan geçerken bahsettiğim bir şey, ama açıkça bahsettiğimi düşündüğüm, (en azından Haskell'de) kısmen sadece fonksiyonlarla değil, aynı zamanda kurucuları da uygulayabileceğinizdir. kullanışlı; Bu düşünülmesi gereken bir şey olabilir.
paul

Tüm Haskell örnekleri verdik. Köriler sadece Haskell'de işe yararsa acaba merak edilebilir.
Manoj R

@ManojR Hepsi Haskell'de örnek vermedi .
phwd

1
Bu soru Reddit hakkında oldukça ilginç bir tartışma yarattı .
yannis

Yanıtlar:


126

Kıvrımlı fonksiyonlar sayesinde, uzmanlaşmaya başladığınızdan daha soyut fonksiyonların tekrar kullanımı daha kolay olacaktır. Diyelim ki bir ek işleviniz var.

add x y = x + y

ve bir listenin her üyesine 2 eklemek istediğinizi Haskell'de bunu yapardınız:

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

Burada sözdizimi, bir işlev oluşturmak zorunda kaldığınızdan daha hafiftir add2

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

veya anonim bir lambda işlevi yapmak zorundaysanız:

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

Ayrıca farklı uygulamalardan uzaklaşmanıza da olanak tanır. İki arama fonksiyonunuz olduğunu varsayalım. Biri bir anahtar / değer çiftleri listesinden ve bir değere bir anahtar ve diğer bir haritadan bir anahtardan bir değere ve bir anahtardan bir değere, örneğin:

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

Ardından, bir arama işlevini kabul eden bir işlevi Anahtar'dan Değer'e yapabilirsiniz. Kısmen sırasıyla bir liste veya harita ile uygulanan yukarıdaki arama işlevlerinden herhangi birini geçebilirsiniz:

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

Sonuç olarak: Körleme iyidir, çünkü hafif bir sözdizimi kullanarak işlevleri uzmanlaştırmanıza / kısmen uygulamanıza izin verir ve daha sonra bu kısmen uygulanan fonksiyonları mapveya gibi yüksek dereceli fonksiyonlara geçirir filter. Daha yüksek dereceli işlevler (işlev olarak parametre alan veya sonuç olarak veren işlevler), işlevsel programlamanın ekmeği ve tereyağıdır ve körleme ve kısmen uygulanan işlevler, daha yüksek dereceli işlevlerin çok daha etkili ve kesin olarak kullanılmasını sağlar.


31
Bu nedenle Haskell'deki fonksiyonlar için kullanılan argüman sırasının çoğunlukla kısmi uygulamanın ne kadar muhtemel olduğuna bağlı olduğunu belirtmek gerekir; Bu nedenle, varsayılan olarak kurutma, buradakiler gibi spesifik örneklerden açıkça görülenden daha faydalı olması ile sonuçlanmaktadır.
CA McCann

wat. "Biri bir anahtar / değer çiftleri listesinden bir anahtar ve bir değere ve diğer bir haritadan bir anahtardan bir değere ve bir değerden bir anahtar" "
Mateen Ulhaq

@MateenUlhaq Bu, bir anahtara dayanarak bir değer elde etmek istediğimizi ve bunun iki yolunun olduğunu düşündüğüm önceki cümlenin bir devamıdır. Cümle bu iki yolu sıralar. İlk olarak, size bir anahtar / değer çiftleri listesi ve değeri bulmak istediğimiz bir anahtar verilirken, diğer taraftan bize uygun bir harita ve tekrar bir anahtar verilir. Cümleyi hemen izleyen koda bakmak yardımcı olabilir.
Boris

53

Pratik cevap, körlemenin adsız işlevler oluşturmayı çok daha kolay hale getirdiğidir. Minimal lambda sözdiziminde bile, kazanılan bir şeydir; karşılaştırmak:

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

Çirkin bir lambda sözdiziminiz varsa, daha da kötüsü. (Sana bakıyorum, JavaScript, Şema ve Python.)

Gittikçe daha yüksek dereceli fonksiyonlar kullandıkça, bu giderek daha kullanışlı hale gelir. Ben kullanırken daha başka dillerde daha Haskell yüksek mertebeden fonksiyonlar, ben aslında lambda sözdizimini kullanın buldum az zamanın üçte ikisi gibi bir şey, lambda sadece kısmen uygulanan fonksiyon olacağından. (Ve diğer zamanların çoğunu adlandırılmış bir işleve çıkarırım.)

Daha temel olarak, bir fonksiyonun hangi versiyonunun "kanonik" olduğu her zaman açık değildir. Örneğin, al map. Türü mapiki şekilde yazılabilir:

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

Hangisi "doğru" olan? Söylemesi zor aslında. Uygulamada, çoğu dil ilkini kullanır - harita bir işlev ve bir liste alır ve bir liste döndürür. Bununla birlikte, temel olarak, haritanın gerçekte yaptığı şey, işlevleri listelemek için normal işlevleri eşlemektir - bir işlevi alır ve bir işlevi döndürür. Harita curried ise, bu soruyu cevaplamanız gerekmez: her ikisini de çok şık bir şekilde yapar.

Bu, maplisteden başka türlere genelleştirdiğinizde özellikle önemlidir .

Ayrıca, kurutma gerçekten çok karmaşık değil. Aslında, çoğu dilin kullandığı model üzerinde bir sadeleştirme biraz: dilinize eklenmiş çoklu argümanların fonksiyon kavramına ihtiyacınız yok. Bu aynı zamanda altta yatan lambda matematiğini daha yakından yansıtır.

Tabii ki, ML tarzı diller, kavisli veya işlenmemiş formda çoklu argüman kavramına sahip değildir. f(a, b, c)Sözdizimi aslında demet içinde geçen tekabül (a, b, c)içine f, bu yüzden fhala sadece argüman alır. Bu aslında diğer dillerin sahip olmasını diliyorum, çünkü şöyle bir şey yazmayı çok doğal kılıyor:

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

Bunu, içine eklenmiş birden fazla argüman fikri olan dillerle kolayca yapamazsınız!


1
"ML tarzı diller, kurutulmuş veya kurutulmuş halde çoklu argüman kavramına sahip değil": bu açıdan Haskell ML tarzı mı?
Giorgio

1
@ Giorgio: Evet.
Tikhon Jelvis

1
İlginç. Bazı Haskell'i biliyorum ve şu anda SML öğreniyorum, bu nedenle iki dil arasındaki farklılıkları ve benzerlikleri görmek ilginç.
Giorgio

Harika cevap, ve eğer hala ikna
olmuyorsanız

"Pratik" cevap pek de önemli değildir, çünkü kısırlık genellikle körleştirme değil, kısmi uygulama ile önlenir . Ve burada iddia ediyorum, lambda soyutlamasının sözdiziminin (tür bildirgesine rağmen), dil düzenini doğru bir şekilde ayrıştırmak için daha yerleşik özel sözdizimsel kurallara ihtiyaç duyduğundan, (en azından), şema üzerinde olduğundan daha çirkin olduğunu iddia ediyorum. anlamsal özellikler hakkında.
FrankHB

24

Birinci sınıf nesne olarak geçirdiğiniz bir işleve sahipseniz ve onu kodda tek bir yerde değerlendirmek için gereken tüm parametreleri alamıyorsanız, körleme yararlı olabilir. Bunları aldığınızda basitçe bir veya daha fazla parametre uygulayabilir ve sonucu daha fazla parametreye sahip başka bir kod parçasına iletebilir ve orada değerlendirmeyi bitirebilirsiniz.

Bunu başarmanın kodu, önce tüm parametreleri bir araya getirmeniz gerekip gerekmediğinden daha basit olacaktır.

Ayrıca, tek bir parametreyi (başka bir curried işlevi) alan işlevlerin tüm parametrelerle özel olarak eşleşmesi gerekmediğinden daha fazla kod yeniden kullanımı olasılığı vardır.


14

Körleme için temel motivasyon (en azından başlangıçta) pratik değil teoriktir. Özellikle, körleme, onlar için anlambilim tanımlamak veya ürünler için anlambilim tanımlamaksızın çok argüman işlevlerini etkili bir şekilde elde etmenizi sağlar. Bu, başka, daha karmaşık bir dil kadar anlamlı olan daha basit bir dile neden olur ve bu da istenirdir.


2
Buradaki motivasyon teorik olsa da, sadeliğin hemen hemen her zaman pratik bir avantaj olduğunu düşünüyorum. Çok değişkenli fonksiyonlar hakkında endişelenmemek, program yaparken anlamsızlıkla çalışıyor olsaydım hayatımı kolaylaştırıyor.
Tikhon Jelvis

2
@TikhonJelvis Yine de programlama yaparken, körleme size derleyici hakkında endişelenecek başka şeyler verir; Currying kullanmadığınızda, hata çok daha belirgindir.
Alex R

Böyle problemlerim hiç olmadı: GHC, en azından, bu konuda çok iyidir. Derleyici her zaman bu tür bir konuyu yakalar ve bu hata için de iyi hata iletilerine sahiptir.
Tikhon Jelvis

1
Hata mesajlarının iyi olduğu konusunda hemfikir değilim. Servis verilebilir, evet, ancak henüz iyi değiller. Ayrıca yalnızca bir tür hatayla sonuçlanırsa, bu tür bir sorunu yakalar, yani sonucu daha sonra bir işlevden başka bir şey olarak kullanmaya çalışırsanız (veya açıklamalı olarak yazıyorsanız, ancak okunabilir hatalar için buna güvenmenin kendi sorunları vardır) ); hatanın bildirilen yeri, gerçek konumundan boşanır.
Alex R

14

(Haskell'de örnekler vereceğim.)

  1. İşlevsel dilleri kullanırken, kısmen bir işlevi uygulayabilmeniz çok kolaydır. Haskell'inki gibi , argümanı verilen bir terime eşitse (== x)dönen bir fonksiyondur :Truex

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

    körelmeden, daha az okunabilir kodumuz olur:

    mem x lst = any (\y -> y == x) lst
    
  2. Bu Tacit programlaması ile ilgilidir (ayrıca bkz . Haskell wiki'deki Pointfree tarzı ). Bu stil değişkenler tarafından temsil edilen değerlere değil, fonksiyonların bir araya getirilmesine ve bilgilerin bir fonksiyonlar zincirinde nasıl aktığına odaklanır. Örneğimizi, değişkenleri hiç kullanmayan bir formata dönüştürebiliriz:

    mem = any . (==)
    

    Burada görmek ==bir fonksiyonu olarak aiçin a -> Boolve anybir fonksiyonu olarak a -> Booliçin [a] -> Bool. Basitçe onları besteleyerek sonucu elde ederiz. Bunların hepsi, kurutma için teşekkürler.

  3. Tersine, körlemeyen, bazı durumlarda da yararlıdır. Örneğin, bir listeyi iki bölüme ayırmak istediğimizi varsayalım - 10'dan küçük ve geri kalan öğeler ve sonra bu iki listeyi birleştirin. Listenin ayrılması ile yapılır (burada ayrıca curried kullanırız ). Sonuç türü . Bunun yerine, ilk ve ikinci kısmına sonucunu çıkarma ve bunları kullanarak birleştirerek , doğrudan uncurrying yapabilirsiniz olarakpartition (< 10)<([Int],[Int])++++

    uncurry (++) . partition (< 10)
    

Aslında, (uncurry (++) . partition (< 10)) [4,12,11,1]değerlendirir [4,1,12,11].

Önemli teorik avantajlar da vardır:

  1. Körleme veri türü olmayan ve yalnızca lambda matematiği gibi işlevleri olan diller için gereklidir . Bu diller pratik kullanım için kullanışlı olmasa da, teorik açıdan çok önemlidir.
  2. Bu, işlevsel dillerin temel özelliği ile bağlantılıdır - fonksiyonlar birinci sınıf nesnedir. Gördüğümüz gibi, (a, b) -> cila dan dönüşüm a -> (b -> c), ikinci fonksiyonun sonucunun tür olduğu anlamına gelir b -> c. Başka bir deyişle, sonuç bir fonksiyondur.
  3. (Un) kurutma, tiplenmiş lambda taşının görüntülenmesi için kategorik bir yol olan kartezyen kapalı kategorilerine yakından bağlıdır .

"Çok daha az okunabilen kod" biti, öyle olmalı mem x lst = any (\y -> y == x) lstmı? (Bir ters eğik çizgi ile).
stusmith

Evet, bunu belirttiğiniz için teşekkürler, düzelteceğim.
Petr Pudlák

9

Körleme sadece sözdizimsel şeker değil!

add1(Sertleşmemiş) ve add2( sertleşmiş) türlerinin imzasını düşünün :

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

(Her iki durumda da, imza türündeki parantezler isteğe bağlıdır, ancak açık olması için bunları ekledim.)

add1bir 2-tuple alan bir fonksiyonudur intve intbir döner int. add2Bir alan bir fonksiyondur intve döndüren başka bir fonksiyon da bir sürer intve bir döner int.

İkisi arasındaki temel fark, işlev uygulamasını açıkça belirttiğimizde daha belirgin hale gelir. İlk argümanını ikinci argümanına uygulayan bir işlevi tanımlayalım (curried):

apply(f, b) = f b

Şimdi fark arasındaki görebilirsiniz add1ve add2daha net. add12 tuple ile çağrılır:

apply(add1, (3, 5))

ancak bir add2ile çağrılır int ve sonra dönüş değeri bir başkasıyla çağrılırint :

apply(apply(add2, 3), 5)

EDIT: Körlemenin temel yararı, ücretsiz olarak kısmi başvuru almanızdır. Diyelim int -> intki, mapparametresine 5 ekleyen bir tür işlev ( listedeki listeye söyleyin) istediğinizi söyleyelim . Yazabilir addFiveToParam x = x+5veya eşdeğerini bir satır içi lambda ile yapabilirsiniz, ancak çok daha kolay (özellikle bundan daha az önemsiz durumlarda) yazabilirsiniz add2 5!


3
Örneğim için perde arkasında büyük bir farklılıklar olduğunu anlıyorum, ancak sonuç basit bir sözdizimsel değişim gibi görünüyor.
Mad Scientist

5
Currying çok derin bir kavram değil. Altta yatan modeli basitleştirmekle ilgilidir (bkz. Lambda Calculus) ya da zaten tekilleri olan dillerde, kısmi uygulamanın sözdizimsel rahatlığı hakkındadır. Sözdizimsel uygunluğun önemini küçümseme.
Peaker

9

Körleme sadece sözdizimsel bir şeker, ama şekerin ne yaptığını biraz yanlış anlıyorsunuz sanırım. Örnek alarak,

fun add x y = x + y

aslında sözdizimsel şeker

fun add x = fn y => x + y

Yani, (x ekle), y değişkenini alan bir işlev döndürür ve x'e y ekler.

fun addTuple (x, y) = x + y

Bu bir demet alır ve elemanlarını ekleyen bir fonksiyondur. Bu iki işlev aslında oldukça farklıdır; farklı argümanlar alırlar.

Listedeki tüm numaralara 2 eklemek istiyorsanız:

(* 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]

Sonuç olurdu [3,4,5].

Her bir listeyi bir listedeki toplamak istiyorsanız, diğer taraftan, addTuple işlevi mükemmel bir uyum sergiliyor.

(* 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)]

Sonuç olurdu [12,13,14].

Kıvrımlı işlevler kısmi uygulamanın yararlı olduğu yerlerde harikadır - örneğin harita, kat, uygulama, filtre. Verilen listedeki en büyük pozitif sayıyı döndüren bu işlevi veya pozitif sayı yoksa 0'ı düşünün:

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

1
Körili işlevin farklı bir imzanın olduğunu ve aslında başka bir işlevi döndüren bir işlev olduğunu anladım. Kısmi başvuru kısmını yine de kaçırıyordum.
Mad Scientist

9

Henüz bahsetmediğim bir diğer şey ise, körlemenin ariteye soyutlama yapmasına (sınırlı) izin vermesidir.

Haskell'in kütüphanesinin bir parçası olan bu işlevleri göz önünde bulundurun

(.) :: (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

Her durumda, tür değişkeni cbir işlev türü olabilir, böylece bu işlevler bağımsız değişkenlerinin parametre listesinin bir ön ekinde çalışır. Kıvrılma olmadan, işlev ariteyi soyutlamak için özel bir dil özelliğine ihtiyacınız olabilir veya bu işlevlerin farklı işlevler için özelleşmiş birçok versiyonuna sahip olabilirsiniz.


6

Sınırlı anlayışım şöyle:

1) Kısmi Fonksiyon Uygulaması

Kısmi İşlev Uygulaması , daha az sayıda argüman alan bir işlev döndürme işlemidir. 3 argümandan 2'sini verirseniz, 3-2 = 1 argümanını alan bir işlev döndürür. 3 argümandan 1 tanesini verirseniz, 3-1 = 2 argümanını alan bir işlev döndürür. İsterseniz, 3 argümandan 3'ünü bile kısmen uygulayabilirsiniz ve argüman almayan bir işlev döndürür.

Yani aşağıdaki işlevi verilen:

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

1'i x'e bağladığınızda ve bunu yukarıdaki fonksiyona kısmen uyguladığınızda f(x,y,z):

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

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

Şimdi, y'yi 2'ye ve z'yi 3'e bağlamak ve kısmen uygulamak f'(y,z)olsaydı , şunları elde edersiniz:

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

Nerede f''() = 1 + 2 + 3:;

Şimdi herhangi bir noktada f, f'veya seçebilirsiniz f''. Böylece yapabilirim:

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

veya

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

2) Currying

Öte yandan, Currying , bir fonksiyonu bir argüman fonksiyonunun iç içe geçmiş bir zincirine bölme işlemidir. Asla 1'den fazla argüman sağlayamazsınız, bir veya sıfırdır.

Yani aynı işlevi verilen:

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

Körlenmiş olursanız, 3 fonksiyondan oluşan bir zincir alırsınız:

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

Nerede:

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

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

f'''(z) = z;

Eğer ararsanız Şimdi f'(x)ile x = 1:

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

Size yeni bir işlev döndürülür:

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

Eğer ararsanız g(y)ile y = 2:

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

Size yeni bir işlev döndürülür:

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

Aradığınızda Nihayet eğer h(z)ile z = 3:

h(3) = 1 + 2 + 3;

Sen döndürülür 6.

3) Kapanış

Son olarak, Kapatma , bir fonksiyonu ve verileri tek bir ünite olarak bir araya getirme işlemidir. Bir işlev kapatması sonsuz sayıda argüman almak için 0 alabilir, ancak aynı zamanda kendisine iletilmeyen verilerin de farkındadır.

Yine, aynı işlevi verilen:

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

Bunun yerine bir kapatma yazabilirsiniz:

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

Nerede:

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

f'kapatıldı x. f'İçindeki x değerini okuyabilen anlam f.

Yani eğer aramak idi file x = 1:

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

Bir kapanış olur:

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

Şimdi ve closureOfFile y = 2aradıysanız z = 3:

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

Hangisi dönecekti 6

Sonuç

Körleme, kısmi uygulama ve kapaklar, bir işlevi daha fazla parçaya ayırmalarından dolayı benzerdir.

Currying, çoklu argümanların bir fonksiyonunu, tek argümanların fonksiyonlarını döndüren tek argümanların iç içe geçmiş fonksiyonlarına ayırır. Bir ya da daha az tartışmanın bir fonksiyonunu köretmenin anlamı yoktur, çünkü mantıklı değildir.

Kısmi uygulama, çoklu argümanların bir fonksiyonunu şimdi eksik argümanları verilen değer ile değiştirilen daha az argümanların bir fonksiyonuna ayrıştırır.

Kapatma, bir işlevi bir işleve ayırır, işlevin içindeki değişkenlerin girilmediği durumlarda değerlendirmek istendiğinde bağlanacak bir değer bulmak için veri kümesinin içine bakabilir.

Tüm bunlar hakkında kafa karıştırıcı olan şey, her birinin diğerlerinin bir alt kümesini uygulamak için kullanılabileceğidir. Yani özünde, hepsi bir uygulama detayı. Hepsi aynı değerleri sağlar; çünkü tüm değerleri önceden toplamanıza gerek yoktur ve işlevin bir bölümünü yeniden kullanabilmeniz gerekir, çünkü bunu gizli birimlere ayırırsınız.

ifşa

Ben konunun uzmanı değilim, sadece son zamanlarda bunları öğrenmeye başladım ve bu yüzden şu anki anlayışımı sağlıyorum, ancak sizi belirtmeye davet ettiğim hatalar olabilir ve / if gibi düzelteceğim Ben herhangi birini keşfederim.


1
Öyleyse cevap: Körlemenin avantajı yok mu?
16'da

1
@ceving Bildiğim kadarıyla, bu doğru. Uygulamada körleme ve kısmi uygulamada size aynı faydalar sağlanacaktır. Bir dilde uygulama seçimi, uygulama nedenleriyle yapılır, biri belirli bir dil verildiğinde bir başkasının uygulanması daha kolay olabilir.
Didier A.,

5

Kıvrılma (kısmi uygulama), bazı parametreleri düzelterek mevcut bir işlevden yeni bir işlev oluşturmanıza olanak sağlar. Bu, anonim fonksiyonun sadece yakalanan argümanları başka bir işleve ileten önemsiz bir sarıcı olduğu özel bir sözcüksel kapanma durumudur. Bunu, sözcük kapanışları yapmak için genel sözdizimini kullanarak da yapabiliriz, ancak kısmi uygulama basitleştirilmiş bir sözdizimsel şeker sağlar.

Lisp programcılarının işlevsel bir tarzda çalışırken, bazen kısmi uygulamalar için kütüphaneler kullanmasının nedeni budur .

Bunun yerine, (lambda (x) (+ 3 x))bize argümanına 3 ekleyen bir işlev verir, onun gibi bir şey yazabilirsiniz (op + 3)ve böylece bir listenin her elemanına 3 eklemek o zamandan (mapcar (op + 3) some-list)ziyade olur (mapcar (lambda (x) (+ 3 x)) some-list). Bu opmakro sizi bazı argümanlar alan x y z ...ve çağıran bir işlev yapacaktır (+ a x y z ...).

Tamamen işlevsel olan birçok dilde, kısmi uygulama sözdizimine gömülüdür, böylece opoperatör yoktur . Kısmi uygulamayı tetiklemek için, gerekenden daha az argüman içeren bir işlevi çağırmanız yeterlidir. Bir "insufficient number of arguments"hata üretmek yerine , sonuç kalan argümanların bir fonksiyonudur.


"Currying ... bazı parametreleri düzelterek yeni bir işlev oluşturmanıza olanak sağlar" - hayır, bir tür işlev sa -> b -> c parametresine sahip değil (çoğul), sadece bir parametreye sahip . Çağrıldığında, bir tür işlevi döndürür . ca -> b
Max Heiber

4

İşlev için

fun add(x, y) = x + y

Biçimindedir f': 'a * 'b -> 'c

Birini değerlendirmek

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

Körili fonksiyon için

fun add x y = x + y

Birini değerlendirmek

add 3
val it = fn : int -> int

Kısmi bir hesaplamanın olduğu yerde, özellikle (3 + y);

it 5
val it = 8 : int

ikinci durumda ekle şeklindedir f: 'a -> 'b -> 'c

Burada körelme, iki anlaşmayı alan bir işlevi, yalnızca bir sonucu döndüren bir işe dönüştürmektir. Kısmi değerlendirme

Buna neden ihtiyaç duyulur?

RHS’de söyle x, normal bir int değil, bunun yerine, artırma, hatırlama için iki saniye süren karmaşık bir hesaplama.

x = twoSecondsComputation(z)

Yani fonksiyon şimdi benziyor

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

Türü add : int * int -> int

Şimdi bu fonksiyonu bir sayı aralığı için hesaplamak istiyoruz.

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

Yukarıdakiler için sonucu twoSecondsComputationher seferinde değerlendirilir. Bu, bu hesaplama için 6 saniye sürüyor demektir.

Kademelendirme ve kurutma işleminin bir kombinasyonunu kullanmak bunu önleyebilir.

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

Körili formdan add : int -> int -> int

Şimdi biri yapabilir

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

twoSecondsComputationSadece ihtiyaçlar kez değerlendirmeye tabi tutulmuş. Ölçeği yükseltmek için, iki saniyeyi 15 dakika veya herhangi bir saat ile değiştirin, ardından 100 sayıya karşı bir harita verin.

Özet : Kısmi bir değerlendirme aracı olarak üst düzey işlevler için diğer yöntemlerle birlikte kullanıldığında, körleme çok iyidir. Amacı gerçekten kendisi tarafından gösterilemez.


3

Kıvrılma esnek fonksiyon bileşimini sağlar.

"Köri" işlevini yaptım. Bu bağlamda ne tür bir kayıt cihazı aldığım veya nereden geldiği umurumda değil. Eylemin ne olduğu veya nereden geldiği umurumda değil. Tek umursadığım tek şey girdiyi işlemektir.

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.

Builder değişkeni, çalışmamı yapan girişimi alan bir işlev döndüren bir işlev döndüren bir işlevdir. Bu basit bir kullanışlı örnek ve görünüşte bir nesne değil.


2

Currying, bir işlev için tüm argümanlara sahip olmadığınızda bir avantajdır. Fonksiyonu tam olarak değerlendiriyorsanız, önemli bir fark yoktur.

Kıvrılma, henüz gerekli olmayan parametrelerden bahsetmekten kaçınmanızı sağlar. Daha özlüdür ve kapsamdaki başka bir değişkenle çarpışmayan bir parametre adı bulmayı gerektirmez (en sevdiğim yararı budur).

Örneğin, işlevleri argüman olarak alan işlevleri kullanırken, genellikle "girişi 3 eklemek" veya "girişi değişken v ile karşılaştırmak" gibi işlevlere ihtiyaç duyduğunuz durumlarda kendinizi bulacaksınız. Kıvrılma ile bu fonksiyonlar kolayca yazılabilir: add 3ve (== v). Kıvrılma olmadan, lambda ifadeleri kullanmanız gerekir: x => add 3 xve x => x == v. Lambda ifadeleri iki katı uzunluğundadır ve xhalihazırda xkapsam dahilinde olup olmadığının yanı sıra bir isim seçmekle ilgili az miktarda yoğun bir çalışmaya da sahiptir .

Körete dayalı dillerin bir yan yararı, işlevler için genel kod yazarken, parametre sayısına bağlı olarak yüzlerce çeşitlemeyle bitmeyeceğinizdir. Örneğin, C # 'da bir' köri 'yönteminin Func <R>, Func <A, R>, Func <A1, A2, R>, Func <A1, A2, A3, R> vb. İçin değişkenlere ihtiyacı olacaktır. sonsuza dek. Haskell'de, bir Func <A1, A2, R> eşdeğeri bir Func <Tuple <A1, A2>, R> veya bir Func <A1, Func <A2, R >> (ve bir Func <R>) gibidir. daha çok bir Func <Unit, R>) gibidir, bu nedenle tüm değişkenler tek Func <A, R> durumuna karşılık gelir.


2

Düşünebildiğim temel akıl yürütme (ve bu konuda herhangi bir şekilde uzman değilim), işlevler önemsizden önemsizliğe geçerken faydalarını göstermeye başlar. Bu doğanın çoğu konseptine sahip olan tüm önemsiz durumlarda, gerçek bir fayda bulamazsınız. Ancak, çoğu işlevsel dil, işlemlerde yığının yoğun şekilde kullanılmasını sağlar. PostScript veya Lisp'i bunun örnekleri olarak düşünün . Körlemeden faydalanarak işlevler daha etkin bir şekilde istiflenebilir ve bu fayda, işlemler gittikçe azaldıkça belirginleştikçe belirginleşir. Kavisli bir şekilde, komut ve argümanlar yığına sırayla atılabilir ve uygun sırayla çalıştırılması için gerektiği şekilde çıkarılabilir.


1
Daha fazla yığın çerçevesi oluşturulması için tam olarak ne gerekiyorsa, işleri daha verimli hale nasıl getirebilir?
Mason Wheeler

1
@MasonWheeler: Bilmiyorum çünkü dediğim gibi özellikle fonksiyonel diller veya köriler konusunda uzman değilim. Bu topluluk wiki'sini özellikle bu yüzden etiketledim.
Joel Etherton

4
@MasonWheeler Bu cevabın ifadesine göre bir noktanız var, fakat içeri girip bana oluşturulan yığın çerçeve miktarının uygulamaya bağlı olduğunu söylememe izin verin. Örneğin, spinless etiketsiz G makinesinde (STG; GHC Haskell'i uygular), tüm (veya en azından gerekli olduğunu bildiği kadar) argümanlarını toplayana kadar gerçek değerlendirmeyi geciktirir. Bunun tüm işlevler için mi yoksa yalnızca inşaatçılar için mi yapıldığını hatırlayamıyorum, ancak çoğu işlev için mümkün olması gerektiğini düşünüyorum . (Sonra tekrar, "yığın çerçeveleri" kavramı gerçekten STG için geçerli değildir.)

1

Körleme, bir fonksiyonun geri dönme kabiliyetine önemli derecede (kesinlikle eşit) bağlıdır.

Bu (kabul edilen) sözde kodu düşünün.

var f = (m, x, b) => ... bir şey döndür ...

F işlevinin üç argümandan az olan bir işlev döndürdüğünü belirtelim.

var g = f (0, 1); // bu, 0 ve 1 (m ve x) 'e bağlanan ve bir tane daha argüman (b) kabul eden bir fonksiyon döndürür.

var y = g (42); // eksik üçüncü argümanla g'yi çağırın, m ve x için 0 ve 1'i kullanın

Argümanları kısmen uygulayabilir ve yeniden kullanılabilir bir fonksiyon (geri aldığınız argümanlara bağlı olarak) geri alabilmeniz oldukça yararlıdır (ve DRY).

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.