Scala devam ettirmeleri nedir ve neden kullanılır?


85

Scala'da Programlamayı yeni bitirdim ve Scala 2.7 ve 2.8 arasındaki değişiklikleri araştırıyorum. En önemli gibi görünen, devam etme eklentisidir, ancak bunun ne için yararlı olduğunu veya nasıl çalıştığını anlamıyorum. Eşzamansız G / Ç için iyi olduğunu gördüm, ancak nedenini bulamadım. Konuyla ilgili daha popüler kaynaklardan bazıları şunlardır:

Ve Stack Overflow'daki bu soru:

Ne yazık ki, bu referansların hiçbiri devam ettirmelerin ne için olduğunu veya kaydırma / sıfırlama işlevlerinin ne yapması gerektiğini tanımlamaya çalışmıyor ve bunu yapan herhangi bir referans bulamadım. Bağlantılı makalelerdeki örneklerden herhangi birinin nasıl çalıştığını (veya ne yaptıklarını) tahmin edemedim, bu yüzden bana yardım etmenin bir yolu, bu örneklerden birini satır satır geçmek olabilir. Üçüncü makaleden bu kadar basit olanı bile:

reset {
    ...
    shift { k: (Int=>Int) =>  // The continuation k will be the '_ + 1' below.
        k(7)
    } + 1
}
// Result: 8

Sonuç neden 8? Bu muhtemelen başlamama yardımcı olur.


Yanıtlar:


38

Benim blog ne anlatmak yapar resetve shiftbunu tekrar okumak isteyebilirsiniz, böylece yapmak.

Blogumda da işaret ettiğim bir diğer iyi kaynak, devam eden geçiş stiliyle ilgili Wikipedia girişi . Bu, Scala sözdizimini kullanmasa da konuyla ilgili en net olanıdır ve devamı açıkça aktarılır.

Blogumda bağlantı kurduğum ancak kırılmış gibi görünen sınırlandırılmış devam ettirmeler hakkındaki kağıt, birçok kullanım örneği veriyor.

Ancak , sınırlandırılmış süreklilik kavramının en iyi örneğinin Scala Swarm olduğunu düşünüyorum. İçinde, kütüphane kodunuzun çalıştırılmasını bir noktada durdurur ve kalan hesaplama devamı olur. Kitaplık daha sonra bir şeyler yapar - bu durumda, hesaplamayı başka bir ana bilgisayara aktarır ve sonucu (erişilen değişkenin değeri) durdurulan hesaplamaya döndürür.

Şimdi, bunu, Scala sayfasında bile basit bir örnek anlamıyorum do blogumu okuyun. Ben sadece bu temel bilgileri, sonucun neden olduğunu açıklamakla ilgileniyorum 8.


Blog yazınızı yeniden okudum ve bu sefer ona sadık kaldım - sanırım neler olup bittiğine dair daha iyi bir fikrim var. Wikipedia sayfasından pek bir şey almadım (Lisp devamlarını zaten biliyorum) ama sıfırlama / vardiya ertelenmiş stil veya adı ne olursa olsun beni şaşırttı. Sabırsız olanlar (yani kendim) için açıklamanız iyiydi ancak insanlar "Sıfırlamanın sonucu vardiya içindeki kodun sonucudur" a uymak zorunda kalacaklar. paragraf ... O noktaya kadar umutsuzca kayboldum ama daha netleşiyor! Swarm'a bir göz atacağım çünkü bunun ne için olduğunu hala merak ediyorum. Teşekkür!
Dave

Evet, işlerin anlam kazanmaya başlaması zaman alır. Açıklamayı daha hızlı yapabileceğimi düşünmedim.
Daniel C. Sobral

"Sıfırlamanın devamın kapsamını sınırlandırdığını (yani: dahil edilecek değişkenler ve ifadeler)" farkına vardığımda hepsi bir araya geldi
JeffV

1
Açıklamanız ayrıntılıydı ve anlamanın özüne ulaşmadı. Örnekler uzundu, ilk paragraflarda bana hepsini okumam için ilham verecek kadar yeterince anlayış alamadım. Ben de reddettim. SO oy verdikten sonra bir mesaj görüntülüyor, yorum eklememi istiyor, bu yüzden uyuyorum. Açık sözlülüğüm için özür dilerim.
Shelby Moore III

1
Bunun hakkında, kontrol akışını anlamaya odaklanarak (uygulamanın ayrıntılarını tartışmadan) blog yazdım. wherenullpoints.com/2014/04/scala-continuation.html
Alexandros

31

Mevcut açıklamaların kavramı açıklamada umduğumdan daha az etkili olduğunu gördüm. Umarım bu açık (ve doğrudur). Devamları henüz kullanmadım.

Bir devam işlevi cfçağrıldığında:

  1. Yürütme shiftbloğun geri kalanını atlar ve sonunda tekrar başlar
    • iletilen parametre , yürütme devam ederken bloğun "değerlendirdiği" cfşeydir shift. bu her çağrı için farklı olabilircf
  2. Yürütme resetbloğun sonuna kadar (veya blok yoksa bir çağrıya kadar reset) devam eder
    • resetbloğun sonucu (veya blok yoksa to reset() parametresi ) cfdöndüren şeydir
  3. Yürütme bloğun cfsonuna kadar devam edershift
  4. Yürütme, resetbloğun sonuna kadar (veya sıfırlama çağrısı?)

Yani bu örnekte, A'dan Z'ye harfleri takip edin

reset {
  // A
  shift { cf: (Int=>Int) =>
    // B
    val eleven = cf(10)
    // E
    println(eleven)
    val oneHundredOne = cf(100)
    // H
    println(oneHundredOne)
    oneHundredOne
  }
  // C execution continues here with the 10 as the context
  // F execution continues here with 100
  + 1
  // D 10.+(1) has been executed - 11 is returned from cf which gets assigned to eleven
  // G 100.+(1) has been executed and 101 is returned and assigned to oneHundredOne
}
// I

Bu şunu yazdırır:

11
101

2
Derlemeye çalıştığımda "CPS tarafından dönüştürülen işlev sonucu için tür hesaplanamıyor" diyen bir hatayla karşılaşıyorum .. Ne olduğundan ne de nasıl düzelteceğimi bilmiyorum
Fabio Veronez

@Fabio Veronez Vardiyanın sonuna bir dönüş ifadesi ekleyin: println(oneHundredOne) }diyelim, olarak değiştirin println(oneHundredOne); oneHundredOne }.
folone

Korkunç bir sözdizimi için güzel bir açıklama. Devam işlevinin beyanı tuhaf bir şekilde gövdesinden ayrılmıştır. Bu tür kafa karıştırıcı kodu başkalarıyla paylaşmak konusunda isteksiz olurum.
joeytwiddle

Önlemek için cannot compute type for CPS-transformed function resulthatayı, +1hemen sonra takip edecektir oneHundredOne}. Şu anda aralarında bulunan yorumlar grameri bir şekilde bozuyor.
lcn

9

Scala'nın sınırlandırılmış devamları için araştırma makalesinden kanonik örnek verildiğinde, biraz değiştirildi, böylece işlev girdisine shiftad verildi fve bu nedenle artık anonim değil.

def f(k: Int => Int): Int = k(k(k(7)))
reset(
  shift(f) + 1   // replace from here down with `f(k)` and move to `k`
) * 2

Scala eklentisi, bu örneği reset, her birinden başlatılan hesaplamanın (giriş argümanı içinde ) işlev (örn. ) shiftGirdisi resetile değiştirileceği şekilde fdönüştürür shift.

Değiştirilen hesaplama bir işleve kaydırılır (yani taşınır) k. Fonksiyonu fişlevi girişleri k, k içeren ikame hesaplama, kgirdileri x: Int, ve hesaplama kyerine geçer shift(f)ile x.

f(k) * 2
def k(x: Int): Int = x + 1

Aşağıdakilerle aynı etkiye sahiptir:

k(k(k(7))) * 2
def k(x: Int): Int = x + 1

Türünü not Intgiriş parametresinin x(type imza yani kgiriş parametresinin türü imzası ile verildi) f.

Kavramsal olarak eşdeğer soyutlama ile ödünç alınmış başka bir örnek, yani readfonksiyon girdisi shift:

def read(callback: Byte => Unit): Unit = myCallback = callback
reset {
  val byte = "byte"

  val byte1 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "1 = " + byte1)
  val byte2 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "2 = " + byte2)
}

Bunun mantıksal eşdeğerine çevrileceğine inanıyorum:

val byte = "byte"

read(callback)
def callback(x: Byte): Unit {
  val byte1 = x
  println(byte + "1 = " + byte1)
  read(callback2)
  def callback2(x: Byte): Unit {
    val byte2 = x
    println(byte + "2 = " + byte1)
  }
}

Umarım bu, bu iki örneğin daha önce sunumu ile biraz karıştırılan tutarlı ortak soyutlamayı aydınlatır. Örneğin, kanonik birinci örnek araştırma makalesinde benim adım yerine anonim bir işlev olarak sunuldu f, bu nedenle bazı okuyucular readiçin ödünç alınan ikinci örnektekine soyut olarak benzer olduğu hemen anlaşılmadı .

Böylelikle sınırlandırılmış sürdürmeler, "beni dışarıdan arıyorsun reset" dan "seni içeriden ararım" a giden bir kontrolün tersine çevrilmesi illüzyonunu yaratırlar reset.

Döndürme türünün döndürme türü ile aynı olması gerekir f, ancak kgerekli değildir, resetyani ile aynı türü döndürdüğü sürece fherhangi bir dönüş türünü bildirme özgürlüğüne sahiptir . Aynen ve (ayrıca aşağıya bakın).kfresetreadcaptureENV


Sınırlandırılmış continuations devlet, örneğin kontrolünde invert değil dolaylı yapmak readve callbacksaf fonksiyonlar değildir. Bu nedenle, arayan kişi referans olarak şeffaf ifadeler oluşturamaz ve bu nedenle amaçlanan zorunlu semantik üzerinde bildirimsel (diğer adıyla şeffaf) kontrole sahip değildir .

Sınırlandırılmış devamlılıklarla saf fonksiyonları açıkça elde edebiliriz.

def aread(env: ENV): Tuple2[Byte,ENV] {
  def read(callback: Tuple2[Byte,ENV] => ENV): ENV = env.myCallback(callback)
  shift(read)
}
def pure(val env: ENV): ENV {
  reset {
    val (byte1, env) = aread(env)
    val env = env.println("byte1 = " + byte1)
    val (byte2, env) = aread(env)
    val env = env.println("byte2 = " + byte2)
  }
}

Bunun mantıksal eşdeğerine çevrileceğine inanıyorum:

def read(callback: Tuple2[Byte,ENV] => ENV, env: ENV): ENV =
  env.myCallback(callback)
def pure(val env: ENV): ENV {
  read(callback,env)
  def callback(x: Tuple2[Byte,ENV]): ENV {
    val (byte1, env) = x
    val env = env.println("byte1 = " + byte1)
    read(callback2,env)
    def callback2(x: Tuple2[Byte,ENV]): ENV {
      val (byte2, env) = x
      val env = env.println("byte2 = " + byte2)
    }
  }
}

Açık ortam nedeniyle gürültü artıyor.

Teğetsel olarak, Scala'nın Haskell'in küresel tip çıkarımına sahip olmadığını ve bu nedenle bildiğim kadarıyla bir devlet monadına örtük kaldırmayı destekleyemeyeceğini unit(açık ortamı gizlemek için olası bir strateji olarak), çünkü Haskell'in küresel (Hindley-Milner) tip çıkarımı elmas çoklu sanal kalıtımın desteklenmemesine bağlıdır .


Ben öneriyorum o reset/ shiftdeğiştirilebilir delimit/ ' replace. Ve Kongre tarafından, o fve readolmak withve kve callbackolmak replaced, captured, continuation, veya callback.
Shelby Moore III

with bir anahtar kelimedir. Not: Sıfırlamalarınızdan bazılarında () var ve bu da {} Her neyse harika yazım!
nafg

@nafg teşekkür ederim, replacementonun yerine teklif edeceğim with. Afaik ()de izin var mı? Afaik, {}olduğu "kapanışları için Scala'nın hafif sözdizimi" altta yatan bir işlev çağrısı saklandığını. Örneğin, Daniel'ısequence nasıl yeniden yazdığıma bakın (kodun hiçbir zaman derlenmediğini veya test edilmediğini unutmayın, bu yüzden lütfen beni düzeltmekten çekinmeyin).
Shelby Moore III

1
Bir blok - yani birden çok ifade içeren bir ifade - kaşlı ayraçlar gerektirir.
nafg

@nafg, doğru. Afaik shift reset, anahtar sözcükler değil, kütüphane işlevleridir. Bu nedenle {}veya işlev yalnızca bir parametre beklediğinde() kullanılabilir . Scala parametre türü ise Yan adına parametreleri (Scala Programlama, 2. baskı. Ug bölümü "9.5 Kontrol soyutlamalar" bölümüne bakın. 218), sahip elimine edilebilir. İsme göre değil varsayıyorum çünkü blok daha önce değerlendirilmelidir , ancak birden çok ifadeye ihtiyacım var . Kullanımım doğru, çünkü açıkça bir işlev türü giriyor. () => ...() =>Unitreset{}shift
Shelby Moore III

8

Devam, daha sonra çağrılacak olan bir hesaplamanın durumunu yakalar.

Kaydırma ifadesini terk etme ile sıfırlama ifadesini bir işlev olarak bırakma arasındaki hesaplamayı düşünün. Kaydırma ifadesinin içinde bu işleve k adı verilir, devamıdır. Onu dolaştırabilir, daha sonra çağırabilirsiniz, hatta birden fazla kez.

Sıfırlama ifadesinin döndürdüğü değerin => 'den sonraki shift ifadesinin içindeki ifadenin değeri olduğunu düşünüyorum, ancak bu konuda tam olarak emin değilim.

Dolayısıyla, devam ettirmelerle, bir işlevde oldukça keyfi ve yerel olmayan bir kod parçasını toplayabilirsiniz. Bu, eşleme veya geriye dönük izleme gibi standart olmayan kontrol akışını uygulamak için kullanılabilir.

Bu nedenle, devamlılıklar sistem düzeyinde kullanılmalıdır. Bunları uygulama kodunuz aracılığıyla serpmek, kabuslar için kesin bir reçete olacaktır, goto kullanan en kötü spagetti kodunun olabileceğinden çok daha kötüdür.

Feragatname: Scala'daki devam ettirmeler hakkında derinlemesine bir anlayışa sahip değilim, sadece örneklere bakarak ve Scheme'deki devamları bilmekten çıkarım yaptım.


5

Benim açımdan en iyi açıklama burada verilmiştir: http://jim-mcbeath.blogspot.ru/2010/08/delimited-continuation.html

Örneklerden biri:

Kontrol akışını biraz daha net görmek için şu kod parçacığını çalıştırabilirsiniz:

reset {
    println("A")
    shift { k1: (Unit=>Unit) =>
        println("B")
        k1()
        println("C")
    }
    println("D")
    shift { k2: (Unit=>Unit) =>
        println("E")
        k2()
        println("F")
    }
    println("G")
}

İşte yukarıdaki kodun ürettiği çıktı:

A
B
D
E
G
F
C

1

- Başka (daha yeni 2016 Mayıs) Scala continuations üzerine makale:
" Scala Zamanda Yolculuk: CPS Scala (Scala'nın devamı) " tarafından Shivansh Srivastava ( shiv4nsh) .
Ayrıca atıfta Jim McBeath 'ın makalesinde belirtilen Dmitry Bespalov s' cevabı .

Ama ondan önce, Devamları şöyle açıklıyor:

Devam, bir bilgisayar programının kontrol durumunun soyut bir temsilidir .
Yani aslında anlamı, işlemin yürütülmesinde belirli bir noktada hesaplama sürecini temsil eden bir veri yapısı olmasıdır; oluşturulan veri yapısına çalışma zamanı ortamında gizlenmek yerine programlama dili ile erişilebilir.

Daha fazla açıklamak için en klasik örneklerden birine sahip olabiliriz,

Diyelim ki mutfakta buzdolabının önünde bir sandviç düşünüyorsunuz. Oraya devam edin ve cebinize yapıştırın.
Sonra buzdolabından biraz hindi ve ekmek alırsın ve kendine bir sandviç yaparsın, şimdi tezgahta duruyor.
Cebinizdeki devamı çağırırsınız ve kendinizi yine buzdolabının önünde bir sandviç düşünürken bulursunuz. Ama neyse ki tezgahta bir sandviç var ve onu yapmak için kullanılan tüm malzemeler gitmiş. Yani yiyorsun. :-)

Bu açıklamada, program verilerinin birsandwich parçasıdır (ör., Yığın üzerindeki bir nesne) ve bir " " rutini çağırmak ve sonra geri dönmek yerine, kişi " " rutin olarak adlandırdı , bu da sandviçi yaratır ve ardından yürütmeye devam eder hariç tutulmuş.make sandwichmake sandwich with current continuation

Scala 2.11.0-RC1 için Nisan 2014'te duyurulduğu gibi

Şu modülleri devralacak bakımcılar arıyoruz: scala-swing , scala-continations .
2.12 yeni bakıcı bulunmazsa bunları içermeyecektir .
Muhtemelen diğer modülleri (scala-xml, scala-ayrıştırıcı-birleştiricileri) korumaya devam edeceğiz, ancak yardım yine de büyük beğeni topluyor.


0

Anlamlı Örneklerle Scala Devamları

from0to10Yineleme fikrini 0'dan 10'a ifade eden tanımlayalım :

def from0to10() = shift { (cont: Int => Unit) =>
   for ( i <- 0 to 10 ) {
     cont(i)
   }
}

Şimdi,

reset {
  val x = from0to10()
  print(s"$x ")
}
println()

baskılar:

0 1 2 3 4 5 6 7 8 9 10 

Aslında şunlara ihtiyacımız yok x:

reset {
  print(s"${from0to10()} ")
}
println()

aynı sonucu yazdırır.

Ve

reset {
  print(s"(${from0to10()},${from0to10()}) ")
}
println()

tüm çiftleri yazdırır:

(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,8) (0,9) (0,10) (1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7) (1,8) (1,9) (1,10) (2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7) (2,8) (2,9) (2,10) (3,0) (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7) (3,8) (3,9) (3,10) (4,0) (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7) (4,8) (4,9) (4,10) (5,0) (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7) (5,8) (5,9) (5,10) (6,0) (6,1) (6,2) (6,3) (6,4) (6,5) (6,6) (6,7) (6,8) (6,9) (6,10) (7,0) (7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7) (7,8) (7,9) (7,10) (8,0) (8,1) (8,2) (8,3) (8,4) (8,5) (8,6) (8,7) (8,8) (8,9) (8,10) (9,0) (9,1) (9,2) (9,3) (9,4) (9,5) (9,6) (9,7) (9,8) (9,9) (9,10) (10,0) (10,1) (10,2) (10,3) (10,4) (10,5) (10,6) (10,7) (10,8) (10,9) (10,10) 

Şimdi, bu nasıl çalışıyor?

Orada denilen kod , from0to10ve çağrı kodu . Bu durumda, takip eden bloktur reset. Çağrılan koda aktarılan parametrelerden biri, çağıran kodun hangi kısmının henüz çalıştırılmadığını gösteren bir dönüş adresidir (**). Çağıran kodun bu kısmı devamıdır . Çağrılan kod, o parametre ile karar verdiği her şeyi yapabilir: kontrolü ona iletebilir, yok sayabilir veya birden çok kez çağırabilir. Burada from0to100..10 aralığındaki her tam sayı için bu devamı çağırır.

def from0to10() = shift { (cont: Int => Unit) =>
   for ( i <- 0 to 10 ) {
     cont(i) // call the continuation
   }
}

Ama devamı nerede bitiyor? Bu önemlidir, çünkü returndevamtan sonuncusu kontrolü çağrılan koda döndürür from0to10. Scala'da resetbloğun bittiği yerde biter (*).

Şimdi devamının olarak ilan edildiğini görüyoruz cont: Int => Unit. Neden? from0to10Olarak çağırırız val x = from0to10()ve Intgiden değerin türüdür x. Unitsonraki bloğun resetdeğer döndürmemesi gerektiği anlamına gelir (aksi takdirde bir tür hatası olacaktır). Genel olarak 4 tip imza vardır: fonksiyon girişi, devam girişi, devam sonucu, fonksiyon sonucu. Dördü de çağrı içeriğiyle eşleşmelidir.

Yukarıda, değer çiftleri yazdırdık. Çarpım tablosunu yazdıralım. Ancak \nher satırdan sonra nasıl çıktı alırız ?

İşlev back, devamından onu çağıran koda kadar kontrol geri döndüğünde ne yapılması gerektiğini belirlememize izin verir.

def back(action: => Unit) = shift { (cont: Unit => Unit) =>
  cont()
  action
}

backönce devamını çağırır ve sonra eylemi gerçekleştirir .

reset {
  val i = from0to10()
  back { println() }
  val j = from0to10
  print(f"${i*j}%4d ") // printf-like formatted i*j
}

Aşağıdakileri yazdırır:

   0    0    0    0    0    0    0    0    0    0    0 
   0    1    2    3    4    5    6    7    8    9   10 
   0    2    4    6    8   10   12   14   16   18   20 
   0    3    6    9   12   15   18   21   24   27   30 
   0    4    8   12   16   20   24   28   32   36   40 
   0    5   10   15   20   25   30   35   40   45   50 
   0    6   12   18   24   30   36   42   48   54   60 
   0    7   14   21   28   35   42   49   56   63   70 
   0    8   16   24   32   40   48   56   64   72   80 
   0    9   18   27   36   45   54   63   72   81   90 
   0   10   20   30   40   50   60   70   80   90  100 

Pekala, şimdi biraz kafa karıştırmanın zamanı geldi. İki çağrı vardır from0to10. Birincinin devamı nedir from0to10? Bu çağırma aşağıdaki from0to10içinde ikili kod , ancak kaynak kodunda da atama deyimi içerir val i =. resetBloğun bittiği yerde biter, ancak bloğun sonu resetkontrolü birinciye geri döndürmez from0to10. resetBloğun sonu, kontrolü 2.'ye döndürür from0to10, bu da sonunda kontrolü döndürür backve bu, backkontrolü ilk çağrısına döndürür from0to10. İlk (evet! 1.!) from0to10Çıkıldığında, tüm resetbloktan çıkılır.

Kontrolü geri döndürmenin böyle bir yöntemi geri izleme olarak adlandırılır , çok eski bir tekniktir, en azından Prolog ve AI yönelimli Lisp türevlerinin zamanlarından bilinmektedir.

İsimler resetve shiftyanlış adlandırmalar. Bu isimler bitsel işlemler için bırakılmalıydı. resetdevam sınırlarını tanımlar shiftve çağrı yığınından bir devam alır.

Not (lar)

(*) Scala'da devam resetbloğun bittiği yerde biter. Bir başka olası yaklaşım, işlevin bittiği yerde bitmesine izin vermektir.

(**) Çağrılan kodun parametrelerinden biri, çağıran kodun hangi kısmının henüz çalıştırılmadığını gösteren bir dönüş adresidir. Scala'da bunun için bir dizi dönüş adresi kullanılır. Kaç? Bloğa girdikten sonra çağrı yığınına yerleştirilen tüm dönüş adresleri reset.


UPD Bölüm 2 Devamları Atma: Filtreleme

def onEven(x:Int) = shift { (cont: Unit => Unit) =>
  if ((x&1)==0) {
    cont() // call continuation only for even numbers
  }
}
reset {
  back { println() }
  val x = from0to10()
  onEven(x)
  print(s"$x ")
}

Bu şunu yazdırır:

0 2 4 6 8 10 

İki önemli işlemi hesaba katalım: devamı ( fail()) atıp kontrolü ona devretmek ( succ()):

// fail: just discard the continuation, force control to return back
def fail() = shift { (cont: Unit => Unit) => }
// succ: does nothing (well, passes control to the continuation), but has a funny signature
def succ():Unit @cpsParam[Unit,Unit] = { }
// def succ() = shift { (cont: Unit => Unit) => cont() }

succ()(Yukarıda) öğesinin her iki versiyonu da çalışır. shiftKomik bir imzaya sahip olduğu ortaya çıktı ve succ()hiçbir şey yapmamasına rağmen , tür dengesi için bu imzaya sahip olması gerekir.

reset {
  back { println() }
  val x = from0to10()
  if ((x&1)==0) {
    succ()
  } else {
    fail()
  }
  print(s"$x ")
}

beklendiği gibi yazdırır

0 2 4 6 8 10

Bir işlev içinde succ()gerekli değildir:

def onTrue(b:Boolean) = {
  if(!b) {
    fail()
  }
}
reset {
  back { println() }
  val x = from0to10()
  onTrue ((x&1)==0)
  print(s"$x ")
}

yine yazdırır

0 2 4 6 8 10

Şimdi, tanımlayalım onOdd()yoluyla onEven():

// negation: the hard way
class ControlTransferException extends Exception {}
def onOdd(x:Int) = shift { (cont: Unit => Unit) =>
  try {
    reset {
      onEven(x)
      throw new ControlTransferException() // return is not allowed here
    }
    cont()
  } catch {
    case e: ControlTransferException =>
    case t: Throwable => throw t
  }
}
reset {
  back { println() }
  val x = from0to10()
  onOdd(x)
  print(s"$x ")
}

Yukarıda, eğer xeşitse, bir istisna atılır ve devamı çağrılmaz; eğer xgarip, özel durum değildir ve devamı olarak adlandırılır. Yukarıdaki kod yazdırır:

1 3 5 7 9 
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.