Scala'da yöntem ve işlev arasındaki fark


254

Scala Fonksiyonlarını okudum ( Scala'nın başka bir turunun parçası ) Bu yazıda şunları söyledi:

Yöntemler ve işlevler aynı şey değil

Ama bu konuda hiçbir şey açıklamadı. Ne demeye çalışıyordu?




İyi cevapları olan bir takip sorusu: Scala'daki fonksiyonlar ve yöntemler
Josiah Yoder

Yanıtlar:


238

Jim blog yazısında bunu çok fazla ele aldı , ama burada referans için bir brifing gönderiyorum.

İlk olarak, Scala Spesifikasyonunun bize ne söylediğine bakalım. Bölüm 3 (türler) bize İşlev Türleri (3.2.9) ve Yöntem Türleri (3.3.1) hakkında bilgi verir. Bölüm 4'te (temel bildirimler) Değer Beyanı ve Tanımları (4.1), Değişken Beyanı ve Tanımları (4.2) ve Fonksiyon Beyanları ve Tanımları (4.6) anlatılmaktadır. Bölüm 6'da (ifadeler) Anonim İşlevler (6.23) ve Yöntem Değerleri (6.7) anlatılmaktadır. Merakla, fonksiyon değerleri 3.2.9'da bir kez konuşulur ve başka yerde yoktur.

Bir Fonksiyon Tip formunun (kabaca) tipidir (T1, ..., Tn) => u özelliği için bir kısaltmadır, FunctionNstandart kütüphanesinde. Anonim İşlevler ve Yöntem Değerler işlev türlerine sahiptir ve işlev türleri değer, değişken ve işlev bildirimleri ve tanımlarının bir parçası olarak kullanılabilir. Aslında, bir yöntem türünün bir parçası olabilir.

Bir Yöntem a, olmayan bir değer türü . Araçlarının orada hiçbir hiçbir nesne, herhangi bir örneği - - bir yöntem türü ile değer. Yukarıda belirtildiği gibi, bir Yöntem Değeri aslında bir İşlev Türüne sahiptir . Bir yöntem türü bir defbildiridir - defbedeni hariç her şey .

Değer Beyanları ve Tanımlar ve Değişken Bildirimleri ve Tanımlar vardır valve varher ikisi de dahil olmak üzere beyanları, tipi ve değeri - hangi olabilir, sırasıyla Fonksiyon Tipi ve Anonim Fonksiyonlar veya Yöntem Değerler . JVM'de bunların (yöntem değerleri) Java'nın "yöntemler" dediği şeyle uygulandığını unutmayın.

Bir Fonksiyon Deklarasyonu bir olduğunu defdahil beyannamesi, tip ve vücutta . Yazım kısmı, Yöntem Türü'dür ve gövde bir ifade veya bloktur . Bu, Java'nın "yöntemler" dediği JVM'de de uygulanır.

Son olarak, Anonim İşlev , İşlev Türü'nün (yani, özelliğin bir örneği FunctionN) bir örneğidir ve Yöntem Değeri aynı şeydir! Fark, (a yöntem değer yöntemleri, ya bir alt postfixing oluşturulur ki m _, (a yöntem değeri "fonksiyonu beyanı" tekabül etmektedir def) m), ya da adı verilen bir işlem ile eta-genişleme yöntemi otomatik bir döküm gibi, işlev.

Spesifikasyonlar böyle söylüyor, bu yüzden bunu öne koymama izin verin: bu terminolojiyi kullanmıyoruz! Çok fazla sözde arasındaki karışıklığa yol açar "işlevi beyanı" ve - programının bir parçası (temel beyanlar bölüm 4) 'dir, "anonim işlev" bir ifadesidir, ve "fonksiyonu tipi" olduğu, iyi bir tür - bir özellik.

Aşağıdaki terminoloji ve deneyimli Scala programcılar tarafından kullanılan, şartnamenin terminoloji itibaren bir değişiklik yapar: demek yerine işlevi bildirimi , söylediğimiz yöntem . Hatta yöntem beyanı bile. Ayrıca, değer bildirimlerinin ve değişken bildirimlerin de pratik amaçlı yöntemler olduğunu belirtiyoruz .

Yani, terminolojideki yukarıdaki değişiklik göz önüne alındığında, işte bu ayrımın pratik bir açıklaması.

Bir fonksiyon biri içeren bir amacı, FunctionXörneğin, özellikleri, Function0, Function1, Function2, vs. de dahil olmak üzere olabilir PartialFunctionaslında uzanan yanı Function1.

Bu özelliklerden biri için tür imzasını görelim:

trait Function2[-T1, -T2, +R] extends AnyRef

Bu özelliğin bir soyut yöntemi vardır (birkaç somut yöntemi de vardır):

def apply(v1: T1, v2: T2): R

Ve bu bize bunun hakkında bilinmesi gereken her şeyi anlatıyor. Bir fonksiyonun , T1 , T2 , ..., TN tipindeki N parametrelerini alan ve türünde bir şey döndüren bir applyyöntemi vardır . Aldığı parametrelerde kontra-varyant ve sonuçta ko-varyanttır.R

Bu varyans Function1[Seq[T], String]a'nın bir alt türü olduğu anlamına gelir Function1[List[T], AnyRef]. Bir alt tip olması , onun yerine kullanılabileceği anlamına gelir . Birisi arayacak f(List(1, 2, 3))ve bir AnyRefgeri bekleyecek olursam , yukarıdaki iki türden birinin işe yarayacağını kolayca görebilirsiniz .

Şimdi, bir yöntemin ve fonksiyonun benzerliği nedir? Peki, eğer fbir işlev ve mkapsam için yerel bir yöntemse, her ikisi de şu şekilde çağrılabilir:

val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))

Bu çağrılar aslında farklıdır, çünkü ilki sadece sözdizimsel bir şekerdir. Scala bunu genişletir:

val o1 = f.apply(List(1, 2, 3))

Tabii ki, nesne üzerinde bir yöntem çağrısıdır f. Fonksiyonların avantajı için başka sözdizimsel şekerleri de vardır: fonksiyon değişmezleri (aslında ikisi) ve (T1, T2) => Rtip imzaları. Örneğin:

val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
  case i: Int => "Int"
  case d: Double => "Double"
  case o => "Other"
}

Bir yöntem ve bir fonksiyon arasındaki diğer bir benzerlik, birincisinin kolayca ikincisine dönüştürülebilmesidir:

val f = m _

Scala genişleyecektir olduğunu varsayarak mtürü olan (List[Int])AnyRef(Scala 2.7) içine:

val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

Scala 2.8'de, aslında AbstractFunction1sınıf boyutlarını azaltmak için bir sınıf kullanır .

Bir işlevden bir yönteme - tersi dönüştürülemez dikkat edin.

Bununla birlikte, yöntemlerin büyük bir avantajı vardır (iki, biraz daha hızlı olabilirler): tip parametrelerini alabilirler . Örneğin, fyukarıdakiler zorunlu olarak alacağı türü belirtebilir List( List[Int]örnekte), mparametreleştirebilir:

def m[T](l: List[T]): String = l mkString ""

Bunun hemen hemen her şeyi kapsadığını düşünüyorum, ancak bunu kalan soruların cevaplarıyla tamamlamaktan mutluluk duyacağım.


26
Bu açıklama çok açık. Aferin. Ne yazık ki, hem Odersky / Venners / Spoon kitabı hem de Scala spec "işlev" ve "yöntem" kelimelerini birbirinin yerine kullanıyor. ("Metot" un daha net olacağı "fonksiyon" deme olasılıkları daha yüksektir, ancak bazen başka şekilde de olur, örneğin spesifikasyonların fonksiyonlara dönüştürülmesini kapsayan bölüm 6.7 "Metot Değerleri" olarak adlandırılır. .) Bence bu dilleri gevşek kullanmanın insanlar dili öğrenmeye çalıştıklarında çok karışıklığa yol açtığını düşünüyorum.
Seth Tisue

4
@Seth biliyorum, biliyorum - PinS bana Scala'yı öğreten kitaptı. Zor yolu daha iyi öğrendim, yani paulp beni düzleştirdi.
Daniel C. Sobral

4
Harika bir açıklama! Eğer genişlemesini alıntı Ne zaman: Ben eklemek için bir şey val f = molarak derleyici tarafından val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }size işaret olmalıdır thisiçindeki applyyöntemle ifade etmez AnyRefnesne, ancak kimin yöntemi nesneye val f = m _değerlendirilir ( dış this , tabiri caizse ), çünkü thiskapak tarafından yakalanan değerler arasındadır (örneğin returnaşağıda belirtildiği gibi).
Holger Peine

1
@ DanielC.Sobral, bahsettiğiniz PinS kitabı nedir? Ben de Scala öğrenmekle ilgileniyorum ve bu isimle bir kitap bulamadım,
tldr

5
@tldr Sca'da Programlama , Odersky ve diğerleri. Bunun ortak bir kısaltmasıdır (bana nedense PiS'i pek sevmediklerini söylediler! :)
Daniel C. Sobral

67

Bir yöntem ve fonksiyon arasındaki büyük pratik fark, ne returnanlama geldiğidir. returnsadece bir yöntemden döner. Örneğin:

scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
       val f = () => { return "test" }
                       ^

Bir yöntemde tanımlanan bir işlevden dönmek, yerel olmayan bir dönüş yapar:

scala> def f: String = {                 
     |    val g = () => { return "test" }
     | g()                               
     | "not this"
     | }
f: String

scala> f
res4: String = test

Oysa yerel bir yöntemden dönmek yalnızca bu yöntemden döner.

scala> def f2: String = {         
     | def g(): String = { return "test" }
     | g()
     | "is this"
     | }
f2: String

scala> f2
res5: String = is this

9
Çünkü geri dönüş kapanış tarafından ele geçirilir.
Daniel C.Sobral

4
Bir fonksiyondan yerel olmayan kapsama 'dönmek' isteyeceğim tek bir zaman düşünemiyorum. Aslında, bir fonksiyonun yığının ötesine geçmek istediğine karar verebiliyorsa ciddi bir güvenlik sorunu olarak görebiliyorum. Longjmp gibi hissediyorum, yanlışlıkla yanlış yapmanın tek yolu daha kolay. Ama scalac'ın işlevlerden dönmeme izin vermediğini fark ettim. Bu, bu iğrençliğin dilden vurulduğu anlamına mı geliyor?
kök

2
@root - ne içeriden dönen hakkında for (a <- List(1, 2, 3)) { return ... }? Bu, kapanmaya kadar şekersizleşir.
Ben Lings

Hmm ... Bu makul bir kullanım durumudur. Hala hata ayıklanması zor sorunlara yol açma potansiyeline sahiptir, ancak bu daha mantıklı bir bağlamda ortaya çıkmaktadır.
Kök

1
Dürüst olmak gerekirse farklı sözdizimi kullanırdım. sahip returnfonksiyonu bir değer iade ve bazı formu escapeveya breakveya continueyöntemleri dönün.
Ryan The Leach

38

işlev Sonuç üretmek için bir işlev listesi içeren bir işlev çağrılabilir. Bir işlevin bir parametre listesi, bir gövde ve bir sonuç türü vardır. Sınıf, özellik veya singleton nesnesinin üyesi olan işlevlere yöntem denir . Diğer işlevler içinde tanımlanan işlevlere yerel işlevler denir. Birimin sonuç türüne sahip işlevlere yordamlar denir. Kaynak koddaki anonim işlevlere işlev değişmezleri denir. Çalışma zamanında işlev değişmezleri işlev değerleri adı verilen nesnelere örneklenir.

Scala Second Edition'da Programlama. Martin Odersky - Lex Kaşık - Bill Venners


1
Bir işlev bir sınıfa def veya val / var olarak ait olabilir. Sadece def'ler yöntemdir.
Josiah Yoder

29

Diyelim bir listeniz var

scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

Bir Yöntem Tanımlama

scala> def m1(i:Int)=i+2
m1: (i: Int)Int

Bir İşlev Tanımlama

scala> (i:Int)=>i+2
res0: Int => Int = <function1>

scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)

Tartışmayı Kabul Eden Yöntem

scala> m1(2)
res3: Int = 4

Val ile Fonksiyon Tanımlama

scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>

İşlev bağımsız değişkeni isteğe bağlıdır

 scala> p(2)
    res4: Int = 4

scala> p
res5: Int => Int = <function1>

Metot Argümanı Zorunludur

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

Yöntem Vs İşlevi ile farklı diff örneği gibi diğer örneklerle diğer farklılıkları aktarmayı açıklayan aşağıdaki Öğreticiyi denetleyin, işlevi Değişkenler olarak kullanma, işlevi döndüren işlev oluşturma


13

İşlevler parametre varsayılanlarını desteklemez. Yöntemler yapar. Bir yöntemden işleve dönüştürmek parametre varsayılanlarını kaybeder. (Scala 2.8.1)


5
Bunun sebebi var mı?
Mayıs

7

Burada, açıklamalarımın çoğunun alındığı güzel bir makale var . Benim anlayışımla ilgili İşlevler ve Yöntemlerin kısa bir karşılaştırması. Umarım yardımcı olur:

Fonksiyonlar : Temelde bir nesnedir. Daha kesin olarak, fonksiyonlar bir uygulama yöntemine sahip nesnelerdir; Bu nedenle, yükleri nedeniyle yöntemlerden biraz daha yavaştırlar. Çağırılacak bir nesneden bağımsız olmaları bakımından statik yöntemlere benzer. Bir fonksiyonun basit bir örneği tıpkı aşağıdaki gibidir:

val f1 = (x: Int) => x + x
f1(2)  // 4

Yukarıdaki satır, bir nesneyi object1 = object2 gibi diğerine atamaktan başka bir şey değildir. Aslında örneğimizdeki object2 anonim bir işlevdir ve sol taraf bu nedenle bir nesnenin türünü alır. Bu nedenle, şimdi f1 bir nesnedir (İşlev). Anonim işlev aslında Int türünde 1 parametre ve Int türünde dönüş değeri olan bir işlev anlamına gelen Function1 [Int, Int] işlevinin bir örneğidir. Bağımsız değişkenler olmadan f1 çağrısı bize anonim işlevin imzasını verecektir (Int => Int =)

Yöntemler : Nesneler değildir, bir sınıf örneğine, yani bir nesneye atanırlar. Java'daki yöntemle veya c ++ 'daki üye işlevlerle tam olarak aynı ( Raffi Khatchadourian'ın bu soruya yaptığı bir yorumda işaret ettiği gibi ) vb. Bir yöntemin basit bir örneği, aşağıdaki gibidir:

def m1(x: Int) = x + x
m1(2)  // 4

Yukarıdaki satır basit bir değer ataması değil, bir yöntemin tanımıdır. Bu yöntemi ikinci satır gibi 2 değeriyle çağırdığınızda, x 2 ile değiştirilir ve sonuç hesaplanır ve çıktı olarak 4 elde edersiniz. Burada sadece yöntem olduğu ve giriş değerine ihtiyaç duyduğu için sadece m1 yazıyorsanız bir hata alırsınız. _ Kullanarak aşağıdaki gibi bir işleve bir yöntem atayabilirsiniz:

val f2 = m1 _  // Int => Int = <function1>

"Bir işleve yöntem atamak" ne demektir? Bu sadece yöntemle aynı şekilde davranan bir nesneye sahip olduğunuz anlamına mı geliyor?
K. M

@KM: val f2 = m1 _ val f2 = new Function1 [Int, Int] {def m1 (x: Int) = x + x};
sasuke

3

İşte Rob Norris'in farkı anlatan harika bir yazı , işte TL; DR

Scala'daki yöntemler değer değil, işlevlerdir. Bir metoda delege eden bir işlevi η-expanding (arkadaki alt çizgi nesnesi tarafından tetiklenen) aracılığıyla oluşturabilirsiniz.

aşağıdaki tanım ile:

bir yöntem def ile tanımlanan bir değerdir ve bir değer val'e atayabileceğiniz bir şeydir

Özetle ( blogdan alıntı ):

Bir yöntem tanımladığımızda, a yöntemine atayamayacağımızı görüyoruz val.

scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int

scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
       val f = add1

Ayrıca Not türünü ait add1olağan görünmüyor; bir değişken türü bildiremezsiniz(n: Int)Int . Yöntemler değer değildir.

Ancak, η-genişletme postfix operatörünü ekleyerek (η “eta” olarak telaffuz edilir), yöntemi bir fonksiyon değerine dönüştürebiliriz. Türüne dikkat edin f.

scala> val f = add1 _
f: Int => Int = <function1>

scala> f(3)
res0: Int = 4

Etkisi _aşağıdakilerin eşdeğerini gerçekleştirmektir: Function1yöntemimize yetki veren bir örnek oluştururuz.

scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>

scala> g(3)
res18: Int = 4

1

Scala 2.13'te, işlevlerin aksine, yöntemler alabilir / döndürebilir

  • tip parametreleri (polimorfik yöntemler)
  • örtük parametreler
  • bağımlı tipler

Bununla birlikte, bu kısıtlamalar Polimorfik fonksiyon türleri # 4672 tarafından dotty (Scala 3) olarak kaldırılır , örneğin dotty sürüm 0.23.0-RC1 aşağıdaki sözdizimini etkinleştirir

Tür parametreleri

def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))

Örtük parametreler ( bağlam parametreleri)

def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero

Bağımlı tipler

class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet

Daha fazla örnek için bkz. Test / run / polimorfik-fonksiyonlar.scala


0

Pratik olarak, bir Scala programcısı sadece fonksiyonları ve yöntemleri düzgün kullanmak için aşağıdaki üç kuralı bilmelidir:

  • Tarafından tanımlanan yöntemler defve tarafından tanımlanan işlev değişmezleri=> işlevlerdir. Scala Programlama kitabının 4. baskısında, sayfa 143, Bölüm 8'de tanımlanmıştır.
  • İşlev değerleri, herhangi bir değer olarak iletilebilen nesnelerdir. İşlev değişmez değerleri ve kısmen uygulanan işlevler işlev değerleridir.
  • Koddaki bir noktada bir işlev değeri gerekiyorsa, kısmen uygulanan bir işlevin alt çizgisini bırakabilirsiniz. Örneğin:someNumber.foreach(println)

Scala'da Programlamanın dört basımından sonra, insanlar için iki önemli kavramı ayırt etmek hala bir sorundur: fonksiyon ve fonksiyon değeri, çünkü tüm basımlar açık bir açıklama vermemektedir. Dil belirtimi çok karmaşık. Yukarıdaki kuralların basit ve doğru olduğunu gördüm.

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.