=>, () => Ve Birim => arasındaki fark nedir


153

Ben hiçbir argüman alır ve hiçbir değer döndüren bir işlevi temsil etmeye çalışıyorum (bilmeniz durumunda JavaScript setTimeout işlevini simüle ediyorum.)

case class Scheduled(time : Int, callback :  => Unit)

derlemez, "val 'parametreleri isimle çağrılamayabilir" demek

case class Scheduled(time : Int, callback :  () => Unit)  

derler, ancak yerine garip bir şekilde çağrılmalıdır

Scheduled(40, { println("x") } )

Bunu yapmak zorundayım

Scheduled(40, { () => println("x") } )      

Ne işe yarıyor

class Scheduled(time : Int, callback :  Unit => Unit)

ancak daha az mantıklı bir şekilde çağrılır

 Scheduled(40, { x : Unit => println("x") } )

(Type Unit değişkeni ne olurdu?) Elbette istediğim , sıradan bir işlev olsaydı onu çağıracak şekilde çağırabilecek bir kurucu.

 Scheduled(40, println("x") )

Bebeğe biberonunu ver!


3
By-name parms ile vaka sınıflarını kullanmanın başka bir yolu, bunları ikincil bir parametre listesine yerleştirmektir case class Scheduled(time: Int)(callback: => Unit). Bu, ikincil parametre listesinin herkese açık olarak gösterilmemesi veya oluşturulan equals/ hashCodeyöntemlere dahil edilmemesi nedeniyle çalışır .
nilskp

Bu soru ve cevapta, ad-adı parametreleri ile 0-arity fonksiyonları arasındaki farklarla ilgili birkaç ilginç yön bulunmaktadır . Aslında bu soruyu bulduğumda aradığım şey buydu.
lex82

Yanıtlar:


234

İsimle Çağrı: => Tür

=> TypeNotasyonu çağrısı-by-name biridir açılımı birçok yönden parametreleri geçirilebilir. Onlara aşina değilseniz, bu wikipedia makalesini okumak için biraz zaman ayırmanızı öneririm, günümüzde çoğunlukla değer çağrı ve referans çağrıdır.

Bunun anlamı, iletilen şeyin işlev içindeki değer adı ile değiştirilmesidir . Örneğin, bu işlevi kullanın:

def f(x: => Int) = x * x

Eğer böyle çağırırsam

var y = 0
f { y += 1; y }

Sonra kod bu şekilde yürütülecek

{ y += 1; y } * { y += 1; y }

Ancak bu, bir tanımlayıcı adı çakışması olduğunda ne olacağı noktasını arttırır. Geleneksel isimle çağrıda, isim çatışmalarından kaçınmak için yakalamadan kaçınma ikamesi adı verilen bir mekanizma gerçekleşir. Bununla birlikte, Scala'da bu, aynı sonuçla başka bir şekilde uygulanır - parametrenin içindeki tanımlayıcı adları, çağrılan işlevdeki gölge tanımlayıcılara başvuramaz veya gölge tanımlayıcılara başvuramaz.

Diğer ikisini açıkladıktan sonra konuşacağım isim ile ilgili başka noktalar da var.

0-arity İşlevleri: () => Tür

Sözdizimi () => Typea türünü ifade eder Function0. Yani, parametre almayan ve bir şey döndüren bir işlev. Bu, örneğin, yöntemi çağırmakla eşdeğerdir size()- hiçbir parametre almaz ve bir sayı döndürür.

Bununla birlikte, bu sözdiziminin, bazı karışıklıkların nedeni olan anonim bir işlev değişmezinin sözdizimine çok benzemesi ilginçtir . Örneğin,

() => println("I'm an anonymous function")

olan Arity 0 anonim fonksiyonu hazır olduğu türü olan

() => Unit

Böylece şunu yazabiliriz:

val f: () => Unit = () => println("I'm an anonymous function")

Bununla birlikte, tipin değerle karıştırılmaması önemlidir.

Birim => Tür

Bu aslında Function1ilk parametresi türünde olan sadece bir Unit. Yazmanın diğer yolları (Unit) => Typeda olabilir Function1[Unit, Type]. Mesele şu ki, bu kişinin istediği şey olması pek olası değildir. Türün Unitana amacı kişinin ilgilenmediği bir değeri göstermektir, bu nedenle bu değeri almak mantıklı değildir .

Örneğin,

def f(x: Unit) = ...

Biri muhtemelen ne yapabilirdi x? Sadece tek bir değere sahip olabilir, bu yüzden onu almanıza gerek yoktur. Olası bir kullanım, geri dönen zincirleme işlevleri olacaktır Unit:

val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g

Çünkü andThensadece tanımlanır Function1, ve zincirleme işlevler dönen Unitbiz türü olarak bunları tanımlamak zorunda Function1[Unit, Unit]zincir bunları mümkün.

Karışıklık Kaynakları

İlk karışıklık kaynağı, 0-arity işlevleri için var olan tür ile değişmez adlar arasındaki benzerliğin, adıyla çağrı için de var olduğunu düşünmektir. Başka bir deyişle, düşünerek, çünkü

() => { println("Hi!") }

bir hazır içindir () => Unit, sonra,

{ println("Hi!") }

bir değişmez olur => Unit. O değil. Bu bir kod bloğu, gerçek bir bilgi değil.

Bir diğer karışıklık kaynağı olmasıdır Unittürünün değeri yazılır ()(ama değil) bir 0-Arity parametre listesi gibi hangi görünüyor.


İki yıl sonra aşağı oy veren ilk kişi olmalıyım. Birisi Noel'deki case => sözdizimini merak ediyor ve bu cevabı kanonik ve eksiksiz olarak tavsiye edemiyorum! Dünya ne geliyor? Belki Mayalar bir hafta kadar kapalı kalmışlardı. Artık yıllarda doğru şekilde mi rakamlar verdiler? Gün ışığından yararlanma?
som-snytt

@ som-snytt Peki, soru sormadı case ... =>, bu yüzden bahsetmedim. Acı ama gerçek. :-)
Daniel C. Sobral

1
@Daniel C. Sobral, "Bu bir kod bloğu, gerçek bir bilgi değil" diye açıklayabilir misiniz? Bölüm. Peki ikisi arasındaki fark nedir?
nish1013

2
@ nish1013 "Değişmez değer" bir değerdir ( bazı örnekler için tam sayı 1, karakter 'a', dize "abc"veya işlev () => println("here")). Bağımsız değişken olarak aktarılabilir, değişkenlerde saklanabilir, vb. "Kod bloğu" ifadelerin sözdizimsel olarak sınırlandırılmasıdır - bir değer değil, etrafından geçilemez veya bunun gibi bir şey.
Daniel C.Sobral

1
@Alex Bu (Unit) => Typevs ile aynı farktır () => Type- birincisi a Function1[Unit, Type], ikincisi a Function0[Type].
Daniel

36
case class Scheduled(time : Int, callback :  => Unit)

caseModifiye örtülü hale valkurucu her bir bağımsız değişken bölgesinin out. Bu nedenle (birisinin belirttiği gibi) kaldırırsanız case, bir adama çağrı parametresi kullanabilirsiniz. Derleyici muhtemelen buna izin verebilir, ancak val callbackdönüşüme dönüştürmek yerine yaratıldığında insanları şaşırtabilir lazy val callback.

callback: () => UnitŞimdi değiştirdiğinizde , davanız sadece çağrı-adı parametresi yerine bir işlev alır. Açıkçası, fonksiyon saklanabilir, val callbackböylece sorun yoktur.

İstediğinizi elde etmenin en kolay yolu ( Scheduled(40, println("x") )bir lambda'yı geçmek için bir çağrı-adı parametresi kullanıldığında) muhtemelen ilk etapta alamadığınız şeyi atlamak caseve açıkça oluşturmaktır apply:

class Scheduled(val time: Int, val callback: () => Unit) {
    def doit = callback()
}

object Scheduled {
    def apply(time: Int, callback: => Unit) =
        new Scheduled(time, { () => callback })
}

Kullanımda:

scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190

scala> Scheduled(1234, println("x")).doit
x

3
Neden bir vaka sınıfı tutmuyorsunuz ve varsayılan uygulamayı geçersiz kılmıyorsunuz? Ayrıca, derleyici bir by-name'i tembel bir val'e çeviremez, çünkü doğası gereği farklı semantiklere sahip olduğundan, tembel en fazla bir kez ve adının her referansta vardır
Viktor Klang

@ViktorKlang Bir vaka sınıfının varsayılan uygulama yöntemini nasıl geçersiz kılabilirsiniz? stackoverflow.com/questions/2660975/…
Sawyer

Object ClassName {def uygulamak (…):… =…}
Viktor Klang

Dört yıl sonra ve seçtiğim cevabın sadece başlıktaki soruyu cevapladığını anlıyorum, aslında sahip olduğum soruya değil (bu cevap veriyor).
Malvolio

1

Soruda, JavaScript'te SetTimeOut işlevini simüle etmek istiyorsunuz. Önceki cevaplara dayanarak, aşağıdaki kodu yazıyorum:

class Scheduled(time: Int, cb: => Unit) {
  private def runCb = cb
}

object Scheduled {
  def apply(time: Int, cb: => Unit) = {
    val instance = new Scheduled(time, cb)
    Thread.sleep(time*1000)
    instance.runCb
  }
}

REPL'de şöyle bir şey elde edebiliriz:

scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
a
b

Simülasyonumuz SetTimeOut ile tam olarak aynı davranmaz, çünkü simülasyonumuz engelleme işlevidir, ancak SetTimeOut engellemez.


0

Bunu bu şekilde yapıyorum (sadece kırmak istemiyorum):

case class Thing[A](..., lazy: () => A) {}
object Thing {
  def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
}

ve ara

Thing.of(..., your_value)
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.