İş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 true
ve 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;
tru
sadece ikinci argümanını görmezden gelen ve ilkini döndüren iki parametreli bir işlevdir. fls
Aynı zamanda ilk argümanını görmezden gelen ve ikincisini döndüren iki parametreli bir işlevdir.
Neden kodlamak vermedi tru
ve fls
bu şekilde? Peki, bu şekilde, iki işlev yalnızca iki kavramı temsil etmiyor true
ve false
hayır, aynı zamanda “seçim” kavramını da temsil ediyor, başka bir deyişle, aynı zamanda bir if
/ then
/ else
ifadesidir! if
Durumu değerlendiririz ve then
bloğu ve else
bloğu argüman olarak iletiriz. Koşul değerlendirirse tru
, then
bloğu döndürür, değerlendirirse bloğu fls
döndürür else
. İşte bir örnek:
tru(23, 42);
// => 23
Bu döner 23
ve bu:
fls(23, 42);
// => 42
42
tam 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 undefined
ve baskı yan etkisi vardır then branch
konsola, ve ikinci bir argüman, değerlendirir aynı zamanda, döner undefined
bir 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, then
koşul true
yalnızca else
bu 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 branch
ve
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
yazdırır else branch
.
Geleneksel if
/ then
/ else
bu ş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 iff
ve iff
yukarı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 true
ilk 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 tru
ve fls
zaten 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 tru
ya fls
da hepsi dönen true
ya da false
bu 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: tru
ve fls
bir çift rolü, bunlar veri değerleri olarak hem hareket true
ve false
fakat 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 tru
ve fls
nesnelerdir. 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)
or
Desen 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.