İşlevler (ECMAScript)
Tek ihtiyacınız olan fonksiyon tanımları ve fonksiyon çağrıları. Dallara, koşullara, operatörlere veya yerleşik işlevlere ihtiyacınız yoktur. ECMAScript kullanarak bir uygulama göstereceğim.
İlk önce trueve adlı iki işlevi tanımlayalım false. Onları istediğimiz şekilde tanımlayabiliriz, tamamen keyfidirler, ancak daha sonra göreceğimiz gibi bazı avantajları olan çok özel bir şekilde tanımlayacağız:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
trusadece ikinci argümanını görmezden gelen ve ilkini döndüren iki parametreli bir işlevdir. flsAynı zamanda ilk argümanını görmezden gelen ve ikincisini döndüren iki parametreli bir işlevdir.
Neden kodlamak vermedi truve flsbu şekilde? Peki, bu şekilde, iki işlev yalnızca iki kavramı temsil etmiyor trueve falsehayır, aynı zamanda “seçim” kavramını da temsil ediyor, başka bir deyişle, aynı zamanda bir if/ then/ elseifadesidir! ifDurumu değerlendiririz ve thenbloğu ve elsebloğu argüman olarak iletiriz. Koşul değerlendirirse tru, thenbloğu döndürür, değerlendirirse bloğu flsdöndürür else. İşte bir örnek:
tru(23, 42);
// => 23
Bu döner 23ve bu:
fls(23, 42);
// => 42
42tam beklediğiniz gibi döner .
Ancak, bir kırışıklık var:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
Bu yazdırır hem then branch ve else branch! Neden?
Eh, o döndüren birinci argüman dönüş değeri, ancak değerlendirir ECMAScript sıkı ve her zaman işlevini çağırmadan önce bir işleve tüm argümanları değerlendirir beri, her iki argüman. IOW: o ilk argüman değerlendirir console.log("then branch")sadece döner undefinedve baskı yan etkisi vardır then branchkonsola, ve ikinci bir argüman, değerlendirir aynı zamanda, döner undefinedbir yan etki olarak konsola ve baskılar. Ardından, ilk döndürür undefined.
Bu kodlamanın icat edildiği cal-hesapta, bu bir problem değil: λ-hesap, saftır , yani herhangi bir yan etkisi yoktur; bu nedenle ikinci argümanın da değerlendirildiğini asla farketmezsiniz. Artı, λ-calculus tembeldir (veya en azından normal sıralar altında değerlendirilir), yani aslında gerekli olmayan argümanları değerlendirmez. Öyleyse, IOW: λ-calculus'ta ikinci argüman asla değerlendirilmeyecekti ve öyle olsaydı farketmezdik.
Ancak ECMAScript katıdır , yani her zaman tüm argümanları değerlendirir. Eh, aslında, her zaman değil: if/ then/ else, örneğin, thenkoşul trueyalnızca elsebu durumda dalı değerlendirir ve yalnızca koşul olduğunda dalı değerlendirir false. Ve biz bu davranışı kopyalamak istiyoruz iff. Neyse ki, ECMAScript tembel olmasa da, bir kod parçasının değerlendirmesini geciktirmenin bir yolu var, neredeyse her dilin yaptığı gibi: onu bir işleve sarın ve bu işlevi çağırmazsanız, kod asla idam edilme.
Böylece, her iki bloğu bir fonksiyona sardık ve sonunda döndürülen fonksiyonu çağırıyoruz:
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
yazdırır then branchve
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
yazdırır else branch.
Geleneksel if/ then/ elsebu şekilde uygulayabiliriz :
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
Yine, işlevi çağırırken bazı ekstra işlev sargısına iffve iffyukarıdaki gibi aynı işlev için ekstra işlev çağrısı parantezine ihtiyacımız var :
const iff = (cnd, thn, els) => cnd(thn, els)();
iff(tru, () => console.log("then branch"), () => console.log("else branch"));
// then branch
iff(fls, () => console.log("then branch"), () => console.log("else branch"));
// else branch
Şimdi bu iki tanımı yaptık, uygulayabiliyoruz or. İlk olarak, doğruluk tablosuna bakarız or: eğer ilk operand truthy ise, ifadenin sonucu ilk operand ile aynıdır. Aksi takdirde, ifadenin sonucu ikinci işlenenin sonucudur. Kısacası: eğer ilk işlenen ise trueilk işleneni geri veririz, aksi takdirde ikinci işleneni geri döndürürüz:
const orr = (a, b) => iff(a, () => a, () => b);
İşe yaradığını kontrol edelim:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
Harika! Ancak, bu tanım biraz çirkin görünüyor. Unutmayın truve flszaten kendi başlarına şartlı gibi davranıyorlar, bu yüzden gerçekten gerek yok iff, ve böylece tüm bu fonksiyonların tamamının sarılmasına gerek yok :
const orr = (a, b) => a(a, b);
İşte burada: or(artı diğer boolean operatörleri) sadece bir avuç satırda fonksiyon tanımları ve fonksiyon çağrıları dışında hiçbir şey tanımlanmamış:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els,
orr = (a , b ) => a(a, b),
nnd = (a , b ) => a(b, a),
ntt = a => a(fls, tru),
xor = (a , b ) => a(ntt(b), b),
iff = (cnd, thn, els) => cnd(thn, els)();
Ne yazık ki, bu uygulama oldukça işe yaramaz: ECMAScript'te geri dönen truya flsda hepsi dönen trueya da falsebu nedenle işlevlerimizle kullanamadığımız hiçbir işlev ya da işleç yok . Ama hala yapabileceğimiz çok şey var. Örneğin, bu tek başına bağlı bir listenin bir uygulamasıdır:
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
Nesneler (Scala)
Sen fark şey Peculiar olabilir: truve flsbir çift rolü, bunlar veri değerleri olarak hem hareket trueve falsefakat aynı zamanda, aynı zamanda bir koşullu ifadesi olarak hareket ederler. Onlar veri ve davranışlardır , bir… uhm… “şey”… veya (söylemeye cüret eder) nesnesine toplanırlar !
Gerçekten truve flsnesnelerdir. Ve eğer Smalltalk, Self, Newspeak veya diğer nesne yönelimli dilleri kullandıysanız, aynı şekilde booleans uyguladığını fark etmişsinizdir. Böyle bir uygulamayı Scala'da göstereceğim:
sealed abstract trait Buul {
def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): T
def &&&(other: ⇒ Buul): Buul
def |||(other: ⇒ Buul): Buul
def ntt: Buul
}
case object Tru extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): U = thn
override def &&&(other: ⇒ Buul) = other
override def |||(other: ⇒ Buul): this.type = this
override def ntt = Fls
}
case object Fls extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): V = els
override def &&&(other: ⇒ Buul): this.type = this
override def |||(other: ⇒ Buul) = other
override def ntt = Tru
}
object BuulExtension {
import scala.language.implicitConversions
implicit def boolean2Buul(b: ⇒ Boolean) = if (b) Tru else Fls
}
import BuulExtension._
(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3
Bu BTW'nin, Polimorfizmi Yeniden Düzenlemeyle Koşullu Değiştirme'nin daima işe yaramasının nedeni budur: programınızdaki her koşullu, polimorfik mesaj gönderme ile her zaman yerine geçebilir, çünkü az önce gösterdiğimiz gibi, polimorfik mesaj gönderme, koşulluları sadece uygulayarak değiştirebilir. Smalltalk, Self ve Newspeak gibi diller bunun varoluş kanıtıdır, çünkü bu dillerin şartsız bile olması şarttır. (Ayrıca, döngülere, BTW'ye ya da polimorfik mesaj gönderme, yani sanal yöntem çağrıları haricinde herhangi bir dilde yerleşik kontrol yapılarına sahip değillerdir .)
Desen Eşleştirme (Haskell)
orDesen eşleştirmeyi veya Haskell'in kısmi işlev tanımları gibi bir şey kullanarak da tanımlayabilirsiniz :
True ||| _ = True
_ ||| b = b
Elbette, desen eşleştirme bir koşullu yürütme biçimidir, ancak daha sonra yine nesne yönelimli ileti gönderimidir.