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)
}
}
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 ")
}
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()
}
}
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()):
def fail() = shift { (cont: Unit => Unit) => }
def succ():Unit @cpsParam[Unit,Unit] = { }
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():
class ControlTransferException extends Exception {}
def onOdd(x:Int) = shift { (cont: Unit => Unit) =>
try {
reset {
onEven(x)
throw new ControlTransferException()
}
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