Anlamlı Örneklerle Scala Devamları
from0to10
Yineleme 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 , from0to10
ve ç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 from0to10
0..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ü return
devamtan sonuncusu kontrolü çağrılan koda döndürür from0to10
. Scala'da reset
bloğun bittiği yerde biter (*).
Şimdi devamının olarak ilan edildiğini görüyoruz cont: Int => Unit
. Neden? from0to10
Olarak çağırırız val x = from0to10()
ve Int
giden değerin türüdür x
. Unit
sonraki bloğun reset
değ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 \n
her 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 from0to10
içinde ikili kod , ancak kaynak kodunda da atama deyimi içerir val i =
. reset
Bloğun bittiği yerde biter, ancak bloğun sonu reset
kontrolü birinciye geri döndürmez from0to10
. reset
Bloğun sonu, kontrolü 2.'ye döndürür from0to10
, bu da sonunda kontrolü döndürür back
ve bu, back
kontrolü ilk çağrısına döndürür from0to10
. İlk (evet! 1.!) from0to10
Çıkıldığında, tüm reset
bloktan çı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 reset
ve shift
yanlış adlandırmalar. Bu isimler bitsel işlemler için bırakılmalıydı. reset
devam sınırlarını tanımlar shift
ve çağrı yığınından bir devam alır.
Not (lar)
(*) Scala'da devam reset
bloğ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. shift
Komik 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 x
eşitse, bir istisna atılır ve devamı çağrılmaz; eğer x
garip, özel durum değildir ve devamı olarak adlandırılır. Yukarıdaki kod yazdırır:
1 3 5 7 9