Scala currying ve kısmen uygulanan fonksiyonlar


82

Burada currying ve kısmen uygulanan fonksiyonların ne olduğuna dair birkaç soru olduğunun farkındayım , ancak bunların ne kadar farklı olduğunu soruyorum. Basit bir örnek olarak, burada çift sayıları bulmak için curried bir işlev verilmiştir:

def filter(xs: List[Int], p: Int => Boolean): List[Int] =
   if (xs.isEmpty) xs
   else if (p(xs.head)) xs.head :: filter(xs.tail, p)
   else filter(xs.tail, p)

def modN(n: Int)(x: Int) = ((x % n) == 0)

Yani bunu kullanmak için aşağıdakileri yazabilirsiniz:

val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))

Hangi döner: List(2,4,6,8). Ama aynı şeyi şu şekilde yapabileceğimi buldum:

def modN(n: Int, x: Int) = ((x % n) == 0)

val p = modN(2, _: Int)
println(filter(nums, p))

bu da döner: List(2,4,6,8).

Öyleyse sorum şu, ikisi arasındaki temel fark nedir ve birini diğerine ne zaman kullanırsınız? Bu, neden birinin diğerine göre kullanılacağını göstermek için çok basit bir örnek mi?


Kısmen uygulandığında, curried ve curried olmayan sürümü bellekte temsil etmenin maliyetleri farklı olabilir , bu nedenle çalışma zamanı performansı da etkilenebilir. (Yani, eğer optimize edici her iki durumda da optimum gösterimi seçecek kadar akıllı değilse.) Yine de, tam farkların ne olduğunu söyleyecek kadar Scala'ya aşina değilim.


Bu açıklamayı çok yararlı buldum, kısmi işlevler, kısmen uygulanan işlevler ve körleme hakkında hepsini tek bir gönderide açıklıyor: stackoverflow.com/a/8650639/1287554
Plasty Grove

Mükemmel bağlantı @PlastyGrove. Teşekkür ederim!
Eric

Ve bağlantınız için @ Utaal'a teşekkür ederim. Martin Odersky'nin kendisinden gelen herhangi bir cevap çok değerli. Sanırım bu kavramlar artık tıklanmaya başlıyor.
Eric

Yanıtlar:


88

Plasty Grove tarafından bağlanılan cevapta semantik farklılık oldukça iyi açıklanmıştır .

İşlevsellik açısından, yine de pek bir fark görünmüyor. Bunu doğrulamak için bazı örneklere bakalım. İlk olarak, normal bir işlev:

scala> def modN(n: Int, x: Int): Boolean = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>

Böylece <function1>, an alan bir kısmen uygulanmış Intoluyoruz, çünkü ona zaten ilk tamsayıyı verdik. Çok uzak çok iyi. Şimdi köriye:

scala> def modNCurried(n: Int)(x: Int): Boolean = ((x % n) == 0)

Bu gösterimle, safça aşağıdakilerin işe yaramasını beklersiniz:

scala> modNCurried(5)
<console>:9: error: missing arguments for method modN;
follow this method with `_' if you want to treat it as a partially applied function
          modNCurried(5)

Dolayısıyla, çoklu parametreli liste gösterimi gerçekten hemen curried bir işlev yaratmıyor gibi görünüyor (varsayım gereği gereksiz ek yükten kaçınmak için), ancak curried yapmak istediğinizi açıkça belirtmenizi bekler (gösterimin başka avantajları da vardır):

scala> modNCurried(5) _
res24: Int => Boolean = <function1>

Bu, daha önce sahip olduğumuz şeyin tam olarak aynısı, yani burada gösterim dışında hiçbir fark yok. Başka bir örnek:

scala> modN _
res35: (Int, Int) => Boolean = <function2>

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>

Bu, "normal" bir işlevin kısmen uygulanmasının tüm parametreleri alan bir işlevle sonuçlandığını gösterirken, birden çok parametre listesiyle bir işlevi kısmen uygulamak, her parametre listesi için bir işlev zinciri oluşturan ve tümü yeni bir işlev döndüren bir işlev zinciri oluşturduğunu gösterir :

scala> def foo(a:Int, b:Int)(x:Int)(y:Int): Int = a * b + x - y
scala> foo _
res42: (Int, Int) => Int => (Int => Int) = <function2>

scala> res42(5)
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2.
Unspecified value parameter v2.

Gördüğünüz gibi, ilk parametre listesi fooiki parametreye sahip olduğu için, kıvrımlı zincirdeki ilk işlevin iki parametresi vardır.


Özetle, kısmen uygulanan işlevler, işlevsellik açısından gerçekten farklı biçimlerde kıvrımlı işlevler değildir. Bu, herhangi bir işlevi curried olana dönüştürebileceğiniz için kolayca doğrulanabilir:

scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>

Yazı Yazısı

Not:println(filter(nums, modN(2)) Örneğinizin alt çizgi olmadan çalışmasının nedeni modN(2), Scala derleyicisinin alt çizginin programcı için bir kolaylık olduğunu varsaymasıdır.


Ekleme: @asflierl'in doğru bir şekilde işaret ettiği gibi, Scala "normal" işlevleri kısmen uygularken türü çıkaramıyor gibi görünüyor:

scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))

Bu bilgi, birden çok parametre listesi gösterimi kullanılarak yazılan işlevler için mevcutken:

scala> modNCurried(5) _
res3: Int => Boolean = <function1>

Bu cevaplar bunun nasıl çok faydalı olabileceğini gösteriyor.


önemli bir fark, tür çıkarımının farklı çalışmasıdır: gist.github.com/4529020

Teşekkürler, ben Yorumunuzu :) ilişkin bir not ekledi
fresskoma

19

Currying, tuple'larla ilgilidir: bir demet argümanını alan bir fonksiyonu n ayrı argüman alan bir fonksiyona dönüştürmek ve bunun tersi de geçerlidir . Bunu hatırlamak, körlemeyi açıkça desteklemeyen dillerde bile köri ile kısmi uygulamayı ayırt etmenin anahtarıdır.

curry :: ((a, b) -> c) -> a -> b -> c 
   -- curry converts a function that takes all args in a tuple
   -- into one that takes separate arguments

uncurry :: (a -> b -> c) -> (a, b) -> c
   -- uncurry converts a function of separate args into a function on pairs.

Kısmi uygulama, bazı argümanlara bir fonksiyon uygulama ve kalan argümanlar için yeni bir fonksiyon üretme yeteneğidir .

Sadece körlemenin tuple'larla ilgili bir dönüşüm olduğunu düşünüyorsanız, hatırlamak kolaydır.

Varsayılan olarak curried olan dillerde (Haskell gibi) fark açıktır - bir demetteki argümanları iletmek için gerçekten bir şeyler yapmanız gerekir. Ancak Scala dahil diğer dillerin çoğu varsayılan olarak çözülmez - tüm bağımsız değişkenler tuple olarak aktarılır, bu nedenle curry / uncurry çok daha az yararlıdır ve daha az açıktır. Ve hatta insanlar, kısmi uygulama ve körlemenin aynı şey olduğunu düşünmeye başlar - çünkü sadece körili fonksiyonları kolayca temsil edemezler!


Tamamen katılıyorum. Scala terimlerinde, kelimenin orijinal anlamında "currying", bir parametre listesi olan bir işlevi birden çok parametre listesi olan bir işleve "dönüştürme işlemidir". Scala'da bu dönüşüm ".curried" kullanılarak yürütülebilir. Ne yazık ki, Scala kelimenin anlamını biraz aşırı yüklemiş gibi görünüyor, çünkü başlangıçta ".curried" yerine ".curry" olarak adlandırılmayı tercih ediyordu.
Fatih Coşkun

2

Çok değişkenli fonksiyon:

def modN(n: Int, x: Int) = ((x % n) == 0)

Köri (veya körili fonksiyon):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

Bu nedenle, körlemeyle karşılaştırılabilecek kısmen uygulanan bir işlev değildir. Bu çok değişkenli fonksiyondur. Kısmen uygulanan fonksiyonla karşılaştırılabilir olan şey, kısmen uygulanan fonksiyonun sahip olduğu aynı parametre listesine sahip bir fonksiyon olan curried fonksiyonun çağırma sonucudur.


0

Sadece son noktayı netleştirmek için

Ekleme: @asflierl'in doğru bir şekilde işaret ettiği gibi, Scala "normal" işlevleri kısmen uygularken türü çıkaramıyor gibi görünüyor:

Scala, tüm parametreler joker karakter ise, ancak bazıları belirtilirken bazıları belirtilmezse türler çıkarabilir.

scala> modN(_,_)
res38: (Int, Int) => Boolean = <function2>

scala> modN(1,_)
<console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1))
       modN(1,_)
              ^

0

Şimdiye kadar bulabildiğim en iyi açıklama: https://dzone.com/articles/difference-between-currying-amp-partial-applied

Currying: çoklu argümanlar içeren fonksiyonların tek argümanlı fonksiyonlar zincirine ayrıştırılması. Scala'nın bir işlevi başka bir işleve argüman olarak aktarmaya izin verdiğine dikkat edin.

İşlevin kısmi uygulaması: bir işleve bildiriminde olduğundan daha az argüman iletir. Scala, işleve daha az bağımsız değişken sağladığınızda bir istisna atmaz, yalnızca bunları uygular ve iletilmesi gereken diğer bağımsız değişkenlerle yeni bir işlev döndürür.

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.