JavaScript dizileriyle fonksiyonel programlamanın gücünü açığa çıkarın. Yerleşik yöntemleri kullanarak verilerinizi verimli bir şekilde dönüştürmeyi, filtrelemeyi ve azaltmayı öğrenin.
JavaScript Dizileriyle Fonksiyonel Programlamada Uzmanlaşmak
Web geliştirmenin sürekli değişen ortamında, JavaScript köşe taşı olmaya devam ediyor. Nesne yönelimli ve zorunlu programlama paradigmaları uzun süredir baskın olsa da, fonksiyonel programlama (FP) önemli ölçüde ilgi kazanıyor. FP, değişmezliği, saf fonksiyonları ve bildirimsel kodu vurgulayarak daha sağlam, bakımı kolay ve öngörülebilir uygulamalara yol açar. JavaScript'te fonksiyonel programlamayı benimsemenin en güçlü yollarından biri, yerel dizi yöntemlerinden yararlanmaktır.
Bu kapsamlı kılavuz, JavaScript dizilerini kullanarak fonksiyonel programlama ilkelerinin gücünden nasıl yararlanabileceğinizi ayrıntılı olarak inceleyecektir. Temel kavramları keşfedecek ve veri manipülasyonunu nasıl ele aldığınızı dönüştürerek map
, filter
ve reduce
gibi yöntemleri kullanarak bunları nasıl uygulayacağınızı göstereceğiz.
Fonksiyonel Programlama Nedir?
JavaScript dizilerine dalmadan önce, fonksiyonel programlamayı kısaca tanımlayalım. Özünde FP, hesaplamayı matematiksel fonksiyonların değerlendirilmesi olarak ele alan ve durumu ve değiştirilebilir verileri değiştirmekten kaçınan bir programlama paradigmasıdır. Temel ilkeler şunları içerir:
- Saf Fonksiyonlar: Saf bir fonksiyon, aynı girdi için her zaman aynı çıktıyı üretir ve yan etkisi yoktur (harici durumu değiştirmez).
- Değişmezlik: Veriler, bir kez oluşturulduktan sonra değiştirilemez. Mevcut verileri değiştirmek yerine, istenen değişikliklerle yeni veriler oluşturulur.
- Birinci Sınıf Fonksiyonlar: Fonksiyonlar diğer değişkenler gibi ele alınabilir - değişkenlere atanabilir, diğer fonksiyonlara argüman olarak geçirilebilir ve fonksiyonlardan döndürülebilir.
- Bildirimsel ve Zorunlu: Fonksiyonel programlama, neyi başarmak istediğinizi tarif ettiğiniz bildirimsel bir stile yönelir; adım adım nasıl başaracağınızı detaylandıran zorunlu bir stile değil.
Bu ilkelerin benimsenmesi, özellikle karmaşık uygulamalarda, hakkında akıl yürütmesi, test etmesi ve hata ayıklaması daha kolay olan bir koda yol açabilir. JavaScript'in dizi yöntemleri, bu kavramları uygulamak için mükemmeldir.
JavaScript Dizi Yöntemlerinin Gücü
JavaScript dizileri, geleneksel döngülere (for
veya while
gibi) başvurmadan karmaşık veri manipülasyonuna izin veren zengin bir yerleşik yöntemler kümesiyle birlikte gelir. Bu yöntemler genellikle değişmezliği destekleyen yeni diziler döndürür ve fonksiyonel bir yaklaşımı mümkün kılan geri arama fonksiyonlarını kabul eder.
En temel fonksiyonel dizi yöntemlerini keşfedelim:
1. Array.prototype.map()
map()
yöntemi, çağıran dizideki her öğe üzerinde sağlanan bir fonksiyonu çağırmanın sonuçlarıyla doldurulmuş yeni bir dizi oluşturur. Bir dizinin her öğesini yeni bir şeye dönüştürmek için idealdir.
Sözdizimi:
array.map(callback(currentValue[, index[, array]])[, thisArg])
callback
: Her öğe için yürütülecek fonksiyon.currentValue
: Dizide işlenen mevcut öğe.index
(isteğe bağlı): İşlenen mevcut öğenin indeksi.array
(isteğe bağlı):map
'in çağrıldığı dizi.thisArg
(isteğe bağlı):callback
yürütülürkenthis
olarak kullanılacak değer.
Temel Özellikler:
- Yeni bir dizi döndürür.
- Orijinal dizi değişmeden kalır (değişmezlik).
- Yeni dizi, orijinal diziyle aynı uzunluğa sahip olacaktır.
- Geri arama fonksiyonu, her öğe için dönüştürülmüş değeri döndürmelidir.
Örnek: Her Sayıyı İkiye Katlama
Bir sayı diziniz olduğunu ve her sayının ikiye katlandığı yeni bir dizi oluşturmak istediğinizi hayal edin.
const numbers = [1, 2, 3, 4, 5];
// Dönüşüm için map kullanma
const doubledNumbers = numbers.map(number => number * 2);
console.log(numbers); // Çıktı: [1, 2, 3, 4, 5] (orijinal dizi değişmedi)
console.log(doubledNumbers); // Çıktı: [2, 4, 6, 8, 10]
Örnek: Nesnelerden Özellikleri Çıkarma
Yaygın bir kullanım durumu, bir nesne dizisinden belirli özellikleri çıkarmaktır. Bir kullanıcı listemiz olduğunu ve sadece adlarını almak istediğimizi varsayalım.
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const userNames = users.map(user => user.name);
console.log(userNames); // Çıktı: ['Alice', 'Bob', 'Charlie']
2. Array.prototype.filter()
filter()
yöntemi, sağlanan fonksiyon tarafından uygulanan testi geçen tüm öğelerle yeni bir dizi oluşturur. Bir koşula göre öğeleri seçmek için kullanılır.
Sözdizimi:
array.filter(callback(element[, index[, array]])[, thisArg])
callback
: Her öğe için yürütülecek fonksiyon. Öğeyi tutmak içintrue
veya atmak içinfalse
döndürmelidir.element
: Dizide işlenen mevcut öğe.index
(isteğe bağlı): Mevcut öğenin indeksi.array
(isteğe bağlı):filter
'ın çağrıldığı dizi.thisArg
(isteğe bağlı):callback
yürütülürkenthis
olarak kullanılacak değer.
Temel Özellikler:
- Yeni bir dizi döndürür.
- Orijinal dizi değişmeden kalır (değişmezlik).
- Yeni dizi, orijinal diziden daha az öğeye sahip olabilir.
- Geri arama fonksiyonu bir boolean değeri döndürmelidir.
Örnek: Çift Sayıları Filtreleme
Sadece çift sayıları tutmak için sayı dizisini filtreleyelim.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Çift sayıları seçmek için filter kullanma
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(numbers); // Çıktı: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(evenNumbers); // Çıktı: [2, 4, 6, 8, 10]
Örnek: Aktif Kullanıcıları Filtreleme
Kullanıcı dizimizden, aktif olarak işaretlenmiş kullanıcıları filtreleyelim.
const users = [
{ id: 1, name: 'Alice', isActive: true },
{ id: 2, name: 'Bob', isActive: false },
{ id: 3, name: 'Charlie', isActive: true },
{ id: 4, name: 'David', isActive: false }
];
const activeUsers = users.filter(user => user.isActive);
console.log(activeUsers);
/* Çıktı:
[
{ id: 1, name: 'Alice', isActive: true },
{ id: 3, name: 'Charlie', isActive: true }
]
*/
3. Array.prototype.reduce()
reduce()
yöntemi, dizinin her öğesinde, sırayla, kullanıcı tarafından sağlanan bir "reducer" geri arama fonksiyonunu yürütür ve önceki öğedeki hesaplamadan dönüş değerini geçirir. Dizinin tüm öğelerinde reducer'ı çalıştırmanın nihai sonucu tek bir değerdir.
Bu, tartışmasız olarak dizi yöntemlerinin en çok yönlü olanıdır ve birçok fonksiyonel programlama modelinin köşe taşıdır ve bir diziyi tek bir değere (örneğin, toplam, ürün, sayı ve hatta yeni bir nesne veya dizi) "azaltmanıza" olanak tanır.
Sözdizimi:
array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
callback
: Her öğe için yürütülecek fonksiyon.accumulator
: Geri arama fonksiyonuna yapılan önceki çağrıdan kaynaklanan değer. İlk çağrıda, sağlandıysainitialValue
; aksi takdirde dizinin ilk öğesidir.currentValue
: İşlenen mevcut öğe.index
(isteğe bağlı): Mevcut öğenin indeksi.array
(isteğe bağlı):reduce
'un çağrıldığı dizi.initialValue
(isteğe bağlı):callback
'in ilk çağrısına ilk argüman olarak kullanılacak bir değer.initialValue
sağlanmazsa, dizideki ilk öğe ilkaccumulator
değeri olarak kullanılır ve yineleme ikinci öğeden başlar.
Temel Özellikler:
- Tek bir değer döndürür (bu da bir dizi veya nesne olabilir).
- Orijinal dizi değişmeden kalır (değişmezlik).
initialValue
, özellikle boş dizilerde veya accumulator türü dizi öğe türünden farklı olduğunda, netlik için ve hataları önlemek için çok önemlidir.
Örnek: Sayıları Toplama
Dizimizdeki tüm sayıları toplayalım.
const numbers = [1, 2, 3, 4, 5];
// Sayıları toplamak için reduce kullanma
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // 0 initialValue'dur
console.log(sum); // Çıktı: 15
Açıklama:
- Çağrı 1:
accumulator
0,currentValue
1'dir. 0 + 1 = 1 döndürür. - Çağrı 2:
accumulator
1,currentValue
2'dir. 1 + 2 = 3 döndürür. - Çağrı 3:
accumulator
3,currentValue
3'tür. 3 + 3 = 6 döndürür. - Ve böyle devam eder, nihai toplam hesaplanana kadar.
Örnek: Nesneleri Bir Özelliğe Göre Gruplandırma
reduce
'u, nesne dizisini, değerlerin belirli bir özelliğe göre gruplandırıldığı bir nesneye dönüştürmek için kullanabiliriz. Kullanıcılarımızı `isActive` durumlarına göre gruplandıralım.
const users = [
{ id: 1, name: 'Alice', isActive: true },
{ id: 2, name: 'Bob', isActive: false },
{ id: 3, name: 'Charlie', isActive: true },
{ id: 4, name: 'David', isActive: false }
];
const groupedUsers = users.reduce((acc, user) => {
const status = user.isActive ? 'active' : 'inactive';
if (!acc[status]) {
acc[status] = [];
}
acc[status].push(user);
return acc;
}, {}); // Boş nesne {} initialValue'dur
console.log(groupedUsers);
/* Çıktı:
{
active: [
{ id: 1, name: 'Alice', isActive: true },
{ id: 3, name: 'Charlie', isActive: true }
],
inactive: [
{ id: 2, name: 'Bob', isActive: false },
{ id: 4, name: 'David', isActive: false }
]
}
*/
Örnek: Oluşumları Sayma
Bir listedeki her meyvenin sıklığını sayalım.
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const fruitCounts = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(fruitCounts); // Çıktı: { apple: 3, banana: 2, orange: 1 }
4. Array.prototype.forEach()
forEach()
yeni bir dizi döndürmese ve birincil amacı her dizi öğesi için bir fonksiyonu yürütmek olduğundan genellikle daha zorunlu olarak kabul edilse de, yine de fonksiyonel modellerde rol oynayan temel bir yöntemdir, özellikle yan etkiler gerektiğinde veya dönüştürülmüş bir çıktıya ihtiyaç duymadan yineleme yapıldığında.
Sözdizimi:
array.forEach(callback(element[, index[, array]])[, thisArg])
Temel Özellikler:
undefined
döndürür.- Sağlanan bir fonksiyonu her dizi öğesi için bir kez yürütür.
- Genellikle konsola kaydetme veya DOM öğelerini güncelleme gibi yan etkiler için kullanılır.
Örnek: Her Öğeyi Kaydetme
const messages = ['Hello', 'Functional', 'World'];
messages.forEach(message => console.log(message));
// Çıktı:
// Hello
// Functional
// World
Not: Dönüşümler ve filtreleme için, değişmezlikleri ve bildirimsel doğaları nedeniyle map
ve filter
tercih edilir. Sonuçları yeni bir yapıda toplamadan her öğe için özellikle bir eylem gerçekleştirmeniz gerektiğinde forEach
kullanın.
5. Array.prototype.find()
ve Array.prototype.findIndex()
Bu yöntemler, bir dizideki belirli öğeleri bulmak için kullanışlıdır.
find()
: Sağlanan dizideki sağlanan test fonksiyonunu karşılayan ilk öğenin değerini döndürür. Test fonksiyonunu karşılayan hiçbir değer yoksa,undefined
döndürülür.findIndex()
: Sağlanan dizideki sağlanan test fonksiyonunu karşılayan ilk öğenin indeksini döndürür. Aksi takdirde, hiçbir öğenin testi geçmediğini gösteren -1 döndürür.
Örnek: Bir Kullanıcıyı Bulma
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const bob = users.find(user => user.name === 'Bob');
const bobIndex = users.findIndex(user => user.name === 'Bob');
const nonExistentUser = users.find(user => user.name === 'David');
const nonExistentIndex = users.findIndex(user => user.name === 'David');
console.log(bob); // Çıktı: { id: 2, name: 'Bob' }
console.log(bobIndex); // Çıktı: 1
console.log(nonExistentUser); // Çıktı: undefined
console.log(nonExistentIndex); // Çıktı: -1
6. Array.prototype.some()
ve Array.prototype.every()
Bu yöntemler, dizideki tüm öğelerin sağlanan fonksiyon tarafından uygulanan testi geçip geçmediğini test eder.
some()
: Dizideki en az bir öğenin sağlanan fonksiyon tarafından uygulanan testi geçip geçmediğini test eder. Bir Boolean değeri döndürür.every()
: Dizideki tüm öğelerin sağlanan fonksiyon tarafından uygulanan testi geçip geçmediğini test eder. Bir Boolean değeri döndürür.
Örnek: Kullanıcı Durumunu Kontrol Etme
const users = [
{ id: 1, name: 'Alice', isActive: true },
{ id: 2, name: 'Bob', isActive: false },
{ id: 3, name: 'Charlie', isActive: true }
];
const hasInactiveUser = users.some(user => !user.isActive);
const allAreActive = users.every(user => user.isActive);
console.log(hasInactiveUser); // Çıktı: true (çünkü Bob aktif değil)
console.log(allAreActive); // Çıktı: false (çünkü Bob aktif değil)
const allUsersActive = users.filter(user => user.isActive).length === users.length;
console.log(allUsersActive); // Çıktı: false
// Doğrudan every kullanarak alternatif
const allUsersActiveDirect = users.every(user => user.isActive);
console.log(allUsersActiveDirect); // Çıktı: false
Karmaşık İşlemler İçin Dizi Yöntemlerini Zincirleme
JavaScript dizileriyle fonksiyonel programlamanın gerçek gücü, bu yöntemleri birbirine zincirlediğinizde ortaya çıkar. Bu yöntemlerin çoğu yeni diziler (forEach
hariç) döndürdüğünden, bir yöntemin çıktısını diğerinin girdisine sorunsuz bir şekilde aktarabilir, zarif ve okunabilir veri işlem hatları oluşturabilirsiniz.
Örnek: Aktif Kullanıcı Adlarını Bulma ve Kimliklerini İkiye Katlama
Tüm aktif kullanıcıları bulalım, adlarını çıkaralım ve ardından her adın *filtrelenmiş* listedeki indeksini temsil eden bir sayıyla eklendiği ve kimliklerinin ikiye katlandığı yeni bir dizi oluşturalım.
const users = [
{ id: 1, name: 'Alice', isActive: true },
{ id: 2, name: 'Bob', isActive: false },
{ id: 3, name: 'Charlie', isActive: true },
{ id: 4, name: 'David', isActive: true },
{ id: 5, name: 'Eve', isActive: false }
];
const processedActiveUsers = users
.filter(user => user.isActive) // Sadece aktif kullanıcıları al
.map((user, index) => ({
name: `${index + 1}. ${user.name}`,
doubledId: user.id * 2
}));
console.log(processedActiveUsers);
/* Çıktı:
[
{ name: '1. Alice', doubledId: 2 },
{ name: '2. Charlie', doubledId: 6 },
{ name: '3. David', doubledId: 8 }
]
*/
Bu zincirleme yaklaşımı bildirimseldir: açık döngü yönetimi olmadan adımları (filtrele, sonra haritala) belirtiriz. Ayrıca, her adım orijinal users
dizisini dokunulmadan bırakarak yeni bir dizi veya nesne ürettiğinden değişmezdir.
Pratikte Değişmezlik
Fonksiyonel programlama büyük ölçüde değişmezliğe dayanır. Bu, mevcut veri yapılarını değiştirmek yerine, istenen değişikliklerle yenilerini oluşturduğunuz anlamına gelir. JavaScript'in map
, filter
ve slice
gibi dizi yöntemleri, yeni diziler döndürerek bunu doğal olarak destekler.
Değişmezlik neden önemlidir?
- Öngörülebilirlik: Paylaşılan değiştirilebilir durumdaki değişiklikleri takip etmek zorunda olmadığınız için kod hakkında akıl yürütmek daha kolay hale gelir.
- Hata Ayıklama: Hatalar oluştuğunda, veriler beklenmedik bir şekilde değiştirilmediğinde sorunun kaynağını belirlemek daha kolaydır.
- Performans: Belirli bağlamlarda (Redux gibi durum yönetimi kitaplıklarıyla veya React'te olduğu gibi), değişmezlik verimli değişiklik algılamaya olanak tanır.
- Eş Zamanlılık: Değişmez veri yapıları doğası gereği iş parçacığı güvenlidir ve eş zamanlı programlamayı basitleştirir.
slice
, yayılma sözdizimi (...
) gibi yöntemleri kullanarak veya diğer fonksiyonel yöntemleri birleştirerek değişmezliği sağlayabilirsiniz.
Örnek: Bir Öğeyi Değişmez Şekilde Ekleme
const originalArray = [1, 2, 3];
// Zorunlu yol (originalArray'i değiştirir)
// originalArray.push(4);
// Yayılma sözdizimini kullanarak fonksiyonel yol
const newArrayWithPush = [...originalArray, 4];
console.log(originalArray); // Çıktı: [1, 2, 3]
console.log(newArrayWithPush); // Çıktı: [1, 2, 3, 4]
// Slice ve birleştirmeyi kullanarak fonksiyonel yol (artık daha az yaygın)
const newArrayWithSlice = originalArray.slice(0, originalArray.length).concat(4);
console.log(newArrayWithSlice); // Çıktı: [1, 2, 3, 4]
Örnek: Bir Öğeyi Değişmez Şekilde Kaldırma
const originalArray = [1, 2, 3, 4, 5];
// 2 indeksindeki öğeyi kaldır (değer 3)
// Slice ve yayılma sözdizimini kullanarak fonksiyonel yol
const newArrayAfterSplice = [
...originalArray.slice(0, 2),
...originalArray.slice(3)
];
console.log(originalArray); // Çıktı: [1, 2, 3, 4, 5]
console.log(newArrayAfterSplice); // Çıktı: [1, 2, 4, 5]
// Belirli bir değeri kaldırmak için filter kullanma
const newValueToRemove = 3;
const arrayWithoutValue = originalArray.filter(item => item !== newValueToRemove);
console.log(arrayWithoutValue); // Çıktı: [1, 2, 4, 5]
En İyi Uygulamalar ve Gelişmiş Teknikler
Fonksiyonel dizi yöntemlerinde daha rahat hale geldikçe, bu uygulamaları göz önünde bulundurun:
- Önce Okunabilirlik: Zincirleme güçlü olsa da, aşırı uzun zincirler okuması zor hale gelebilir. Karmaşık işlemleri daha küçük, adlandırılmış fonksiyonlara bölmeyi veya ara değişkenler kullanmayı düşünün.
reduce
'un Esnekliğini Anlayın:reduce
'un sadece tek değerler değil, diziler veya nesneler de oluşturabileceğini unutmayın. Bu, onu karmaşık dönüşümler için inanılmaz derecede çok yönlü hale getirir.- Geri Aramalarda Yan Etkilerden Kaçının:
map
,filter
vereduce
geri aramalarınızı saf tutmaya çalışın. Yan etkileri olan bir eylem gerçekleştirmeniz gerekiyorsa,forEach
genellikle daha uygun bir seçimdir. - Ok İşlevlerini Kullanın: Ok işlevleri (
=>
), geri arama işlevleri için kısa bir sözdizimi sağlar vethis
bağlamayı farklı şekilde ele alır, bu da onları genellikle fonksiyonel dizi yöntemleri için ideal hale getirir. - Kitaplıkları Düşünün: Daha gelişmiş fonksiyonel programlama desenleri için veya değişmezlikle yoğun bir şekilde çalışıyorsanız, Lodash/fp, Ramda veya Immutable.js gibi kitaplıklar faydalı olabilir, ancak modern JavaScript'te fonksiyonel dizi işlemlerine başlamak için kesinlikle gerekli değildirler.
Örnek: Veri Toplamaya Fonksiyonel Yaklaşım
Farklı bölgelerden satış verileriniz olduğunu ve her bölge için toplam satışları hesaplamak, ardından en yüksek satışlara sahip bölgeyi bulmak istediğinizi hayal edin.
const salesData = [
{ region: 'North', amount: 100 },
{ region: 'South', amount: 150 },
{ region: 'North', amount: 120 },
{ region: 'East', amount: 200 },
{ region: 'South', amount: 180 },
{ region: 'North', amount: 90 }
];
// 1. Reduce kullanarak bölge başına toplam satışları hesaplayın
const salesByRegion = salesData.reduce((acc, sale) => {
acc[sale.region] = (acc[sale.region] || 0) + sale.amount;
return acc;
}, {});
// salesByRegion şöyle olacaktır: { North: 310, South: 330, East: 200 }
// 2. Toplanan nesneyi daha fazla işlenmesi için bir nesne dizisine dönüştürün
const salesArray = Object.keys(salesByRegion).map(region => ({
region: region,
totalAmount: salesByRegion[region]
}));
// salesArray şöyle olacaktır: [
// { region: 'North', totalAmount: 310 },
// { region: 'South', totalAmount: 330 },
// { region: 'East', totalAmount: 200 }
// ]
// 3. Reduce kullanarak en yüksek satışlara sahip bölgeyi bulun
const highestSalesRegion = salesArray.reduce((max, current) => {
return current.totalAmount > max.totalAmount ? current : max;
}, { region: '', totalAmount: -Infinity }); // Çok küçük bir sayıyla başlatın
console.log('Bölgelere Göre Satışlar:', salesByRegion);
console.log('Satış Dizisi:', salesArray);
console.log('En Yüksek Satışlara Sahip Bölge:', highestSalesRegion);
/*
Çıktı:
Bölgelere Göre Satışlar: { North: 310, South: 330, East: 200 }
Satış Dizisi: [
{ region: 'North', totalAmount: 310 },
{ region: 'South', totalAmount: 330 },
{ region: 'East', totalAmount: 200 }
]
En Yüksek Satışlara Sahip Bölge: { region: 'South', totalAmount: 330 }
*/
Sonuç
JavaScript dizileriyle fonksiyonel programlama sadece stilistik bir seçim değildir; daha temiz, daha öngörülebilir ve daha sağlam kod yazmanın güçlü bir yoludur. map
, filter
ve reduce
gibi yöntemleri benimseyerek, fonksiyonel programlamanın temel ilkelerine, özellikle değişmezliğe ve saf fonksiyonlara bağlı kalarak verilerinizi etkili bir şekilde dönüştürebilir, sorgulayabilir ve toplayabilirsiniz.
JavaScript geliştirmedeki yolculuğunuza devam ederken, bu fonksiyonel desenleri günlük iş akışınıza entegre etmek şüphesiz daha sürdürülebilir ve ölçeklenebilir uygulamalara yol açacaktır. Projelerinizde bu dizi yöntemlerini deneyerek başlayın ve kısa süre sonra onların muazzam değerini keşfedeceksiniz.