Fonksiyonel Programlamaya Giriş: JavaScript Paradigmaları
Yayınlanan: 2022-03-11İşlevsel programlama, durumu ve verileri değiştirmeden ifadeleri ve işlevleri kullanarak bilgisayar programları oluşturmaya yönelik bir paradigmadır.
Bu kısıtlamalara uyarak, işlevsel programlama, anlaşılması daha açık ve hataya karşı daha dayanıklı kod yazmayı amaçlar. Bu, kodun izlenmesini zorlaştıran akış kontrol ifadeleri ( for
, while
, break
, continue
, goto
) kullanmaktan kaçınılarak elde edilir. Ayrıca, işlevsel programlama, hata yapma olasılığı daha düşük olan saf, deterministik işlevler yazmamızı gerektirir.
Bu yazımızda JavaScript kullanarak fonksiyonel programlama yapmaktan bahsedeceğiz. Bunu mümkün kılan çeşitli JavaScript yöntemlerini ve özelliklerini de keşfedeceğiz. Sonunda, fonksiyonel programlama ile ilgili farklı kavramları keşfedeceğiz ve neden bu kadar güçlü olduklarını göreceğiz.
İşlevsel programlamaya geçmeden önce, saf ve saf olmayan işlevler arasındaki farkı anlamak gerekir.
Saf ve Saf Olmayan İşlevler
Saf fonksiyonlar bir miktar girdi alır ve sabit bir çıktı verir. Ayrıca dış dünyada hiçbir yan etkiye neden olmazlar.
const add = (a, b) => a + b;
Burada, add
saf bir fonksiyondur. Bunun nedeni, sabit a
ve b
değeri için çıktının her zaman aynı olacağıdır.
const SECRET = 42; const getId = (a) => SECRET * a;
getId
saf bir işlev değildir. Bunun nedeni, çıktıyı hesaplamak için global SECRET
değişkenini kullanmasıdır. SECRET
değişecek olsaydı, getId
işlevi aynı giriş için farklı bir değer döndürür. Bu nedenle, saf bir fonksiyon değildir.
let id_count = 0; const getId = () => ++id_count;
Bu aynı zamanda saf olmayan bir işlevdir ve bu da birkaç nedenden dolayı—(1) çıktısını hesaplamak için yerel olmayan bir değişken kullanır ve (2) o değişkende bir değişkeni değiştirerek dış dünyada bir yan etki yaratır. Dünya.
Bu kodda hata ayıklamak zorunda kalırsak, bu zahmetli olabilir.
id_count
şu anki değeri nedir? id_count
başka hangi işlevler değiştiriyor? id_count
dayanan başka işlevler var mı?
Bu sebeplerden dolayı, fonksiyonel programlamada sadece saf fonksiyonları kullanıyoruz.
Pure fonksiyonların bir başka faydası da paralelleştirilip hafızaya alınabilmeleridir. Önceki iki fonksiyona bir göz atın. Bunları paralelleştirmek veya ezberlemek imkansızdır. Bu, performanslı kod oluşturmaya yardımcı olur.
Fonksiyonel Programlamanın İlkeleri
Şimdiye kadar fonksiyonel programlamanın birkaç kurala bağlı olduğunu öğrendik. Bunlar aşağıdaki gibidir.
- Verileri değiştirmeyin
- Saf işlevleri kullanın: sabit girdiler için sabit çıktı ve yan etki yok
- İfadeleri ve bildirimleri kullanın
Bu koşulları sağladığımızda kodumuzun işlevsel olduğunu söyleyebiliriz.
JavaScript'te Fonksiyonel Programlama
JavaScript'in zaten işlevsel programlamayı etkinleştiren bazı işlevleri vardır. Örnek: String.prototype.slice, Array.protoype.filter, Array.prototype.join.
Öte yandan, Array.prototype.forEach, Array.prototype.push saf olmayan işlevlerdir.
Array.prototype.forEach
tasarım gereği saf olmayan bir işlev olmadığı iddia edilebilir, ancak bir düşünün - yerel olmayan verileri mutasyona uğratmak veya yan etkiler yapmak dışında onunla hiçbir şey yapmak mümkün değildir. Bu nedenle, onu saf olmayan işlevler kategorisine koymakta bir sakınca yoktur.
Ayrıca JavaScript, herhangi bir veriyi mutasyona uğratmayacağımız için işlevsel programlama için mükemmel olan bir const bildirimine sahiptir.
JavaScript'te Saf İşlevler
JavaScript tarafından verilen bazı saf işlevlere (yöntemlere) bakalım.
filtre
Adından da anlaşılacağı gibi, bu diziyi filtreler.
array.filter(condition);
Buradaki koşul, dizinin her bir öğesini alan bir işlevdir ve öğenin tutulup tutulmayacağına karar vermeli ve bunun için doğru boole değerini döndürmelidir.
const filterEven = x => x%2 === 0; [1, 2, 3].filter(filterEven); // [2]
filterEven
öğesinin saf bir işlev olduğuna dikkat edin. Saf olmayan olsaydı, tüm filtre çağrısını safsız yapardı.
Harita
map
, dizinin her öğesini bir işleve eşler ve işlev çağrılarının dönüş değerlerine dayalı olarak yeni bir dizi oluşturur.
array.map(mapper)
mapper
, bir dizinin bir öğesini girdi olarak alan ve çıktıyı döndüren bir işlevdir.
const double = x => 2 * x; [1, 2, 3].map(double); // [2, 4, 6]
Azaltmak
reduce
diziyi tek bir değere indirger.
array.reduce(reducer);
reducer
, dizideki birikmiş değeri ve sonraki öğeyi alan ve yeni değeri döndüren bir işlevdir. Birbiri ardına dizideki tüm değerler için bu şekilde adlandırılır.
const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem [1, 2, 3].reduce(sum); // 6
concat
concat
, yeni bir dizi oluşturmak için mevcut bir diziye yeni öğeler ekler. Push()'un verileri mutasyona uğratması ve bu da onu saf olmayan hale getirmesi anlamında push()
push()
'tan farklıdır.

[1, 2].concat([3, 4]) // [1, 2, 3, 4]
Aynı işlemi spread operatörünü kullanarak da yapabilirsiniz.
[1, 2, ...[3, 4]]
nesne.atama
Object.assign
, sağlanan nesnedeki değerleri yeni bir nesneye kopyalar. İşlevsel programlama değişmez verilere dayandığından, onu mevcut nesnelere dayalı yeni nesneler yapmak için kullanırız.
const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2
ES6'nın ortaya çıkmasıyla birlikte bu, yayılma operatörü kullanılarak da yapılabilir.
const newObj = {...obj};
Kendi Saf İşlevinizi Yaratmak
Saf fonksiyonumuzu da yaratabiliriz. Bir diziyi n
kez çoğaltmak için bir tane yapalım.
const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);
Bu işlev, bir dizeyi n
kez çoğaltır ve yeni bir dize döndürür.
duplicate('hooray!', 3) // hooray!hooray!hooray!
Üst Düzey Fonksiyonlar
Üst düzey işlevler, bir işlevi argüman olarak kabul eden ve bir işlev döndüren işlevlerdir. Genellikle, bir işlevin işlevselliğine katkıda bulunmak için kullanılırlar.
const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; };
Yukarıdaki örnekte, bir işlevi alan ve sarmalanmış işlev çalışmadan önce bir iletiyi günlüğe kaydeden bir işlev döndüren bir withLog
üst düzey işlevi oluşturuyoruz.
const add = (a, b) => a + b; const addWithLogging = withLog(add); addWithLogging(3, 4); // calling add // 7
withLog
HOF, diğer işlevlerle de kullanılabilir ve herhangi bir çakışma veya ekstra kod yazmadan çalışır. Bu bir HOF'un güzelliğidir.
const addWithLogging = withLog(add); const hype = s => s + '!!!'; const hypeWithLogging = withLog(hype); hypeWithLogging('Sale'); // calling hype // Sale!!!
Bir birleştirme işlevi tanımlamadan da çağrılabilir.
withLog(hype)('Sale'); // calling hype // Sale!!!
köri
Körleme, birden çok argümanı bir veya daha fazla üst düzey işlev düzeyine alan bir işlevi parçalamak anlamına gelir.
add
fonksiyonunu ele alalım.
const add = (a, b) => a + b;
Kör edeceğimiz zaman, argümanları aşağıdaki gibi çoklu düzeylere dağıtarak yeniden yazarız.
const add = a => { return b => { return a + b; }; }; add(3)(4); // 7
Körlemenin faydası not almadır. Artık bir işlev çağrısında belirli argümanları not alabiliriz, böylece daha sonra çoğaltma ve yeniden hesaplama olmadan yeniden kullanılabilirler.
// assume getOffsetNumer() call is expensive const addOffset = add(getOffsetNumber()); addOffset(4); // 4 + getOffsetNumber() addOffset(6);
Bu kesinlikle her iki argümanı da her yerde kullanmaktan daha iyidir.
// (X) DON"T DO THIS add(4, getOffsetNumber()); add(6, getOffsetNumber()); add(10, getOffsetNumber());
Kısa ve öz görünmesi için köri işlevimizi de yeniden biçimlendirebiliriz. Bunun nedeni, körleme işlevi çağrısının her düzeyinin tek satırlık bir dönüş ifadesi olmasıdır. Bu nedenle, ES6'daki ok işlevlerini aşağıdaki gibi yeniden düzenlemek için kullanabiliriz.
const add = a => b => a + b;
Kompozisyon
Matematikte kompozisyon, birleşik bir çıktı yaratmak için bir fonksiyonun çıktısını diğerinin girdisine geçirmek olarak tanımlanır. Saf fonksiyonlar kullandığımız için aynı şey fonksiyonel programlamada da mümkündür.
Bir örnek göstermek için, bazı fonksiyonlar oluşturalım.
İlk işlev, bir başlangıç sayısı a
ve bir bitiş numarası b
alan ve a
ile b
arasındaki sayılardan oluşan bir dizi oluşturan aralıktır.
const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];
Sonra bir dizi alan ve içindeki tüm sayıları çarpan bir çarpma fonksiyonumuz var.
const multiply = arr => arr.reduce((p, a) => p * a);
Faktöriyel hesaplamak için bu fonksiyonları birlikte kullanacağız.
const factorial = n => multiply(range(1, n)); factorial(5); // 120 factorial(6); // 720
Faktöriyel hesaplamak için yukarıdaki fonksiyon f(x) = g(h(x))
ile benzerdir, dolayısıyla kompozisyon özelliğini gösterir.
Sonuç Sözleri
Saf ve saf olmayan işlevleri, işlevsel programlamayı, buna yardımcı olan yeni JavaScript özelliklerini ve işlevsel programlamadaki birkaç temel kavramı inceledik.
Bu parçanın işlevsel programlamaya olan ilginizi çekeceğini ve muhtemelen kodunuzda denemeniz için sizi motive edeceğini umuyoruz. Bunun bir öğrenme deneyimi ve yazılım geliştirme yolculuğunuzda bir kilometre taşı olacağından eminiz.
İşlevsel programlama, bilgisayar programları yazmak için iyi araştırılmış ve sağlam bir paradigmadır. ES6'nın kullanıma sunulmasıyla birlikte JavaScript, her zamankinden çok daha iyi bir işlevsel programlama deneyimi sağlar.