(bu yazidaki resimlerin guncellenmesi gerekiyor)
Sıradan bir iş günü.
JavaScript’te tablolarımızdan birisine sayfalar arasında geçiş yaparken veri indir/kaldır yapma kabiliyeti ekliyorum (halk arasında buna "pagination" diyorlar).
retrieveItems
diye ve drawItems
diye birer fonksiyon yazdım. retrieveItemsAndDraw
yazıp çözüme ulaşmak üzereyken şu kodu yazdığımı farkediyorum:
const retrieveAndDrawItems = R.compose(drawItems, retrieveItems)
Ekip arkadaşlarımdan birisi bu satırda n’aptığımı sorduğunda "Basit!" diye cevaplıyorum:
Bu iki fonksiyonu birleştirip yeni bir fonksiyon oluşturuyorum.
De ki
f(x) = 2x + 3
demek istiyorum. Benim deadd3
vemul2
isimli iki fonksiyonum var:add3 = x => x + 3 mul2 = x => x * 2 compose2 = (f, g) => x => f(g(x)) f = compose2(add3, mul2) // f = x => add3(mul2(x))
Peki ya 2 fonksiyondan fazlasını birleştirmek istiyor isem? İşte
compose
bunu yapabiliyor!Hemencecik kendimiz yazalım bu
compose
un nasıl olabileceğini:fonksiyonları birleştirmeye calışan masum bir kodcompose = (...functions) => x0 => (functions .reverse() (1) .reduce((x, f) => f(x), x0) ) f = compose(add3, mul2)
1 compose(f, g)(x)
demekg(f(x))
değil def(g(x))
demek oldugu icin önce listeyi tersine çeviriyorum. Çünküreduce
sondan başa doğru değil, baştan sona doğru gidiyor.Gördün mü?
Derken fonksiyonu bir daha çalıştırıyorum:f(5) //16
Sonuç mu değişti?!f(5) //13 f(5) //16 f(5) //13 f(5) //16 ...
Bu compose
fonksiyonu bozuk!
Peki siz sorunu farkettiniz mi?
Array
'in reverse
fonksiyonu, üzerinde çağırıldığı array
'i değiştiriyor.
Yani reverse
fonksiyonu mutative
.
Mutative
in, yani değiştirebilirlik
in ne olduğunu açıklayacağım önce inşaa etmeye calıştığım bu bileşke fonksiyonunun ne olduğunu hatırlayalım.
Bileşke fonksiyon
Bileşke fonksiyonu matematikten hatırlıyorsunuz değil mi? Birden fazla fonksiyonu tek bir fonksiyon altinda birleştirmeye yarıyor. Bileşke, birleştirebilme (yani "composability"); fonksiyonel programlamada "modülerlik" elde edebilmek için yegane silahımız.
\$g(x) = 3x\$
\$(f@g)(x) = f(g(x)) = (3x) + 2\$
f = x => x + 2
g = x => x * 3
fog = x => f(g(x))
Bileşke fonksiyonlara Programming with Types adlı (henüz yayınlanmamış) yazımda daha detayli olarak değindim.
Problemleri fonksiyonlara parçalayıp, fonksiyonların birleşimi şeklinde çözümler üretmek fonksiyonel programlamadaki en temel yaklaşımlardan. Bu yüzdendir ki Haskell
'de iki fonksiyonun bileşkesini almak .
yazmak kadar kolay:
fog = (f . g)
JavaScript’te ise bunu kendimiz inşaa etmek durumundayız:
//compose :: (('b -> 'c), ('a -> 'b)) -> 'a -> 'c (1)
compose = (f, g) => x => f(g(x))
// f :: int -> int
f = x => x + 2
// g :: int -> int
g = g => x * 3
// fog :: int -> int
fog = compose(f, g)
// stringLength :: string -> int
stringLength = s => s.length
// nameOfStudent :: student -> string
nameOfStudent = s => s.name
// nameLengthOfStudent :: student -> int
nameLengthOfStudent = compose(stringLength, nameOfStudent)
1 | Fonksiyonların girdi/çıktı tiplerini tanımlamak için kullandığım bu gösterim-şeklinden Programming with Types yazımda detaylıca bahtettim: nameLengthOfStudent örneğindeki gibi birleştirilen iki fonksiyonun girdi/çıktı tiplerinin birbiriyle uyum saglaması önemli. Ve compose 'a verdiğimiz fonksiyonların tiplerini biliyorsak o zaman compose un bize döndüğü fonksiyonun tipini de bilebiliriz. |
Hatta ayni şeyi OCaml’de yapıp compose
'un tipini sorgulatacak olursam:
OCaml’da bir
Ama OCaml’da
|
Kakoune’da imleci istediğim fonksiyonun üzerine getirip :lsp-hover dediğimde, o değerin tipini ve (var ise) tanımını görebiliyorum.
|
Ben de zaten 2’den fazla fonksiyonu birleştiren bir kodu yazmaya calışmış, ama mutative
olduğu için sorun yaşamıştım. Şimdi de mutative
in ne olduğunu irdeleyelim…
Değiştirebilir (Mutative) Fonksiyon
Değiştirebilir (mutative) fonksiyon adi ne diyorsa onu yapiyor; değiştirebilir fonksiyon programın vaziyetini değiştir(ebil)iyor.
Vaziyet tanımını daha açık hale getirmek adına önce "fonksiyon"un tanımını yapmak lazım.
Ruby’de mutative fonksiyonlarin adlarının sonunda ! oluyor. Bir fonksiyonun adı reverse değil de reverse! ise biliyorsunuz ki bu fonksiyon bir yerlere müdahele ediyor.
|
Saf Fonksiyon
Matematikte (hatırlarsanız) fonksiyon bir kümeden bir kümeye giden bir bağıntıya denir.
Bu fonksiyona fab
ismini verip JavaScript’te yazalım:
const A2B = { a: 1, b: 2, c: 2 }
fab = x => A2B[x]
fab
A’dan B’ye giden bir fonksiyon olduğu gibi, f(x) = 2x + 3
gibi tam sayılarda tanımlı bir fonksiyon da olabilir.
f(x) = 2x + 3
fonksiyonufab
ve f(x) = 2x + 3
gibi tek yaptığı bir kümeyi başka bir kümeye bağlamak olan fonksiyonlara programlamada saf denir. Saf fonksiyon tanımı yan etki açıklanınça daha anlaşılır olacak:
Yan Etki
fab
fonksiyonu A kümesini B kümesine bağlamaktan fazlasını yapıyor olsun, mesela bir web sayfasına yazı yazsın:
fabAndEdit = x => {
document.querySelector("#text").innerHTML = x
return A2B[x]
}
Bu innerHTML
'nin atanışı gibi, girdi-çıktı bağıntısı dışında olan etkilere yan etki diyoruz.
Yan etki pröğramın vaziyetini değiştiren her şey olarak da düşünülebilir.
Vaziyet (state) pröğramın/bilgisayarın o anki halidir. Örneğin siz klavyede bir tuşa basınca bilgisayarin vaziyetini bir-tuşu-basılı-bilgisayar vaziyetine dönüştürürsünüz. Bir fonksiyonda bir metin-kutusuna (textbox) müdahele ederseniz artık programın vaziyeti değişmiştir, o metin-kutusunda evvelde yazmayan bir şey yazıyordur; başka bir kod o metin-kutusunun değerini degiştikten sonra okuyacak olursa, değiştikten önce okuyacağından başka bir değer bulacaktır.
-
Programın vaziyetine müdahele etmeyen fonksiyonlara saf fonksiyon denir.
-
Bir fonksiyon programın vaziyetine müdahele ediyor ise, bu müdaheleye yan etki denir.
-
Saf fonksiyonlarin yan etkisi yoktur. Yan etkisi olan fonksiyonlar saf değildir.
-
Saf fonksiyonlar ne zaman cağırılırsa çağırılsın, her zaman aynı girdi için aynı sonucu verir.
-
Değiştirebilir fonksiyonların yan etkileri vardır. Değiştirebilir fonksiyonlar saf değildir.
Değiştirebilir Fonksiyonlarin Zihinsel Yükü
Bir insanın short-term memory’sinde tutabileceği sey sayısı 7±2’dir
The Magical Number Seven
Zihinsel kapasitemiz sınırsız değil; zaten benim gibiyseniz bir kulağınızla podcast dinliyorsunuz, bir kulağınızla da "acaba benimle ilgili bir şey mi konuşuluyor" diye toplantı odasından gelen sesleri dinler iken kod yazıyorsunuz.
Bu haldeyken yazdığınız koddaki her bir fonksiyon cağrısının acaba programın vaziyetini nasıl değiştirdiğini, global değişkenlere nasıl müdahele ettiğini, verdiğiniz argümanlari değiştirip değiştirmedigini kendinize zihinsel yük edinmemekten kaçınmak gerek.
Kodunuzu olabildiğince saf yazmak, Yan etkileri (var ise) sınırlı kullanmak kodun yazılabilirliğini, okunabilirliğini ve sürdürülebilirliğini arttıracaktır.
(Zaten yan etkisi olmayan program olmaz; kullanıcıdan girdi almayan ve kullanıcıya çıktı vermeyen programın manası yoktur. Felsefe yapıp çıktısı olmayan programın var olmadığını bile iddia edebiliriz).
Değişebilirlik (Mutability), Değişemezlik (Immutability)
Aslında değiştirebilirlik ile iç içe bir kavram. JavaScript’teki Array ile aşağıda yazdığım ImmutableArray’in davranışını bi' karşılaştırın:
Array’in push
fonksiyonunda programda varolan değerleri deşiştirir iken ImmutableArray
'in push
fonksiyonu varolan değerlere asla dokunmuyor. ImmutableArray gibi kendi vaziyetini koruyan nesnelere/değişken-tiplerine değişemez (immutable) deniyor. Değişemez değerler kullanıldıkları fonksiyonların saflaşmasını da teşvik ediyor.
Fonksiyonel programlama dillerinde değişemez değerleri kullanmak için çabalamaya gerek yok; doğal ve kolay bir şekilde destekleniyorlar. Mesela OCaml’da imperative dillerden alışık olunan değişken yok. Zaten matematikteki değişken de aslında ismiyle çakışıyor; OCaml’da Record kullanımında ise: ocaml mutable records
Siz de immutable kod yazmak istiyorsanız ama ekibinizde JavaScript’i terkedip OCaml yazmak mümkün değilse immutableJS kütüphanesi sayesinde primitive değerler olmasa da liste, küme, map gibi veri tiplerini immutable kullanabilirsiniz. "Yok istemem" diyorsanız da o zaman size bol |
Ee… Sonuç?
Sonuç basit, çalışan bir compose kodu:
reverseList = l => l.slice().reverse()
compose = (...fs) => x0 => reverseList(fs).reduce((x, f) => f(x), x0)
Tabii bana R.compose
'un n’olduğunu soran çocuk tüm bu hikayeyi dinlemeye kalmadı… Olsun, yarın kilitlerim ben onu aynı çok-bilmişlikle~