iKoco ← Düzenleme penceresine dön

Scala'ya Hızlı Giriş

Skala/Kojo öğreticisi — masaüstü Koco'daki 19 sayfalık öykünün tarayıcı sürümü.

1Scala'ya Hızlı Giriş

Scala diline hoşgeldin! Skala diye oku, olur mu? Bu kılavuz, masaüstü Koco'nun Yardım → Scala'ya Giriş öyküsünün tarayıcı sürümüdür. Soldaki listeden istediğin bölüme atlayabilirsin; her bölümün sonunda da bir önceki ve bir sonraki bölüme bağlantı var.

Bu kılavuz nasıl kullanılır?

Kılavuzda pek çok yazılımcık örneği bulacaksın. Her kod bloğunun altındaki Editörde aç → bağlantısı örneği yeni bir sekmede iKoco düzenleyicisine taşır; oradaki Çalıştır düğmesine basman yeter. Sonra istediğin değişikliği yapıp tekrar çalıştır. Değiştir, boz, düzelt: en iyi böyle öğrenilir.

  • Çok uzun örneklerde bağlantı yerine Kopyala düğmesi var. Kodu panoya alır; düzenleyiciye yapıştırıp çalıştır.
  • Bazı tablolarda tek komutluk hücreler (ileri(100) gibi) de tıklanabilir; onlar da düzenleyicide açılır.
  • Turuncu masaüstü rozeti taşıyan örneklerde, yalnız masaüstü Koco'da bulunan bir komut var. Rozetin yanında hangi komutun eksik olduğu ve varsa ikojo'daki karşılığı yazar. Kod yine gösteriliyor ki masaüstünde deneyebil. Farkların özeti için Farklar sayfasına bak.
Masaüstü Koco'dan iki fark daha var, bilmende fayda olacak. Bir: masaüstünde 1 + 2 gibi yalın bir deyiş yazınca sonucu çıktı gözünde görürsün; tarayıcıda görmek için satıryaz(1 + 2) yazmak gerekir, o yüzden burada örneklerin çoğu satıryaz içinde. İki: masaüstünde art arda çalıştırdığın örnekler birbirinin tanımlarını hatırlar; tarayıcıda her çalıştırma temiz başlar. Onun için birbirine dayanan örneklerin tanımlarını her blokta yineledik. Ayrıca ikojo'da satıryaz(a, b) ikiliyi (a,b) biçiminde tek parça yazar.

Bölümler

#BölümNe öğreneceğiz?
2Kaplumbağacığın KullanılışıKaplumbağayı yürüten ve çizdiren komutlar
3Anahtar SözcüklerTürkçe anahtar sözcükler ve İngilizceleri
4BaşlayalımDeyişler, dez ve den, açıklamalar, yazılar, parçacık işlemleri
5Yazılım Akışı: Eğer, Yoksa ve İçinKarar çatalları ve döngüler
6Yalın Değerler, Sayılar, Kesirler ve YazılarTemel türler
7İşlevlertanım ile işlev tanımlamak, özyineleme
8Nesneler ve Sınıflarsınıf, yeni, durum sınıf, yayar, baskın
9Desen Eşleme: Eşle ve Durumeşle / durum yapısı
10İleri Eşleme: İkil AğaçSınıf hiyerarşisi ve özyineli arama
11Dingin Türleme ve Tür ÇıkarımıDerleyici türleri nasıl bulur?
12İşlevler de Birer Nesnedirİşlev girdileri, adsız işlevler, genelleyici işlevler, işle/ele/katla
13Sıralamalar (Tuple)Çoklu değerler ve çözümleme
14Matematiksel İşlevlerTrigonometri, üst, yuvarlama, rastgele
15İşlem Önceliği ve BirleşmeliğiKendi işlemlerini tanımlamak
16Yazıların (String) KullanılışıYazı yöntemleri
17Dizinlerin (List) KullanılışıDizin yöntemleri
18Çizim ve OyunResim, canlandır, klavye, saat, Yaşam Oyunu, Planarity
19Daha Çok ÖğrenelimKitaplar ve bağlantılar

Kojo'nun kaplumbağacığını zaten iyi tanıyorsan ikinci bölümü atlayıp doğrudan Başlayalım ile başlayabilirsin. İlk iki üç bölümü okuduktan sonra sona yakın olan Çizim ve Oyun bölümüne göz atmanda da fayda var: orada çok şey öğreneceksin.

Not: Bu kılavuz Anthony Bagwell'in simplyscala.com sitesinden Kojo'ya uyarlanarak yazılmıştır (Phil Bagwell; Kojo örneklerinin bir kısmı Lalit Pant'tan). O site ne yazık ki artık çalışmıyor, ama arşivde bulabilirsin: web.archive.org'dan simplyscala.com. Türkçesi Bülent Başaran'ın sevgiyle çevirisidir.

2Kaplumbağacığın Kullanılışı

Bu bölümü en başa, hatta Başlayalım adlı bölümden de öne koyduk. Çünkü hem okuması, anlaması ve oynaması daha kolay hem de daha görsel ve onun için de biraz daha cazip. Kojo'nun kaplumbağacığını zaten iyi tanıyorsan bir sonraki bölüme atlayıp Scala yani epey ciddi bir yazılım dili öğrenmeye başlayabilirsin.

Kaplumbağacığı hareket ettirerek ona çizgi çizdirten pek çok komutumuz var. Çoğunu aşağıdaki tabloda bulacaksın. Onlara tıklayıver ki kaplumbağacık neler yapabiliyor göresin: her komut düzenleyicide açılır, Çalıştır'a basman yeter. Tablodaki sırayı izlemene gerek yok. İstediklerine birkaç kere tıklayabilirsin.

Aşağıdaki ilk örnekte bir işlev (ya da komut) tanımlıyoruz. Ne yaptığını anladın mı? Çalıştırınca göreceksin. Bir üçgen çiziyor. Fark ettin mi, yinele komuduyla ileri ve sağ komutlarını üçer kere çağırıyoruz.

tanım birÜçgenÇiz() = {
    yinele(3) {
        ileri(100)
        sağ(120)
    }
}
sil()
birÜçgenÇiz()
sol()
birÜçgenÇiz()

Çalıştırdın ve kaplumbağanın iki tane üçgen çizmesini seyrettin, değil mi? Bu kılavuzdaki kod bloklarının her birini denemeyi unutma. Genelde sırayla gitmende fayda var. Ama bazılarını birden çok da çalıştırabilirsin, bazılarını da es geçebilirsin. Rahat takıl. Biraz oku, biraz dene. Düzenleyicide değişiklikler yap ve tekrar çalıştır. İçinden geldiği gibi davran ki daha keyifle keşfedesin bilgisayar programlamayı!

KomutNe yapar
ileri(100)100 adım ilerler
sil()Tuvali temizler ve başlangıç noktasına döndürür
geri(50)50 adım geriye gider
eksenleriGöster()X ve Y eksenlerini gösterir
ızgarayıGöster()Tuvalin ızgarasını çizer
konumuKur(150, 100)Çizgi çizmeden koordinatları verilen (x, y) noktasına gider. Ama baktığı doğrultu değişmez
noktayaGit(0, -100)Doğrultusunu koordinatları verilen noktaya çevirir ve o noktaya kadar ilerler
eksenleriGizle()Eksenleri saklar
ızgarayıGizle()Izgarayı gizler
dön(30)Saat yönünün tersine doğru 30 derece döner. Eksi girersen saat yönünde döner
sağ()Sağa döner. Yani saat yönünde 90 derece döner
sağ(60)Sağa doğru 60 derece döner
sol()Sola döner. Yani saat yönünün tersinde 90 derece döner
sol(30)Sola doğru 30 derece döner
noktayaDön(40, 60)Doğrultusunu koordinatları verilen (x, y) noktasına çevirir
açıyaDön(30)Diyelim ki 0 derece ekranın sağı, 90 derece ekranın üstü olsun. Verilen açıya döner
satıryaz(doğrultu)Şu anda baktığımız doğrultuyu açı olarak çıktıyla bildirir. 180 sola bakıyor demek. 270 de aşağıya
ev()Evine yani (0, 0) noktasına döner ve 90 dereceye yani yukarı bakar
satıryaz(konum)Şu andaki konumu çıktı olarak bildirir
ışınlarıAç()Dört yönü belirten farlar yansın
ışınlarıKapat()Farları söndürelim
satıryaz(canlandırmaHızı)100 adımı şu anda kaç milisaniyede attığını bildirir
kaplumbağa.geri(100)Başlangıçtaki kaplumbağamızın adı kaplumbağa. Onu yöntemleriyle de çağırabiliriz

Tuvali silmek için sil() komutunu kullan (masaüstünde tuvale sağ tıklayıp Temizle'ye de basabilirsin). Şimdi biraz daha uzun örnekler:

sil
kalemiKaldır()
ileri(100)
kalemiİndir()
ileri(100)

Kalem kalkıkken hareket ederse çizim yapmaz. Kalem inince çizmeye devam eder.

tanım birÜçgenÇiz() = {
    yinele(3) {
        ileri(100)
        sağ(120)
    }
}
sil()
kalemRenginiKur(mavi)
birÜçgenÇiz()

Rengini değiştirelim. Çizimin içini boyamak da kolay. Kojo'nun kendi üçgen komudu da var; girdisini değiştirerek değişik boylarda üçgen çizebiliriz:

sil()
boyamaRenginiKur(kırmızı)
üçgen(80)

Çizim yaptığı kalem kalınlığını da giriyoruz:

sil()
kalemKalınlığınıKur(10)
üçgen(90)
kalemKalınlığınıKur(1)

görünmez ve görünür komutları da şöyle çalışıyor. Kaplumbağacık görünmese de çizim yapmaya devam edebiliyor:

sil()
görünmez()
ileri(100)
görünür()
dön(120)
ileri(100)

Bulunduğu noktaya yazı yazdırabiliriz. İngilizce'den çevirdiğimiz bazı türlerin adı İngilizce olabilir. Şaşırtmasın seni!

sil
yazı("Merhaba Kardeş!")
zıpla()
yazı(Aralık(10, 0, -1).yazıya)
zıpla()
yazı(Dizi(1, 2, 3).yazıya)
zıpla()

Kaplumbağacığın hızını belirlemek için bir süre giriyoruz. 100 adımı girdiğimiz kadar milisaniyede atıveriyor. Epey hızlı canım! Başlangıçta 100 adım atmak 1000 milisaniye yani bir saniye alıyor.

sil()
ileri(-100)
canlandırmaHızınıKur(10)
dön(120)
ileri(100)

Verilen (x, y) noktasında yeni bir kaplumbağa canlandırmak da mümkün. İlk yeni kaplumbağaya ad takmayı unuttuk!

yeniKaplumbağa(50, 50)
dez yk2 = yeniKaplumbağa(100, 100)
yk2.geri(180)
// ikinci kaplumbağacığı da hızlandıralım:
yk2.canlandırmaHızınıKur(10)
yk2.sol()
yk2.ileri(300)

yaklaş(oran, x, y) tuvali verilen oran kadar büyültür ya da küçültür ve verilen noktayı tam tuvalin merkezine getirir. ikojo'da aynı işi yaklaşXY(oran, oran, x, y) yapar:

sil()
üçgen(100)
yaklaş(0.5, 10, 10)
sil()
üçgen(100)
yaklaşXY(0.5, 0.5, 10, 10)

İleri bölümlerde başka pek çok örnek göreceğiz. Matematiksel işlevleri sevenlere iki üç bölüm sonra kaplumbağalı bir sürpriz de var: Kaplumbağamız Yazılım Akışı bölümünün sonuna yakın hızla bir parabol çizecek! Devam etmek için bir sonraki bölüme geç. Ya da soldaki listeden istediğin bölüme atla.

3Anahtar Sözcükler

Türkçemizde kimbilir kaç bin kelime var? Hepsini bilmek mümkün değil elbet. Ayrıca pek çok kelimenin birden çok anlamı oluyor. Hatta bazen anlamları belirsiz bile olabiliyor ve sözlüğe bakmamız yetmiyor, ne demek istediniz diye soruyoruz. Ama yine de birbirimizi anlamakta genelde zorlanmıyoruz. Bilgisayar dillerindeyse durum çok farklı. Hem çok daha az sözcük gerekli hem de sözcüklerin anlamı çok daha belirli.

Bilgisayar dillerinin derleyicileri olduğunu ve yazdığımız yazılımları donanımın anlayacağı 0 ve 1 sayılarına çevirdiğini duymuş olabilirsin. Bunu yapabilmek için genelde derleyiciler çok küçük ama çok becerikli bir sözlükle yazılır. Bu sözlükteki kelimelere 'anahtar' ya da 'özel' sözcükler deriz. Scala dilinin anahtar sözcüklerinin en önemlilerini bu kılavuzda örnekleriyle görecek ve öğreneceğiz. Yine de bu bölümde hepsini yazdık ki ilerde İngilizcelerini bilmek gerekince gelip bakabilelim.

Bu bölümün gerisini şimdi atla istersen. Sonra merak ettikçe gelip bakarsın. İstersen hızlıca bir göz at. Zaten detaylarını görmeden aşağıdaki açıklamalar pek anlamlı olmaz. Şimdilik bilmemiz gereken tek şey, herşeyin çok küçücük bir anahtar kelimeler sözlüğüyle başladığı. Onun için bir bilgisayar dili öğrenmek yabancı bir dil öğrenmekten çok daha kolay aslında! Tabii bir de kaplumbağacığın anladığı komut sözcükler var. Bir önceki bölümde bir kısmını gördük ve öğrendik. Ama onlar da zaten bizim dilimizdeki anlamlarına çok yakın, değil mi?

Aşağıdaki her satırda bir anahtar sözcük var. İlk sütun, kılavuzdaki kullanış sırasına göre sıralı. Sonra gelip bulmak kolay olsun diye. İkinci sütunda anahtar sözcüğün İngilizce'si var. Keyword diye de bilinir. Eğer o keyword, İngilizce bir sözcüğün kısaltmasıysa o sözcüğün kendisi de üçüncü sütunda. Son sütunda da ufak bir açıklamayla yetindik. Kılavuzumuzun diğer bölümleri daha geniş açıklamalar verecek.

TürkçeİngilizceAçılımıAçıklama
tanımdefdefineİlk bölümde kaplumbağacığa üçgen çizdirmek için kullanmıştık. Bu anahtar sözcük yeni bir işlev tanımlar ve ona bir ad takar. Bu yeni ad da derleyicinin sözlüğüne eklenmiş olur!
dezvalvalueİlk bölümdeki yeni kaplumbağacığa ad takmamızı sağlayan anahtar sözcük. Bilinen ve değişmez bir değere bir ad takar. Bu yeni ad da yazılımcığın özel sözlüğüne eklenmiş olur.
denvarvariableYeni bir değişken tanımlar ve ona bir ad takar. Bu yeni ad da sözlüğe eklenmiş olur.
eğerifBir çatallanma yani karar verme durumu tanımlar. Duruma göre iki seçenekten birini seçer derleyici.
yoksaelseeğer sözcüğünden sonra kullanılır.
içinforİşlevsel bir döngü tanımlar. Çok daha iyi bir yöntemdir!
veryieldİçin komudunun içinde teker teker nesne çıktısı vermek için kullanılır.
sınıfclassYeni bir tür tanımlar. Nesne odaklı yazılımın ana birim taşıdır.
durumcaseİki durumda kullanılır: Daha becerikli bir sınıf tanımlamaya ve örüntü/desen eşlemeye yarar.
yayarextendsBir türün başka bir türü yaymasına yarar. İleri bir konudur. Hafifçe değindik sadece.
yeninewYeni bir nesne oluşturmak için gerekebilir.
eşlematchÖrüntü/desen eşlemeye yarar.
doğrutrueMantıksal doğru. Eğer komuduna girdi olarak kullanılabilir.
yanlışfalseMantıksal yanlış. Eğer komuduna girdi olarak kullanılabilir.
baskınoverrideDaha temel bir türden (üst sınıf da denir) gelen yöntemleri yeniden tanımlarken gerekir.

Yukarıdaki 15 sözcükle neler neler yapabiliriz yakında göreceğiz. Bu kılavuzda hiç kullanmadığımız diğer anahtar sözcükler de aşağıda. İngilizcelerinin A'dan Z'ye sıralamasına uyduk. Örnek yazılımcıklarda ender de olsa kullanılıyorlar. Ama şu ikisini de unutmayalım:

TürkçeİngilizceAçıklama
yineleDoğruKaldıkçawhileBir koşul doğru kaldıkça yinelenen bir döngü tanımlar. Koşul baştan yanlışsa döngüye hiç girmez.
yapdoÖnce 'yineleDoğruKaldıkça' döngüsünün komutları en az bir kere çalışsın sonra da koşuluna bakılsın dersen bu sözcükle olur.

Bunlar eski programlama dillerinde çok faydalı komutlardı. Ama Scala'da pek gerek yok. Kullanılmasalar daha iyi olur. Onun için birincisinin adını da uzun tuttuk. Kılavuzumuzda da az yer verdik.

TürkçeİngilizceAçıklama
soyutabstractEnder kullanılır. Scala kitabından soyut sınıflar
yakalacatchKuraldışı bir durum varsa işe yarar. 'dene' ile birlikte kullanılır.
sonfinalBaskın komuduyla yeniden tanımlanmasını istemediğimiz nesneler ve türler için gerekir.
sonundafinallydene ve yakala komutlarıyla birlikte işe yarar.
bazıforSomeBu Scala3'te yok. Sadece Scala2'de var. Unut daha iyi!
örtükimplicitİşte usta yazıcılar için çok faydalı bir komut. Ama Scala3'te daha iyisi var.
getirimportBaşka bir yazılımın bizim yazılımcığımız içinde kullanılmasını sağlar.
miskinlazyMiskinlik de çok faydalıdır bazı durumlarda.
yoknullBu Java'dan kalma. Hiç kullanma!
nesneobjectBazen sınıfa gerek olmaz. Tek nesne yeter. Bazen de sınıfa destek olur.
destepackageBüyük yazılımları deste deste düzenlemeye yarar. Sonra 'getir' ile kullanılır.
gizliprivateDışarıya gizli nesneler ve türler tanımlamak için.
koruprotectedAlt türlere açık dışarıya gizli.
geriDönreturnİşlevden prematüre geri dönüş için. Hiç kullanma daha iyi.
damgalısealedBir dosya dışında değişiklik yapılmasını istemediğimiz türler için kullanırız.
üstsuperBir üst türden bahsetmek gerekirse.
buthisBir sınıfın tanımının içinde kendi nesnelerinden bahsetmek için kullanılabilen adıl. Ya da eski adıyla bu zamiri!
bildirthrowKuraldışı durumlarda kullanılır. 'Throw' atmak demek. Emir ya da istek kipi 'at' olacaktı. Ama bazı teknik sorunlardan ötürü olmadı. Zaten bildir sözcüğü daha kibar oldu, değil mi?
özelliktraitTemel bir tür tanımlamak için çok faydalıdır. Ama onun yerine 'sınıf' da kullanılabilir.
denetryKuraldışı durumları sınır içine alır.
türtypeBir türe yeni bir ad takar.
birliktewithBir türün bir türü başka bir türle birlikte yaymasına yarar. İleri bir konudur. Bu kılavuzda henüz anlatmadık.

Son olarak da alfabetik sırayla hepsini birbirine bağlayalım, bulmak kolay olsun diye. Önce Türkçe'de sıralı, sonra İngilizce sıralı:

TürkçeİngilizceİngilizceTürkçe
baskınoverrideabstractsoyut
bazıforSomecasedurum
bildirthrowcatchyakala
birliktewithclasssınıf
buthisdeftanım
damgalısealeddoyap
denvarelseyoksa
denetryextendsyayar
destepackagefalseyanlış
dezvalfinalson
doğrutruefinallysonunda
durumcaseforiçin
eğerifforSomebazı
eşlematchifeğer
geriDönreturnimplicitörtük
getirimportimportgetir
gizliprivatelazymiskin
içinformatcheşle
koruprotectednewyeni
miskinlazynullyok
nesneobjectobjectnesne
sonfinaloverridebaskın
sonundafinallypackagedeste
soyutabstractprivategizli
sınıfclassprotectedkoru
tanımdefreturngeriDön
türtypesealeddamgalı
veryieldsuperüst
yakalacatchthisbu
yanlışfalsethrowbildir
yapdotraitözellik
yayarextendstruedoğru
yeninewtrydene
yineleDoğruKaldıkçawhiletypetür
yoknullvaldez
yoksaelsevarden
örtükimplicitwhileyineleDoğruKaldıkça
özelliktraitwithbirlikte
üstsuperyieldver

İşte hepsi bu kadar! Yabancı dil öğrenmekten çok daha kolay değil mi ama? Türkçe ve İngilizce adların tam listesi için Koco Sözlüğü'ne de bakabilirsin.

4Başlayalım

Başlayalım

Kılavuzumuzda pek çok yazılımcık örneği bulacaksın. Onları buradan kolaylıkla çalıştırabilirsin. Herhangi birinin altındaki Editörde aç bağlantısına tıkladığında o örnek düzenleyiciye taşınır; Çalıştır'a bas. Yani hepsini yazmana gerek yok. Ayrıca işin bir güzel yanı da şu: istediğin değişiklikleri orada yapıp tekrar çalıştırabilirsin. Haydi şimdi bunu deneyelim. Aşağıdaki mesajı Bülent yerine kendi adını yazıp tekrar çalıştırıver. Bu arada benim adım Bülent. Bu satırları siz Türkçe severler için severek çevirdim.

satıryaz("merhaba dünya ve kaplumbağacık")
satıryaz("Bülent'ten hepinize selamlar, sevgiler")

Yaptığın değişikliğin sonucunu çıktı alanında ya da çizim tuvalinde hemen göreceksin.

Bu kılavuz ilk bölümde gördüğün bölümlerden oluşuyor. Bir sonraki bölüme geçmek için her bölümün sonundaki bağlantıyı kullan; ilgini çeken bölüme de soldaki listeden kolaylıkla atlayabilirsin. Kılavuz ayrı bir sekmede açıldığı için düzenleyicideki yazılımcığın kapanmaz; onun üzerinde değişiklikler yapıp çalıştırmaya odaklanabilirsin.

Her örneği deneyebilir, istediğine geri dönüp değişiklikler yapıp tekrar çalıştırabilirsin. Değişik fikirler dener, yazılım dilini daha iyi tanıyıp yazım, yazılım gramer (syntax) kurallarını daha çabuk öğrenebilirsin. Düzenleyicideki programları Kaydet ile kalıcı bir adrese kaydedebilir, bağlantısını paylaşabilirsin (giriş yapman gerekir).

Şu anda kullandığın Kojo öğrenim ortamı, çok gelişmiş ve uzman bilgisayar mühendislerinin en sevdikleri dillerin önde gelenlerinden Scala yazılım dilini öğrenmene yardımcı olmak için hem çok faydalı hem de eğlendirici özellikler ve beceriler içeriyor. İleri matematik kavramlarını resimler ve grafikler çizerek inceleyip daha iyi öğrenir, değişik tür oyunları hem oynayabilir hem de nasıl yazıldıklarını kolayca öğrenebilir, hatta fizik deneyleri bile yapabilirsin! En güzeli Kojo'nun kaplumbağaları var! Onlara yol göstererek çizimler yaptırabilirsin. Küçük, hemen anlayıp seveceğin tek kaplumbağalı bir örnekle başlayalım mı? Kaplumbağa yürüsün ve bir taraftan da bir üçgen çizsin istersen, bu yazılımcığı çalıştırman yeter:

sil
ileri(100)
sağ(120)
ileri(100)
sağ(120)
ileri(100)
sağ(120)

Bu kılavuzun ikinci bölümünde daha pek çok örnek yazılımcık ve kaplumbağanın anladığı komutların bir listesini bulabilirsin.

Scala dili tam anlamıyla genel ve güçlü bir bilgisayar programlama dilidir. Özelliklerinin çoğunluğu uzman programcılara ve bilgisayar mühendislerine tanıdık gelecektir. Bu kılavuzdan faydalanmak için yazılım dili bilmesen de olur, ama elbette daha önce başka bir yazılım diliyle deneyimin olduysa daha hızlı ilerleyebilirsin. Onun için anladığın yerleri ve bölümleri hızlı geçmen hatta atlaman bile doğal olur.

Öyleyse, başlayalım mı artık?

Deyişler

Basit matematiksel deyişler pek şaşırtıcı gelmeyecektir. Tanıdık matematik işlemleri ve öncelikleri Scala'da da geçerlidir. İşlemleri daha açık açık sıralamak için parantez kullanılır.

Masaüstü Kojo bir deyişin değerini kendiliğinden çıktı gözüne yazar. Tarayıcıda görmek için satıryaz içine alıyoruz.
satıryaz(1 + 2)
satıryaz(3 + 4 * (2 - 3))
satıryaz(23 % 5)

Son satırda tabanlı aritmetik işlemi yaptık. 5 tabanında. Yani 5'e bölünce ne kalıyorsa onu bulduk.

satıryaz(6 / 4)

Bu son işlemde önemli bir nokta var. Tam sayılar bölününce sonuç yine bir tam sayı olur ve kalan dikkate alınmaz. Ufak bir değişiklikle kesirli ve daha doğru bir işlem yapabiliriz:

satıryaz(6 / 4.0)

Biraz daha uzun bir işlem yapalım:

satıryaz(3.5 * 9.4 + 7 / 5)

Scala tam ve kesirli gibi değişik bir kaç sayı tipini tanır. Hepsinin bir tür adı var. Örneğin Sayı, Uzun, İriSayı, Kesir, vb. Bunları ileride detaylı olarak inceleyeceğiz. Yukarıdaki iki örnekten 4.0 ve 3.5 kesir (İngilizcesi Double), 4 ve 6 ise birer tam sayıdır (İngilizcede Integer, kısaca Int). Eğer bir matematiksel deyiş bir kaç tür sayı içeriyorsa Scala derleyicisi mümkünse sayının türünü biraz zorlayarak değiştirebilir.

Bir deyişin sonucunu bir değişken (aslında çoğunlukla bir değişmez değer) kullanarak kaydedip daha sonra yine kullanabiliriz. Değişken ve değişmez değer isimleri (ve sonra göreceğimiz başka tür isimler) harf, sayı ve * / + - : = ! < > & ^ | gibi semboller kullanarak yazılır. Örneğin, "FutbolTopu", "BilardoTopuBeyaz1", "yardımHattı", "*+" ve "res4" (result yani sonuç)...

Bunun için iki yöntem vardır: den ve dez anahtar sözcükleri. dez sözcüğüyle sabit ve hiç değişmeyecek değerleri ve sonuçları saklayabiliriz. Bunlara değişmez değer, ya da kısaca değişmez diyelim. Ya neden bir de den anahtar sözcüğü var? Aşağıda bir örnekle ikisinin farkını hemen anlayacağız. dez ile tanımlanan değerlerin sabit olması (İngilizcede 'immutable value') aslında çok önemli bir işlevsel yazılım (functional programming) kavramıdır, ama bunu daha sonra yeri gelince daha iyi anlayacağız. Şimdilik mümkün oldukça den yerine dez komutunu kullanmaya dikkat edelim. Bu sayede yazılımın başka bir yerindeki değişkenleri yanlışlıkla bozamayız.

dez noktaSayısı = 34 + 5
satıryaz(noktaSayısı)

Bir ya da daha fazla sayıda işlemin sonucunu çıktı alanına satıryaz(deyiş) komutunu kullanarak yazabiliriz. Birden çok deyişi yan yana yazmak istersek satıryaz(deyiş1, deyiş2, deyişn) da olur; masaüstü Kojo bunları aralarına boşluk koyarak yazar, ikojo ise (d1,d2,dn) biçiminde bir sıralama olarak gösterir. Deyişler arasına virgül koymayı unutmayalım.

dez noktaSayısı = 34 + 5
satıryaz(noktaSayısı, 3 + 2, noktaSayısı / 2, 3.9 / 2.3)
den boy = noktaSayısı + 4
satıryaz(boy)

Masaüstü Kojo'nun çıktı gözü her değerin adını ve değerini yazmakla kalmaz, ikisinin arasında o değerin türünü de yazar: Int (Sayı), Double (Kesir) vb. Şimdi deneyelim:

dez noktaSayısı = 34 + 5
noktaSayısı = 10

Bu hata verdi, değil mi? dez ile tanımlanan değişkenlerin değerleri değiştirilemez. Scala derleyicisi (compiler), 'Error: reassignment to val' yani sabit bir değeri değiştirmek hata olur diyor ve izin vermiyor. 'error' hata demek.

dez x = 3.14
x = 3.1415

Şimdi boyunu 4 nokta uzatalım:

dez noktaSayısı = 34 + 5
den boy = noktaSayısı + 4
boy += 4
satıryaz(boy)

Okuyanları bilgilendirmek ve kendimize anımsatmak için satır sonlarına // yani iki taksim ya da bölüm işaretinden sonra bir açıklama yazabiliriz. Bir satıra sığmıyorsa /* ile başlayıp */ ile biten daha uzun açıklamalar ekleyebiliriz. Scala derleyicisi bunları göz ardı eder ve bu sayede bilgisayarın kafası karışmaz 8-).

/*
  Çok satırlı bir açıklama örneği
  Fahrenayttan santigrata çevirelim
*/
dez dereceF = 98.4 // vücut termometresi bunu göstersin
satıryaz(dereceF, "derece Fahrenayt",
         (dereceF-32)*5/9, "derece Santigrat")

satıryaz komutuyla yazı yazdık, yukarıda gördüğün gibi. Böyle çift tırnaklar içine alınan yazıların türüne Yazı (İngilizcesi String) diyoruz. Bu tür, sadece yazı yazmak için değil, yazılarla işlemler yapmak için de kullanılabilir:

dez adım = "Mustafa Kemal"
dez mesaj = "Merhaba " + adım
satıryaz(mesaj)

Toplama işareti sanki toplama yaparmış gibi yazıları birbirine ekleyiveriyor. Mantıklı değil mi? Yazı türüyle daha neler yazılabilir neler! İki bölüm sonra başka örnekler de göreceğiz.

İki tabanlı sayma ve parçacık işlemleri

Duymuşsundur eminim, bilgisayar devreleri aslında 2, 3, 4 gibi sayıları bile bilmez. Onun yerine sadece 0 ve 1 sayılarını tanır. Hatta tanımak dedik de aslında sadece voltaj değerlerini ve elektrik akımlarını tanır onlar. Bu uzun, ilginç ve çok keyifli bir öyküdür. Benim gibi elektrik mühendisi olmak istersen, bana emaille selam ve sorularını yollayabilirsin. Neyse, konumuza dönelim. Daha büyük sayılarla işlemler yapmak için bilgisayar onları bir 0 ve 1 dizisi olarak ele alır ve içindeki sayısız mantık devreleri sayesinde toplama, çıkarma, çarpma, bölme ve hatta türev ve integral alma gibi daha ileri matematik işlemlerini kolayca ve hiç üşenmeden halleder. Bunların detayı bilgisayar uzmanlarının işi. Biz 0 ve 1 dizilerine dönelim, çünkü herşey onlarla başlıyor! İngilizcede 'binary arithmetic' denir. Biz iki tabanlı sayma diyelim. Her sayı, 0 ve 1'lerden oluşan bir dizi olduğu için, direk onun parçacıkları üzerinde de işlemler yapabiliriz. Bu işlemlere 'bitwise' yani parçacıksal işlem denir. Böyle 0 ve 1 dizilerinin her bir elemanına İngilizcede 'bit' denir. Saçlarımızda yaşayan ve zararsız küçük böcekcikler değil elbet! İngilizcede azıcık, küçücük anlamlarına geliyor. Biz parçacık diyelim istersen. Çok uzattık. Kusura kalma. Şimdi parçacıkları teker teker nasıl işleme sokarız bir kaç örnek görelim:

satıryaz(3 & 2)

Bu & imiyle mantıksal 've' işlemi yapıyoruz (İngilizcesi 'logic and'). Sadece 1 ve 1 sonuç olarak 1 verir. Girdilerden biri 0 olursa sonucu da 0 olur. | da mantıksal veya (or) işlemi. Sadece 0 veya 0 sonuç olarak 0 verir. Biri 1 olursa sonuç da 1 olur:

satıryaz(1 | 2)

Mantıksal dışlayan veya işlemi (xor yani exclusive or) aşağıda. Sadece biri 1 öbürü 0 olunca 1 verir:

satıryaz(1 ^ 2)

Parçacıkları sola kaydırmak ikiyle çarpmaya denk! İki kere kaydırsak ne olur? Ya 10, 20 ya da 30 kere kaydırsak?

satıryaz(1 << 2)
satıryaz(1 << 10)
satıryaz(1 << 20)
satıryaz(1 << 30)

Nasıl da hızlı büyüdü, değil mi? 10 kere kaydırmak, 2'yi on kere kendisiyle çarpmak demek, yani iki üssü ona denk. O da 1024 ediyor. Binden birazcık fazla. 2 üssü 20, bir milyonu; 2 üssü 30 da bir milyarı geçti...

Sağa kaydır ama eksiyse eksi kalsın:

satıryaz(-24 >> 2)
satıryaz(-14 >>> 2)

Sağa kaydırıyor ama sonuca bakın! Ben anlamadım vallahi. Ya sen? Bakın bu çok ilginç. Parçacıkları sola kaydırmak ikiyle çarpmaya denk! Sağa kaydırmaksa ikiye bölmeye benzemiyor mu? Bu daha önce de dediğim gibi uzmanlık konusu. Üzerinde yazılmış pek çok bilimsel makale ve ders kitapları var. Hatta bazıları çok azımızın anlayabileceği yüksek ihtisas kitapları! Bugünlük bu kadarı fazla bile. Ama sen istersen bu yazılımcığı kurcala. -14 yerine 14'ü dene. Çalıştır ki sonucu hemen göresin. Bakarsın uzman olmak istersin. Neden olmasın?

Farkında mısınız? Bu kadarcık bilgiyle bile artık çok güçlü bir hesap makinemiz oldu. Ama dahası var! Bir sonraki bölümde program akışı nasıl düzenlenir öğrenecek ve yazılımcıklarımızı çok daha becerikli hale getireceğiz.

5Yazılım Akışı: Eğer, Yoksa ve İçin

Şu ana kadar yazdığımız yazılımcıkların komutları, baştan sona kadar, sırayla, satır satır ve teker teker çalıştılar. Ama pek çok durumda program akışını değiştirmek isteriz. Yani komutların çalışma sırasını duruma göre değiştiririz. Bu sayede, bazı komutları yineler, bazılarını atlarız.

İlk önce bir komut dizisi (ya da komut bloğu da denir) oluştururuz. Bunun için komutları kıvrık parantezler, yani {} içine alıyoruz. Bu komut dizisi istediğimiz kadar satır ve hatta başka komut dizileri de içerebilir. Dizinin içindeki son komut dizinin değerini belirler.

Program akışını değiştirmek için kullandığımız bir kaç değişik yöntem var. Gelin en önemlilerinden biri olan, eğer/yoksa yapısıyla başlayalım. Bu iki anahtar sözcük bir koşul kullanarak bir karar çatalı oluşturmaya yarar.

eğer

Genel olarak şöyle yazarız: eğer (koşul) dizi1/deyiş1 yoksa dizi2/deyiş2. Eğer koşul doğruysa ilk kısım yani dizi1/deyiş1 çalışır. Yoksa 'yoksa' sözcüğünden sonra gelenler çalışır.

eğer(doğru) satıryaz("Doğru") yoksa satıryaz("Doğru değil")

Bunu daha kısa da yazabiliriz. 'eğer/yoksa' deyişi hep bir değer verir. Bakın bunu da satıryaz komutuyla yazıyoruz.

satıryaz(eğer(doğru) "Doğru" yoksa "Doğru değil")

Kullandığımız koşulun geçerli olması için 'doğru' ya da 'yanlış' (İngilizcede 'true' veya 'false') değerlerinden birini vermesi gerekir. Bu değerlerin türüne biz İkil deriz (İngilizcesi Boolean). Bu koşulu sağlayan işlemlere karşılaştırma işlemi deriz. Bunlar matematiksel işlemler ya da benzerleri olabilir. İlk önce matematiksel olanları, yani sayıları karşılaştıran işlemleri görelim. Başka tür değerleri (örneğin sözcükleri) karşılaştırmayı sonraya bırakalım.

satıryaz(1 > 2)  // büyüktür
satıryaz(1 < 2)  // küçüktür
satıryaz(1 == 2) // eşittir
satıryaz(1 >= 2) // büyük ya da eşittir
satıryaz(1 != 2) // eşit değil
satıryaz(1 <= 2) // küçük ya da eşittir

eğer/yoksa yapısının nasıl çalıştığını anlamak çok kolay. Koşul doğruysa 'yoksa' sözcüğünden önceki komutlar çalıştırılırlar. Yok, eğer koşul doğru değilse, o halde 'yoksa' sözcüğünden sonraki komutlar çalıştırılır. Scala ve diğer işlevsel dillere benzemeyen yazılım dilleri (örneğin, C, Java, Python), eğer/yoksa yapısını sadece akışı belirlemek için kullanırlar. Ama Scala, Haskell ve diğer işlevsel diller gibi, eğer/yoksa yapısından bir değer beklerler. Onun için 'eğer' genelde tek başına kullanılamaz. Arkasından hemen 'yoksa' gelir ve iki durumda da bir değer geri bildirilir. Bunun bir istisnası da vardır, ama onu da sonraya bırakalım. Bakın bu örneklerde koşul olarak hep yalın sayıları karşılaştırıyoruz. Ama elbette başka dez değerler ve komutlar da kullanabiliriz. Yeter ki bir İkil, yani doğru ya da yanlış değeri olsun.

satıryaz(eğer (1>2) 4 yoksa 5)
satıryaz(eğer (1<2) 6 yoksa 7)
dez deneme1 = eğer (1==2) 8 yoksa 9
satıryaz(deneme1)
dez kitapsa = 6 >= 3 // bu doğru
dez değer = 16
dez sayı = 10
dez satış = eğer (kitapsa) değer * sayı yoksa {
  dez birim = değer / sayı
  birim * 3
}
satıryaz(satış) // doğru mu?

{ ve } içinde bir dizi deyiş ya da bir dizi komut yazabiliyoruz. satış değeri dizi içindeki en son deyişin değeri olur.

Gerçek hayatta da olduğu gibi bazen bir kaç koşul bir araya gelir. Bu durumda iki tane mantıksal işlem kullanırız. Bunlardan birincisi && mantıksal 'Ve' anlamına gelir. İkincisi de || mantıksal 'Veya' anlamına gelir. Bunları sakın parçacıkları işleyen & ve | işlemleriyle karıştırmayalım. Aslında ilişkili ve benzer kavramlar. Ama dikkat edelim. Çok uzatmadan birkaç örnek görelim, göreceksin çok doğal gelecek. Ne de olsa mantık hepimizde var.

dez kitapsa = 6 >= 3
dez değer = 16
dez sayı = 10
dez satış =
    eğer ( (kitapsa && değer > 5) || ( sayı > 30) ) değer * sayı
    yoksa değer / sayı
satıryaz(satış)

yineleDoğruKaldıkça

Bu yapı bilhassa eski yazılım dillerinde çok kullanılır. İngilizce'de pek çok farklı anlama gelen 'while' sözcüğü komut olarak kullanılır. Türkçemize 'o halde' ya da 'o sırada' diye de çevirebiliriz. Genel olarak, yineleDoğruKaldıkça (koşul) dizi/deyiş yapısı kullanarak komut dizisini tekrar tekrar çalıştırabiliriz. Koşul sağlandığı sürece yineleme devam eder. Koşul değişince, yani artık doğru olmadığında yineleme son bulur. Bir örnekle anlamak çok daha kolay olacak. Ama ilk önce bir değişkenle başlayalım:

den toplam = 18
yineleDoğruKaldıkça (toplam < 15) toplam += 5
satıryaz(toplam)

Kolay değil mi? Sizce kaç kere yinelenecek komut? Sonunda toplam kaç olacak? Bu biraz hileli bir soru oldu. Aslında yinelenecek mi, toplam değişecek mi diye mi sormalıydık? Toplamın ilk değerini ya da koşulu değiştirip tekrar çalıştırabiliriz elbet. Bu 'yineleDoğruKaldıkça' yapısını 'yap' adlı anahtar sözcüğü kullanarak tersine de çevirebiliriz: yap dizi/deyiş yineleDoğruKaldıkça (koşul). Bakın burada ilk önce komut dizisi çalıştırılır sonra koşula bakılır. Doğruysa komut dizisi yinelenir:

den toplam = 18
yap toplam += 5
yineleDoğruKaldıkça (toplam < 15)
satıryaz(toplam)

Gördük ki bu sefer toplam 23 oldu. Bir önceki örnekteki halbuki 18 olmuştu. Bakın bu yapıyı iki sayının ortak paydalarının en büyüğünü hesaplamak için kullanalım:

// en büyük ortak paydayı bulalım
den x = 36
den y = 99
yineleDoğruKaldıkça (x != 0) {
    dez yardımcı = x
    x = y % x
    y = yardımcı
}
satıryaz("ortak paydaların en büyüğü: " + y)

için

Komut dizilerini kolayca yinelemek için kullanabileceğimiz bir yöntem daha var ki belki de en faydalısı. için (aralık) dizi/deyiş sayesinde verilen aralıktaki her bir değer için dizi/deyiş yinelenir. Aralık da nedir mi? Hemen bir örnek görelim:

için ( i <- 1 |-| 4 ) yaz("merhaba!")

Bunun İngilizcesi de şöyle:

için ( i <- 1 to 4 ) yaz("merhaba!")

'to' sözcüğü de bizim yapım/çekim ekimiz gibi, birden dörde kadar derken dörde sözcüğündeki '-e' anlamında. İngilizceyle Türkçe ne kadar farklı sanki, değil mi? Bir de bana sorun. 22 yaşında yaşamak için Amerika'ya gittiğimde, ki daha önce yurtdışına çıktığım günler sayılıdır, o kadar zorluk çektim ki! Güya iyi biliyordum hem de İngilizceyi! İşimize dönelim: Aralık burada birden dörde kadar olan sayılar elbet. i değişkeni 1 değeriyle başlıyor ve her tekrarda bir artıyor. Son sayı burada 4. Ama sonuncu sayıya gelmeden hemen önce durmak istersek |-| yerine |- imgesini kullanıyoruz:

için ( i <- 1 |- 12 )  {
  dez kare = i*i
  satıryaz(i, kare)
}

Bunun İngilizcesi de şöyle:

için (i <- 1 until 12) {
  dez kare = i*i
  satıryaz(i, kare)
}

İngilizce'de 'until 12', 12'ye kadar anlamına geliyor.

Biliyor musun, bu yineleme işlemlerini birden çok boyutta yapmak bilgisayarla çok kolay. Birden fazla aralık vereceğiz ve her aralık için de bir değişken (aslında değişiyor gibi göründüğüne bakma, bunlar da değişmez). Tek dikkat etmemiz gereken ikisi arasına bir noktalı virgül koymak. Bakın ne kolay!

için (i <- 1 |- 5 ; j <- "abc") satıryaz(i, j)

Bakın şu işe! Sayı yerine harfler kullandık! için yapısı içinde kullandığımız kümeler illa da sayılardan oluşmak zorunda değil yani. Genel olarak biz bunlara küme tekerleme diyebiliriz (İngilizcesi: iterating through a set or collection) yani teker teker her küme elemanını ele alıyoruz. "abc" yazısı da aslında bir harf kümesi ya da kolleksiyonu. Bakın hep küme ya da kolleksiyon dedim. Bu kavramlar yakın ama ufak farklılıkları var. Daha sonra bunlara verilen anlamı daha iyi anlayacağız. Şu anda çok da önemli değil gerçekten. Neyse. Harflerle tekerlemeye bir örnek daha verelim ve devam edelim:

için(c<-"merhaba!") satıryaz(c)

Bu için komudu aslında çok faydalı. Sadece çıktı için kullanılmıyor. Anahtar sözcük ver ile birlikte kullanarak bir dizi değer oluşturabiliriz. Bir küçük örnekle yetinelim şimdilik:

dez ikililer = için(i <- 1 |- 5 ; j <- "abc") ver(i, j)
yaz(ikililer)

Şimdi de matematik, bilhassa kartezyen geometrisi sevenlere bir sürprizimiz var. Kaplumbağacığı kullanarak bir eğri çizelim. Neyin eğrisi? İki boyutlu bir polinom, yani çok terimli bir matematiksel deyiş. Genel olarak a*x^2 + b*x + c diye yazabiliriz. Yine bu çok faydalı olan için yapısıyla:

sil
yaklaşXY(0.9, 0.5, 60, 400)
tanım eğri(x: Kesir) = 0.01 * x * x - 0.5 * x - 10
ızgarayıGöster(); eksenleriGöster()
dez aralık = 200
atla(-aralık,eğri(-aralık))
hızıKur(orta)
için(x <- -aralık+10 |-| aralık+100; eğer (x % 10 == 0)) noktayaGit(x, eğri(x))

eğri adında yeni bir işlev tanımlayıverdik. Bunu daha sonra daha iyi anlayacağız. (ikojo'da ızgara ve eksen komutları henüz yok; o satırı silip çalıştır, parabol yine çizilir.)

Şimdilik x ve y eksenlerini ve kare çizgileri silelim. Ve bir sonraki bölümle devam edelim!

eksenleriGizle(); ızgarayıGizle()

6Yalın Değerler, Sayılar, Kesirler ve Yazılar

Bütün hesaplar gibi, bütün programların da yapıtaşları 'yalın' değerlerdir. Nedir onlar? Örneğin 1, 2, 1/3, 3.14, 'a', 'b', 'ç', "Merhaba Güzel Dünya!", ve aklın alamayacağı kadar çok sabit yani değişmez değerler. Hepsini biliyorsun elbette. Tabii bu örnekler biraz karışık oldu. Bir harfle bir sayıyı karıştırmak iyi olmaz. Onun için bunları türlerine ayırmak Scala'nın çok iyi becerdiği bir konu. Başka pek çok dil de buna dikkat eder. Ama göreceksin Scala bir başka. Uzatmadan en temelinden başlayalım.

Temel Türler

Scala kendiliğinden bazı temel türler tanımlar. Daha ilerde göreceğimiz gibi sen de kendi türlerini tanımlayıp Scala'dakilere ekleyebilirsin. Ama ilk önce bu temel türlerle tanışalım, işi kolay kılalım.

TürAçıklama
Lokmaİngilizcesi Byte. -128'den başlar, -127 -126 ... -1 0 1 2 ... 127'e kadar gider. Neden seçip ayırmış bu sayıları acaba? Daha önce de konuştuk ya, bilgisayar, her sayıyı sıfır ve bir sayılarının dizisi olarak saklar ve işler. Bu sayı dizilerindeki bir ve sıfır sayılarına parçacık demiştik. Sekiz tanesini bir arada ele alırsak, ona Lokma diyoruz. Eksi sayıları artılardan ayırmak için o parçacıklardan birine özel bir anlam veriyoruz. Geriye kalıyor 7 parçacık. İkinin üstlerini anımsayalım: 2 4 8 16 32 64 128. Yani 7 parçacıkla 128'e kadar sayabiliyoruz. İngilizcede buna "8-bit signed 2's complement" derler. Uzattık ama temelin temeli bu! Neyse, şimdilik bu kadar yetsin
Kısa(Short). İki lokma yani 16 parçacık. -32,768'den başlayıp 32,767'yle biter
Sayı(Integer) Dört lokma yani 32 parçacık. -2,147,483,648'den 2,147,483,647'e kadar gider
Uzun(Long) İki sayı yani 64 parçacık. -2^63 ile 2^63-1 arası
UfakKesir(Float) 32 parçacık kullanarak kesirli sayıları ifade eder. İşini ciddiye alır ve IEEE'nin 754 no'lu standardına göre yapar. Bilirsin pi sayısının basamakları bitmek bilmez. UfakKesir olarak ama tam değeri şudur: 3.1415927
Kesir(Double) Ufak kesirden iki kat daha hassas. Bunu da 64 parçacık kullanarak sağlar. Pi sayısını şöyle bilir: 3.141592653589793. Aynı ufak kesir gibi ciddi standarda uyar.
Harf(Char) Unicode karakter. 16 parçacık (bit) yani 2 lokma kullanır. Eksi artı sıkıntısı yoktur. İngilizce'de 'unsigned' denir.
Yazı(String) Sınırsız bir harf dizisidir. Çok basit olsa da çok faydalıdır.
İkil(Boolean) doğru ve yanlış değerlerini bilir başka da birşey bilmez. Başka bir lakabı da Seçim.

Sayma Sayıları

Tabloda da gördüğümüz gibi dört temel tür var (bir de iri sayılar var. Çok büyük sayma sayılarıyla oynamak için tasarlanmış sayı türünün adı İriSayı (BigInt). Eğer canın mümkün olan bütün briç ellerini saymak isterse Uzun yetmez İriSayı gerekir. Merak edersen bana haber ver. Konuşuruz.) Bunları tekrar anımsayalım çünkü çok faydalılar: Lokma Kısa Sayı ve Uzun. Yalın sayı değerlerini girmenin tek yolu var sanma. Farklı tabanlar kullanabiliriz. Sadece onluk tabanla sınırlı değil becerimiz. Onaltılık taban da bazen işe yarar. Bunu belirtmenin yolu ilk rakamı 0 yapmak.

Bize en doğal gelen onluk tabanın yalın sayıları 1 2 3 ... 8 ya da 9 ile başlar (decimal):

satıryaz(17)
satıryaz(653)
dez benimSayım = 653
dez benimSayım2: Sayı = 653
satıryaz(benimSayım == benimSayım2)

Onaltılık taban da bilgisayara ve bazı algoritmalara çok doğal gelir (hexadecimal): Sıfırla başlar ve x harfini ekleriz. Büyük X de olur. Farketmez. Sonra da onaltılık tabanın 16 sayısından seçeneklerle devam ederiz. İlk onunu herkes bilir, 0 1 2 ... 8 9. Sonra değeri onluk tabanda 10 olan a ya da A, sonra 11'e denk b/B ... en son da 15'e denk olan f/F. Ne ilginç değil mi? 13 yazmak yerine 0xd ile batıl inançlara da son verebiliriz belki!

dez x1 = 13
dez x2 = 0xd
eğer (x1 == x2) satıryaz("kim korkar d'den?!")
satıryaz(0x23)   // onaltılık taban. Yani onluk tabanda 35
satıryaz(0x01FF) // onluk tabanda 511
satıryaz(0xcb17) // onluk tabanda 51991

Varsayılan durum bunların Sayı türünde olduğudur. Ama bazen Uzun sayı olması gerekebilir. Onu ifade etmek de kolay. Sonuna "l" ya da "L" (yani İngilizce Long) koyarız. U veya u da iyi olurdu ama bunu Kojo'ya eklemeyi bilemedim ben. Belki de Scala, hatta Scala'nın atası olan Java'yı güncellemek gerekmesin?

satıryaz(0XFAF1L) // Uzun sayı onluk tabanda 64241

Yalın değerlerin Kısa ve Lokma türlerinde olmasını isteriz bazen. Ama, dikkat edelim ki geçerli aralığı aşmasın. Yoksa ne olur? Deneyerek daha iyi anlarsın sanıyorum.

dez birLokma: Lokma = 27
dez kısaSayı: Kısa = 1024
satıryaz(birLokma, kısaSayı)
dez sorunYok: Lokma = 127
dez sorunsuz: Lokma = -128
dez hataVerir: Lokma = 128

Son satır hata verdi. "Error... type mismatch" gibi birşeyler dedi. Yani türler uyuşmadı diyor. 128 sayısı Lokma'ya sığmıyor.

Kesirli sayılar

Kesirli sayıları yalın olarak ifade etmek için nokta kullanırız elbet. Sıfır dışında bir sayıyla başlarlar. İşin ilginci sonuna E veya e harfi ekleyebiliriz. O İngilizce exponent yani üssü anlamına gelir ve hemen arkasından bir tam sayı (eksi de olabilir) ekleriz. Örneklerle anlamak daha kolay olacak:

satıryaz(9.876)
dez çokKüçük = 1.2345e-5
dez epeyBüyük = 9.87E45
satıryaz(çokKüçük, epeyBüyük)

Daha iyi anlamak için şu örneklere bakalım:

satıryaz(2e3)   // yani 2000. üç sıfır ekliyoruz yani
satıryaz(3e-2)  // yani 0.03. yüzle çarparsak 3 oluyor, fark ettin mi?
satıryaz(0.5e1) // 5.0 yazsak daha açık olurdu. Sadece daha iyi anlamak için
satıryaz(8e9)

Son satır 2022'nin son günlerinde yeryüzündeki insan kardeşlerimizin sayısını gösteriyordu! Nüfusumuz kaç oldu?

Varsayılan, kesirli yalın sayıların Kesir türünde olmasıdır. Ama istersen UfakKesir de olabilirler. Sonuna f ya da F getirmen yeter. Bu İngilizce "float" sözcüğünden gelir o da bizim noktanın adı "floating point" olduğu için. Eğer Kesir olduğunu açık etmek istersen sonuna d veya D getiriver. O da "double" sözcüğünün baş harfi. Neden double? Çünkü UfakKesir 32 parçacık kullanırken, Kesir 64, yani iki katı parçacık yani daha çok bellek kullanır. Bellek büyüdükçe de hassasiyet artar. Öte yandan ne kadar çok bellek kullanırsak, hem belleğin sınırlarını aşma tehlikesi artar hem de program daha yavaş çalışır. Onun için bazı programları yazarken hangi tür sayıları seçeceğimize çok dikkat ederiz.

dez ufakkesir1 = 1.5324F
dez ufak2 = 3e5f
satıryaz(ufakkesir1, ufak2)

Harfler

Bilgisayar, okumayı yazmayı bilmese de pek çok karakteri ya da harfi tanır. Sadece Türkçe ve İngilizce alfabeler değil hem de. Daha başka pek çok tek harf dediğimiz sembol, ya da karakter var. İngilizce adı da zaten Char, yani character sözcüğünün kısaltması. Bu harfleri yalın olarak girmek kolay. Tek tırnak içine alıverelim:

dez harf = 'A'
satıryaz(harf)

Bu değeri girmenin daha zor yolları da var! Şaka şaka. Daha genel yolları bilmekte fayda var: Evrensel kodlama şöyle oluyor. '' ve '￿' arasında yani onaltı tabanlı bir sayı seçiyoruz.

dez a = 'A' // A oldu
dez e = 'E' // E oldu
satıryaz(a, e)

Evrensel Kod (bunların genel adı Unicode) hakkında Vikipedi makalesi

En son olarak, 'kaçış' yalın harfi diye de bilinen bir kaç özel duruma bakalım. Bunlara harf demek doğru olmuyor ama ne yapalım. Referans kaynağından yalın kaçış harfleri listesi: Java'nın teknik özelliklerini anlatan siteden. Her türlü yazılım sorularına yanıt bulabildiğimiz meşhur bir siteden de benzer bilgiler edinebiliriz: stackoverflow.com'dan.

Yazılar

Yalın bir yazı, çift tırnaklar arasına aldığımız harflerden oluşur. Birkaç tane örnek gördük bile. Birkaç tane daha olsun:

dez merhaba = "merhaba dünya!"
satıryaz(merhaba)

Bazı özel karakterler var ki onları yazıya eklemek için özel bir kaçış karakteri kullanmamız gerekiyor. Bunların hepsi iki karakterden oluşuyor. Birincisi \ olan kaçış karakteri. Arkasından da bir özel karakter daha gelir. En yaygın olanlar ve işlevleri şöyle:

KaçışAnlamıKaçışAnlamı
\nyeni satır\bgeri adım
\tbüyük ara\fbir sayfa at
\rsatır başı\"çift tırnak
\'tek tırnak\\geri taksim işareti
dez kaçışKarışıklığı = "\\\"\'"
satıryaz(kaçışKarışıklığı)

Bu son örnekte gördüğümüz karışıklıktan kurtulmak için Scala üç tane çift tırnak (""") arasına aldığımız yazıları olduğu gibi yazar. Yani yeni satır, tek tırnak, çift tırnak ne varsa olduğu gibi alır. Bakın bir örnekle anlayalım:

satıryaz("""Kojo'ya hoşgeldin!
"Kaplumbağa" ile çizim yapmak hakkında daha çok bilgi edinmek için
\Kaplumbağacığın Kullanılışı\ adlı bölüme bakabilirsin.""")

İkil

İngilizcesi boolean olan bu tür, mantıksal işlemlerde ve koşullar ifade etmek için kullanılır. Çok özel bir türdür. Sadece iki yalın değerle iş biter:

dez büyükOlsunMu = doğru
dez birazŞapşalMı = yanlış
satıryaz(büyükOlsunMu, birazŞapşalMı)

7İşlevler

İşlevlerin başka bir adı da fonksiyon. Matematikte de kullanırız bu terimi. Yazılım yazarken biraz farklı bir anlamı var. İşlevler, komut ve deyiş dizileri gibidirler. Tekrar tekrar kullanmak istediğimizde de çok faydalı olurlar. Onun için ilk önce bir ad vermek gerekir. Sonra da bir ya da daha fazla parametre girişi yapabiliriz. tanım özel sözcüğüyle başlarız bir işlev tanımlamaya. Arkasından da işlevin adını veririz. Yine en iyisi bir güzel örnekle başlayalım. Bakın işimiz şu. İki sayı arasından hangisinin daha büyük olduğunu bulmak.

tanım büyük(x: Sayı, y: Sayı): Sayı = {
    eğer (x > y) x
    yoksa y
}
satıryaz(büyük(6, 7))

İşlevin adını "büyük" koyduk. Hemen tanım anahtar sözcüğünden sonra geldi. Arkadan parantez içinde gerekli iki sayının adlarını tanımladık. Bunlara genelde işlevin değiştirge ya da parametreleri denir. Her değiştirge için türünü de belirtmemiz gerekir. Değiştirgenin adıyla türü arasına da iki nokta üstüste konur. Bu işlevimizin iki değiştirgesi var ve ikisi de Sayı türünde. Parantezden sonra da işlevimizin çıktısının türünü bildiriyoruz ki o da bir sayı. En son da bir eşittir işareti ve sonra kıvrık parantezler içinde işlevin bedeni geliyor. Bu şekilde tanımladığımız anda artık onu çağırabilir ve kullanabiliriz: büyük(6,7).

Genelde her işlevin bir çıktısı vardır. Ama bazen çıktısız işlevler de olur. O tür işlevlerin sadece yan etkisi olacaktır. Örneğin ekrana yazı yazar ya da diske bir dosya yazar. O durumda çıktının türü Birim olur (İngilizcesi Unit) ve değer yok ya da boşluk (void) anlamına gelir. Bu tür işlevlere yöntem, metod, prosedür ya da altprogram da denir bazen (procedure ve subroutine İngilizce'de iki ilişkili sözcük). Daha önce birkaç örnek yöntem görmüştük:

sil
ileri(100)
satıryaz("bunu yazan işleve yöntem de deriz")

Yan etkisi olmayan işlevlere ise arı işlevler deriz (pure functions). Bunlar matematikte kullandığımız işlevlere benzerler. Aynı girdi değerleri hep aynı çıktıyı verir. Arı işlevler, adını işlevsel yazılım koyduğumuz bir yazılım tarzının temel yapıtaşıdır.

Özyineli (recursive) İşlevler

İşlevler bazen kendilerini çağırabilirler (recursion). Özyineli işlevlerle daha önce gördüğümüz küme tekerlemesi, yani kümenin her elemanını teker teker ele alma işlemini daha kısa şekilde ifade edebiliriz. Yine bir örnekle anlamak daha kolay olacak. Bakın hiç yardımcı değişken kullanmadan iki sayının bölenlerinin en büyüğünü bulabiliriz:

// en büyük ortak payda
tanım enbop(x: Uzun, y: Uzun): Uzun = eğer (y == 0) x yoksa enbop(y, x % y)
satıryaz(enbop(96, 128))

Bu tanımı daha önce yineleDoğruKaldıkça komutuyla yaptığımız tanımla karşılaştırmanda fayda var (bir önceki bölüm).

Şimdi de daha renkli bir özyineleme görelim. Bu işlev kendini iki kere çağırarak kaplumbağacığa bir ağacın dallarını çizdiriyor. Bu tür ağaçlara ikil ağaç (binary tree) deriz. Sen de beğendin mi? Özyineleme nasıl duruyor? Yukarıdaki enbop işlevinde y'nin değeri sıfır olunca. Aşağıda ise uzaklık dört ya da daha küçük olduğunda.

tanım ağaç(boy: Kesir): Birim = {
    // sayıya yöntemi kesirli sayıyı tam sayıya çeviriyor
    // yani boy 1.75 olursa boy.sayıya 1 oluyor
    tanım renk = Renk(
        boy.sayıya % 255,
        mutlakDeğer(255 - boy * 3).sayıya % 255,
        125)
    eğer(boy > 4) {
        kalemKalınlığınıKur(boy / 7)
        kalemRenginiKur(renk)
        ileri(boy)
        sağ(25)
        ağaç(boy * 0.8 - 2)
        sol(45)
        ağaç(boy - 10)
        sağ(20)
        ileri(-boy)
    }
}
sil()
hızıKur(hızlı)
konumuKur(100, -200)
ağaç(90) // 100, 120 ve 150 gibi boyları da dene!

8Nesneler ve Sınıflar

Herşey bir Nesne!

Scala nesneye yönelik bir yazılım dili olarak tasarlanmıştır. İngilizce'de Object Oriented Programming, hatta kısaca OOP diye bilinen bu kavramı destekleyen pek çok dil var. Bu kavramın temelinde nesneler yatıyor. Nesne ne demek biliriz elbet. Ama yazılım yazmaya gelince nesne ne demek? Herhangi bir anda programın içinde bulunduğu evre (state) nesnelerin yapısında saklanıyor demek. Bu ne demek? Programdaki nesnelere bakarak program hakkındaki herşeyi bilebiliriz, bir. İki, programın çalışması ve gerekli değişiklikleri yerine getirmesi için de içindeki nesnelerden bir kısmının değişmesini sağlamak yeter. Üçüncü önemli nokta da şu: nesnelerin değişmesi ancak nesnelerin üzerinde tanımlanmış yöntemleri kullanarak sağlanır. Kojo da sadece bir nesne kümesinden ibaret! Epey çok nesne var elbette Kojo yazılımının içinde. Bunlar düzenleyiciye yazdığımız komutları işleyerek kaplumbağayı hareket ettiriyor, çizgiler çizdiriyor ya da çıktı alanına yazılar yazıyor.

Temel türleri hatırlıyorsun, değil mi? Sayı türleri, yazı türleri... Yeni nesneler tanımlamak için de yeni bir tür tanımlarız ilk önce. Yani yeni nesnenin nasıl bir evre ve davranışları olduğunu belirleyen bir sınıf oluştururuz (sınıf ve tür çok benzer kavramlar). Sınıfın tanımı, nesnelerinin ne değişkenler içereceğini ve ne yöntemler (yani davranışlar) sunacağını belirler. Yöntem dediğimiz de aslında birer işlevdir ve nesnenin uzuvları gibidirler, nesnenin içindeki değişkenleri bilir, onlara göre davranır ve hatta o değişkenleri değiştirebilirler. Sınıfın içinde tanımlanan bu yöntemler de tanım anahtar komutu, ya da özel yazılım sözcüğü, kullanarak tanımlanırlar. Aynı daha önce gördüğümüz işlevler gibi. Yöntemler yöntem ya da arı işlev olabilirler. Sana ve ne yapmak istediğine bakar.

Yeni bir sınıf tanımladığında yeni tür bir ya da daha çok nesne tanımlamış oluyorsun. Bu yeni tür, daha önce tanımlanmış, hatta Scala'yla hazır gelen Sayı, Kesir gibi türlerin hepsiyle bir tutulur. Yani Scala hiç ayırım yapmaz! Scala'nın türler ve sınıflar arasında taraf tutmaması, yani herşeyin bir nesne ve her nesnenin de bir türü olması ne kadar yararlıdır yakında göreceğiz.

Haydi iki boyutlu bir nokta tanımı yaparak başlayalım yeni türler üretmeye! İki tane değişkeni olacak, x ve y. Sınıf adlarını da geleneksel olarak büyük harfle başlıyoruz, Sayı, Harf gibi temel türlerde olduğu gibi. Ama sınıf içindeki değerler, değişkenler ve yöntemler küçük harflerle yazılıyor. Bakın nesnenin tanımı şu kadar! Daha doğrusu sınıf belirlendi. Yeni bir tür yarattık bile. Bu sınıfın bir elemanı, yani bu türden yeni bir nesne oluşturmak da çok kolay. Sizi fazla şaşırtmayacak yeni anahtar sözcüğünü kullanıyoruz. Nesnenin iç değerlerini ele almak ya da değiştirmek için de nesnenin adına nokta yani . ekliyor arkasından da iç değişkenin adını yazıyoruz. İç değişkenlerin değerini okumak için de aynı şeyi yapıyoruz:

// Anahtar kelimemiz sınıf:
sınıf Nokta {
    den x = 0
    den y = 0
}
// x ve y için başlangıç değeri gerekli. Ve 0 mantıklı gibi.
// Ama daha da iyi bir çözüm bulacağız yakında..
dez n = yeni Nokta
n.x = 3
n.y = 4
satıryaz(n.x, n.y)

Bu biraz zahmetli oldu yalnız. Her yeni nokta için x ve y değerlerini bu şekilde belirlememiz fazla zamanımızı alıyor. Bunun daha kolay bir yolu var. Nesneyi oluştururken noktanın koordinatlarını girmek daha kullanışlı olurdu. Bunun için yapmamız gereken değişiklik küçük. Sonra da yeni bir nokta tanımlayalım:

sınıf Nokta(den x: Sayı, den y: Sayı)
dez n = yeni Nokta(3, 4)
satıryaz(n.x, n.y)

Bakın ilk nokta tanımda 0 değerini vererek x ve y'nin Sayı türünde olduğunu belirlemiştik aslında. İkinci tanımda ise tür bilgisini açık açık verdik. Mantıklı, değil mi? Derleyici ne yapması gerektiğini biliyor iki durumda da. Herneyse, bu biraz ileri bir yazılım kavramı oldu. Biz noktalara dönelim. Her nokta aslında iki boyutlu bir vektör demektir. İki noktayı toplamak demek iki vektörü birbirine eklemek demek. Nasıl yapacağız? Nokta sınıfına yeni bir yöntem eklemek en mantıklısı. Deneyelim. Bakın bu basit bir işlev ama sınıf tanımının içinde olması onu özel bir yöntem haline getiriyor. Neden özel? Çünkü nesnenin iç değerlerini okuyabiliyor ve iç değişkenlerini değiştirebiliyor. Bakın şimdi iki vektörü birbirine ekleyelim, bakalım ne bulacağız:

sınıf Nokta(den x:Sayı, den y:Sayı) {
    tanım vektörToplama(yeniNokta: Nokta): Nokta = {
        yeni Nokta(x + yeniNokta.x, y + yeniNokta.y)
    }
}
dez n1 = yeni Nokta(3,4)
dez n2 = yeni Nokta(7,1)
dez n3 = n1.vektörToplama(n2)
satıryaz(n3.x, n3.y)

Eğer Java yazılım yazmayı biliyorsan bu çok tanıdık gelecektir. Ama n1+n2 yazmak çok daha doğal olurdu, değil mi? Scala ile mümkün! Gelin deneyelim. Hatta vektör çıkarmayı da tanımlayıverelim:

sınıf Nokta(den x: Sayı, den y:Sayı) yayar BaskınYazıyaYöntemiyle {
    tanım +(yeniNokta: Nokta): Nokta = {
        yeni Nokta(x + yeniNokta.x, y + yeniNokta.y)
    }
    tanım -(yeniNokta: Nokta): Nokta = {
       yeni Nokta(x - yeniNokta.x, y - yeniNokta.y)
    }
    baskın tanım yazıya = "Nokta("+x+", "+y+")"
}
dez n1 = yeni Nokta(3,4)
dez n2 = yeni Nokta(7,2)
dez n3 = yeni Nokta(-2,2)
dez n4 = n1+n2-n3
satıryaz(n4)

Bu örnekle iki özel/anahtar sözcükle tanışıyoruz. yayar ve baskın. İkincisi daha kolay. Onunla başlayalım: üstüne yazmak ya da yeniden tanımlamak anlamlarına geliyor. Neyin üzerine yazıyor ve tekrar tanımlıyoruz? yazıya adlı işlevi. Peki aslı nereden geliyor ki tekrar tanımlayalım? Hani dedik ya herşey bir nesne. Gerçekten de adı Nesne olan bir tür var ve aslında bütün türlerin temelini oluşturuyor. Bu temel sınıf ne demek daha sonraya bırakalım. Ama bilmemiz gereken şey şu: bu temel sınıfın yazıya adlı bir yöntemi var ve bu yöntemin her nesne için çalışması gerek. Bu yöntem her nesneyi okunabilecek bir yazı olarak ifade ediyor. Aslında o temel (ya da üst) sınıf İngilizce'yle yazılı. yazıya değil toString adında. Onun için şöyle de yazabiliriz:

dez n0 = yeni Nesne()
satıryaz(n0.yazıya)
// her tür kendisi için yazıya yöntemini yeniden tanımlıyor:
dez n1 = 9
satıryaz(n1.yazıya)

dez n2 = Dizin(9, 99)
satıryaz(n2.yazıya)
// bu 'durum sınıf' nedir birazdan göreceğiz!
durum sınıf AkıllıSayı(dez sayı: Sayı) /* yayar BaskınYazıyaYöntemiyle */ {
    baskın tanım toString = "Ben bir sayıyım. Değerim de " + sayı + "'dur."
    // baskın tanım yazıya = "Ben bir sayıyım. Değerim de " + sayı + "'dur."
}

dez n3 = AkıllıSayı(99)
satıryaz(n3.yazıya)

Çalıştı, değil mi? Şimdi AkıllıSayı türümüzü tanımladığımız satırdaki /* ve */ imgelerini silip tekrar dene! Derleyici bir hata verecek ve şöyle diyecek:

Error: class AkıllıSayı needs to be abstract. Missing implementation for member of trait BaskınYazıyaYöntemiyle: def yazıya: Yazı = ???

Bu sorunu çözmek için de toString tanımını silelim ve altındaki yazıya tanımını etkinleştirelim:

durum sınıf AkıllıSayı(dez sayı: Sayı) yayar BaskınYazıyaYöntemiyle {
    baskın tanım yazıya = "Ben bir sayıyım. Değerim de " + sayı + "'dur."
}

dez n3 = AkıllıSayı(99)
satıryaz(n3)
// 'yazıya' yöntemini açık açık söylememiz bile gerekmiyor!
// Leb demeden leblebiyi anlıyor akıllı Scala derleyicisi 8-)
// ve gizlice 'yazıya' yöntemini çağırıyor ki 'satıryaz' doğru çalışsın.

Geldik yayar anahtar sözcüğüne. Bu nesne odaklı yazılımın ana konularından biri. İki bölüm sonra Ağaç ve Yaprak türleri oluştururken detaylıca inceleyeceğiz. Ama kısacası daha temel bir türün özelliklerini kullanan bir alt tür oluşturmak istersek yayar sözcüğünü kullanıyoruz. Buradaki temel tür BaskınYazıyaYöntemiyle. Onun sayesinde toString yerine Türkçesi olan yazıya yöntemini yeniden tanımlayınca herşey güzelce çalışıyor. Tam anlaması biraz zor olabilir. Hiç dert etme. Sırası gelince hemen anlayıvereceksin.

Bu çok daha okunaklı, değil mi? Scala ve Kojo'yla kocaman bir vektör cebiri (ya da tam Türkçesiyle yöney ölçülümü) yaratmak işten bile değil!

Yukarıda basit bir örneğini gördüğümüz durum anahtar sözcüğüyle sınıf tasarımı biraz daha kolaylaşıyor. Bu durum sözcüğü yazılımda önemli bir kavrama işaret ediyor. İleride göreceğimiz eşle anahtar sözcüğüyle beraber de kullanılıyor. İngilizce'de durum, olay ve olgu gibi anlamlara gelir. Kullandıkça daha iyi anlayacağız.

durum sınıf Nokta(x: Sayı, y: Sayı) {
    tanım +(yeniNokta: Nokta) = Nokta(x + yeniNokta.x, y + yeniNokta.y)
    tanım -(yeniNokta: Nokta) = Nokta(x - yeniNokta.x, y - yeniNokta.y)
}
dez n1 = Nokta(3, 4)
dez n2 = Nokta(7, 2)
dez n3 = Nokta(-2, 2)
satıryaz(n1+n2-n3)

Burada durum sayesinde yeni anahtar sözcüğüne gerek kalmadı. n1, n2 ve n3 nesnelerini yaratmak için sadece Nokta(...) yetti. Bu Scala derleyicisinin Java derleyicisinden daha becerikli olduğunu ortaya koyan örneklerden biri.

İki husus daha var üzerinde durmamızda fayda olan. 1. + ve - işlevleri tanımlarken tanım anahtar sözcüğünü kullandık ama eşittir işaretinden sonra kıvrık parantezlere gerek duymadık. Bunun nedeni basit. Sağ tarafta sadece bir tane deyiş ya da sadece bir tane komut varsa kıvrık parantezlere gerek kalmıyor. Bu sadece durum sınıf içinde değil bütün tanım işlevleri için geçerli. 2. + ve - yöntemlerinin tanımında çıktı türü belirtmeye de gerek duymadık. Bu Scala derleyicisinin çok faydalı becerilerinden biridir: tanımda kullanılan deyiş ve komutlara bakarak çıktı türünü kendisi belirleyiveriyor! Buna İngilizce'de özel bir ad takılmıştır: "type inference" yani tür çıkarımı.

Herşey bir nesne demiştik. Bunu anımsamakta fayda var. Peki o zaman neden Java gibi nesneye yönelik dillerdeki biçimde yazmadık:

durum sınıf Nokta(x: Sayı, y: Sayı) {
    tanım +(yeniNokta: Nokta) = Nokta(x + yeniNokta.x, y + yeniNokta.y)
    tanım -(yeniNokta: Nokta) = Nokta(x - yeniNokta.x, y - yeniNokta.y)
}
dez n1 = Nokta(3, 4)
dez n2 = Nokta(7, 2)
dez n3 = Nokta(-2, 2)
satıryaz(n1.+(n2.-(n3))) // n1.+(n2).-(n3) ?

Scala'nın sözdizimini kolaylaştıran ve kodun daha okunur hale gelmesini sağlayan becerilerinden biri sayesinde oldu. Parantezleri eklememize gerek kalmıyor çünkü derleyici onların nerede olması gerektiğini biliyor. Derleyicinin böyle becerikli olması bir tesadüf değil elbette. Scala DSL tasarımını desteklemeyi amaçlamış. DSL İngilizce Domain Specific Language sözcüklerinin baş harfleri. Değişik uğraş alanlarının kendine özgü terimleri var elbet. Onları Scala kullanarak tanımlayıp uzmanlarının kolaylıkla kullanmalarını sağlayabiliyor uzman Scala mühendisleri. Şimdilik bilgin olsun. İlerde örneklerini gördükçe daha anlamlı olacak bu bahsettiklerimiz.

Sayılar da herşey gibi birer nesne. Ayrıca matematik işlemleri de özel değil. Onlar da hep bir nesne türü üzerinde tanımlı birer işlev yani yöntem. Örneğin + yöntemini 1 sayısı üzerinde kullanmanın ikinci bir yolu da nesne adı ve sonra nokta koyup yöntem adını eklemek. Scala için +, -, * ve / gibi matematik işlemlerinin hepsi birer yöntem:

satıryaz(1.+(2))

Son kere anımsatalım: Scala'da herşey bir nesne ve herşeyin bir türü (ya da sınıfı) var. Bütün işlevler de bir yöntem ve hep bir nesne ve türe bağlı.

Bu arada son yazdığımız satıra dikkat. Scala derleyicisi 1. deyişini 1.0 yani kesirli bir sayı sanmadı. Yoksa, bir çift parantez daha gerekecekti ve şöyle yazacaktık:

satıryaz((1).+(2))

Tabii bu da hala çalışıyor ama böyle yazmak zorunda kalmamamız iyi birşey. Scala'nın daha yaşlı versiyonları bu kadar akıllı değillerdi sanıyorum. Hatta daha hala pek çok eski dil var ki bu konuda kafaları karışık olabiliyor. Bilmende fayda olabilir.

9Desen Eşleme: Eşle ve Durum Komutları

Desen Eşleme

Program akışını değiştirmenin bazı yollarını görmüştük. Bilhassa eğer/yoksa yapısıyla akış nasıl dallandırılır biliyorsun. Şimdi de eşle/durum yapısıyla akışı aynı anda birden fazla dala ayırmayı görelim. C ve Java gibi daha eski dillerde de vardır benzeri yapılar. Scala bu kavramı daha da geneller ve cebirsel desen eşleme yapmayı sağlar! Bunu böyle anlamak mümkün değil elbet. Desene İngilizcede 'pattern' deniyor. 'Design patterns' ve 'pattern matching' gibi terimler çok yaygındır bilgisayar mühendisliğinde. Türkçeye 'tasarım desenleri' ve 'desen eşleme' diye çevirebiliriz. Desen yerine örüntü sözcüğü de var, ama onu pek duyan ve kullanan yok. Neyse biz gelin birkaç örnekle gizemi çözüverelim. İlk önce geleneksel ve daha basit kullanışıyla başlayalım:

tanım sayıdanYazıya(s: Sayı): Birim = {
  s eşle {
    durum 1 => satıryaz("Bir")
    durum 2 => satıryaz("İki")
    durum 5 => satıryaz("Beş")
    durum _ => satıryaz("Hata")
  }
}
sayıdanYazıya(5)

Burada her durum satırında kullandığımız => iminin adı kalın ok imi olsun. Bu imden önce gelen desen ondan sonra gelen komut dizisi ya da deyişle eşleşiyor. Son örneğimizde 1 sayısı "Bir" yazısıyla eşleşiyor. Son desen olarak kullandığımız _ imi herşey demek. Yani s sayısı ne olursa olsun artık eşini buluyor. Bu yapıda kullandığımız eşle anahtar sözcüğü hemen hemen bütün işlevler gibi bir değere sahip. Onun için son örneği daha da kısa ve öz bir şekilde yazalım:

tanım sayıdanYazıya(s: Sayı): Birim = {
  satıryaz(s eşle {
    durum 1 => "Bir"
    durum 2 => "İki"
    durum 5 => "Beş"
    durum _ => "Hata"
  })
}
sayıdanYazıya(3)

İşin güzel tarafı yukarıdaki eşleşmeyi tam tersine çevirmek de mümkün (Java ve C'deyse yapamazdık bunu):

tanım yazıdanSayıya(y: Yazı): Birim = {
  satıryaz(y eşle {
    durum "Bir" => 1
    durum "İki" => 2
    durum "Beş" => 5
    durum _ => 0
    }
  )
}
yazıdanSayıya("Beş")

İngilizce'de açık açık okuyabildiğimiz bir yazıyı sayıya çevirmeye "encoding", tersine de "decoding" derler. Son örnekle encoding, ondan öncekiyle de decoding yapmış olduk. Haberin olsun. Desen eşlemeyle bir nesnenin türüne göre de farklı işlemler yapmak kolaylaşır. Hemen bir örnek görelim:

tanım nedir(a: Her): Yazı = {
    a eşle {
        durum x: Sayı  => "Bir Sayı"
        durum x: Yazı  => "Bir Yazı"
        durum x: Kesir => "Bir Kesir"
        durum _        => "Kim bilir ne türdür?"
    }
}

/*
 * Aşağıda üç örnek var. Son örnek kesir
 * olsun diye sonuna F koyduk.
 * Anımsadın mı? Float UfakKesir demek.
 */
satıryaz(nedir("text"), nedir(2), nedir(2F))

Bakın bu yöntem durum sınıf kullanarak yarattığımız türleri işlemekte o kadar faydalıdır ki, bir sonraki bölümü tamamen ona ayırdık.

10İleri Eşleme Yöntemleri: İkil Ağaç

Şimdiki örneğimizin adı ikil ağaç (binary tree). Neden böyle deniyor çok yakında anlayacağız. Ağaç diye adlandırdığımız veri yapılarının bilgisayar mühendisliğinde önemi büyük. Çünkü verileri saklamak ve hızlı bulmak için bire birler. İlk önce veri ile ne demek istiyoruz onu görelim. Pek çok tür veri olabilir elbet. Çok yaygın olan anahtar/değer çiftleri bu örneğimizde bizim işimizi görecek. Örneğin ("a" -> 30) "a" anahtarıyla saklayıp sonra da arayıp bulacağımız 30 değeri. Ağacın tasarımında iki gereksinim olacak: 1) ağacın çatallanarak dallanmasını sağlamak, 2) ağacın yapraklarıyla verileri saklamak. Unutmayalım ki arama işleminin çabuk çalışmasını istiyoruz. Bunun için de her seferinde bütün yapraklara bakmak yerine daha verimli bir düzen istiyoruz. Arama işlemini yapacak elbette bir işlevimiz olacak. Ağacı gezerken anahtara göre hangi dala gitmesi faydalı olur nasıl bilebilir bu işlev? Düşündüğünden kolay olacak. Bak gör! Çatalların özelliği iki dala ayrılmaları (onun için ikil dedik!) ve de bir anahtar tutmaları. Diyelim ki sağ daldaki veriler sözlükte hep bu anahtardan önce gelsin. O zaman daha sonra gelen bir anahtar arıyorsak sadece sol dala bakmamız yeter! O sayede arama hızlı çalışacak. Yapraklara gelince, durum daha basit. Her yaprak sadece bir anahtar ve onunla saklanan değeri tutacak (boş yere anahtar/değer çifti demedik).

/*
 *           [b]
 *          /  \
 *        [a]  c,20
 *       /  \
 *     a,30  b,10
 */

İlk görevimiz çatal ve yaprak türlerini tanımlamak. Şimdi sorun şu. Her çatal iki dala ayrılıyor ama her iki dal da ya başka bir çatal olabilir ya da bir yaprak olabilir! Yani çatalın iki çocuğu olmalı ve bu çocuklar sadece bu iki türden biri olmalılar, ya bir çatal ya da bir yaprak, ama başka da hiç bir tür olmamalılar. Yanlışlıkla bir sayı, yazı ya da dizin olursa başımız derde girer (buna type unsafe, yani tür güvensizliği derler ve eski çağlarda bilgisayar programcılarını çok terletmiştir). Scala bu zorluğu sınıf hiyerarşisi dediğimiz yöntemle çözer. İlk önce genel bir ağaç türü ya da sınıfı oluşturalım sonra da buna iki tane alt tür ekleyelim. Bakın nasıl da kolay:

sınıf Ağaç
durum sınıf Çatal(anahtar: Yazı, sol: Ağaç, sağ: Ağaç) yayar Ağaç
durum sınıf Yaprak(anahtar: Yazı, değer: Sayı) yayar Ağaç
dez ağaç1: Ağaç = Yaprak("c", 24)
satıryaz(ağaç1)

Çatal ve Yaprak için Ağaç türünün alt türü deriz. Bunu yayar özel sözcüğüyle belirliyoruz. Ağaç da üst tür olarak bilinecek bundan sonra. Çatal ve Yaprak alt türlerine ataları olan Ağaç üst türünün bütün özellikleri miras kalır, hem de Ağaç daha ölmeden! :-) Bakın çok ilginç birşey daha gördük hemen: Bir değerin türü Ağaç olsun dedik ama onun gerçek değeri bir Yaprak. Bu nasıl oluyor? Yaprak Ağaç'ın uzantısıydı ya. O sayede Ağaç deyince genel olarak ya Çatal ya da Yaprak demiş oluyoruz. Bu sayede Çatal'ın tanımındaki sol ve sağ değerlerinin türü neden Ağaç oldu anladık değil mi? Ama tersini yapamayız ona göre. Neden? Çünkü Yaprak dedik mi artık yeterince özelleşmiş oluyor ve anahtar ve değeri olarak bir sayı gerekiyor. Ne Ağaç ne de Çatal'da bir sayı yok, değil mi?

Şimdi en başta bahsettiğimiz arama işlevine geldi sıra. Desen eşleme değil mi bu kısmın adı? Bakın bu işlev ağacımızı alacak ve özyineleme yöntemiyle verimli arama işlemini gerçekleştirecek. Yani bir çatala geldiğinde kendi kendini sağ ya da sol küçük ağaç ile tekrar çağıracak. Ama eğer bir yaprak görürse elbette yineleme duracak. Desen nerede o zaman? Küçük ağaç bir çatal mı yoksa yaprak mı onu belirleyecek desenlerimiz (ya da örüntülerimiz). Çok lafa gerek yok. Yazılımcık yalın ve kendi kendini anlatıveriyor:

sınıf Ağaç
durum sınıf Çatal(anahtar: Yazı, sol: Ağaç, sağ: Ağaç) yayar Ağaç
durum sınıf Yaprak(anahtar: Yazı, değer: Sayı) yayar Ağaç

tanım bul(ağaç: Ağaç, ne: Yazı): Sayı = {
  ağaç eşle {
    durum Yaprak(a, değer)   => eğer (a == ne) değer yoksa 0
    durum Çatal(a, sol, sağ) => bul((eğer (a >= ne) sol yoksa sağ), ne)
  }
}

// Bir örnek gerek. İkil ağacımız şöyle olsun:
dez ağaç = Çatal("b",
  Çatal("a", Yaprak("a", 30), Yaprak("b", 10)),
  Yaprak("c", 20))
/*
 *           [b]
 *          /  \
 *        [a]  c,20
 *       /  \
 *     a,30  b,10
 */

// 'durum sınıf' sayesinde Çatal ve Yaprak nesnelerini kolayca tanımladık.
// Haydi şimdi yapalım bir iki arama:
satıryaz(bul(ağaç, "a"))  // 30
satıryaz(bul(ağaç, "c"))  // 20

Bu kadarla kalmaz elbet. Ağaca yeni anahtar/değer çiftleri eklemek için de bir işlev iyi olur. Ha, bir de yaprağı koparmak gerekebilir. Bütün bu işlevleri yeni bir İkilAğaç sınıfı tanımlayıp içine koymaya ne dersin? Onu sana bırakıyorum. Biraz düşün, Kojo'da birşeyler yazıp çiziştir bakalım. Çok daha iyi öğreneceksin o sayede. Kolay gelsin!

Bu arada desenler/örüntüler yukarda gördüğümüz gibi değişken olmak zorunda değil. Yalın bir değer de kullanabiliriz örüntü olarak. Bir önceki bölümde de görmüştük hani sayıdan yazıya ve tersini yaparken. Bir örnek daha verelim yine de. Diyelim ki (c -> 20) çiftinin bulunmasını istemiyoruz. Nedense. Bakın nasıl kolay:

sınıf Ağaç
durum sınıf Çatal(anahtar: Yazı, sol: Ağaç, sağ: Ağaç) yayar Ağaç
durum sınıf Yaprak(anahtar: Yazı, değer: Sayı) yayar Ağaç

dez ağaç = Çatal("b",
  Çatal("a", Yaprak("a", 30), Yaprak("b", 10)),
  Yaprak("c", 20))

tanım bul2(ağaç: Ağaç, ne: Yazı): Sayı = {
  ağaç eşle {
    durum Çatal(a, sol, sağ) => bul2(eğer (a >= ne) sol yoksa sağ, ne)
    durum Yaprak("c", _)     => 0
    durum Yaprak(a, değer)   => eğer (a == ne) değer yoksa 0
  }
}

satıryaz(bul2(ağaç, "c"))  // 0
satıryaz(bul2(ağaç, "a"))  // 30

Burada yine _ imini joker gibi kullanarak bütün değerlerle eşleşmesini sağladık. Ayrıca bilelim ki bu eşleştirme işlemi yukarıdan aşağıya sırayla gidiyor. İlk eşleşme ile iş bitiyor. Bariz tabii ama yine de benden söylemesi.

Burada örneğini gördüğümüz sınıf hiyerarşisi ve alt türlerin üst türü uzatması, OOP, yani nesneye yönelik yazılımdaki en temel kavramlardan. Desen eşleme yöntemi sayesinde alt türleri birbirinden ayırıp gereğini yapabiliyoruz. Ne dahice, değil mi?!

11Dingin Türleme ve Tür Çıkarımı

Scala dingin türleme (static typing) yapan bir dil. Bu ne demek? Bütün değişkenlerin, değişmezlerin ve işlevlerin türü, program çalışmadan önce, yani derleme sırasında belirleniyor. Bütün diller böyle değil. Örneğin pek çok yazılımcının favorilerinden olan Python ve Ruby, devingen türlemeli diller (dynamically typed languages). Dingin türleme sayesinde pek çok yazım ve mantık hatası programı çalıştırmadan önce yani derleme sırasında yakalanır. Örneğin, yanlışlıkla bir işleve uygun olmayan tür bir nesneyi girdi olarak sokarsak, derleyici hemen yakalar ve bizden hatayı düzeltmemizi ister. Kısaca, her değerin türünün önceden belli olması hem faydalı hem de gerekli. Ama, buna rağmen yazılımcık örneklerimizde tür tanımlarının pek az olduğunu farketmiş olabilirsin. Bu nasıl oluyor? Çünkü Scala tür çıkarımında çok becerikli. Tür çıkarımı (type inference) bir değerin türünü kullanıldığı yere bakarak belirlemek demek.

Örneğin, dez x = 3 yazalım. Derleyici elbette 3'ün yalın bir değer olduğunu ve ayrıca ne tür bir sayı olduğunu biliyor. 3 (kesirsiz bir) Sayı. Onun için derleyici x değişmezinin türünün de Sayı olması gerektiği çıkarımında bulunuyor ve elbette yanılmıyor. Az da olsa bir kaç durum var ki derleyici tür çıkarımını beceremeyebiliyor. İşte o zaman, kafam karıştı der ve bir hata verir. Hatanın türü "type ambiguity" gibi birşeylerdir, yani tür belirsizliği. O durumda gerekli tür bilgisini yazılımcığa ekleyiverirsin.

dez x = 3          // derleyici: x bir Sayı
dez y = 3.0        // derleyici: y bir Kesir
dez z = "üç"       // derleyici: z bir Yazı
tanım iki(a: Sayı) = a * 2   // çıktının türünü derleyici çıkarır: Sayı
satıryaz(iki(x))
// Şunu deneyip hatayı gör: iki(y)

Genelde işlevlerin girdilerinin türünü tanımlarız. Ama çıktının türünü belirtmek gerekmez. Çünkü derleyici işlevin tanımından çıktının türünün çıkarımını yapıverir. Bunun istisnası özyineleyen, yani kendi kendini çağıran işlevler. Pek çok örneğini gördük elbet. Onların çıktısının türünü hep belirttik.

Tür çıkarımı sayesinde programların doğru çalışması için yazmamız gereken sözcük ve harf sayısı epey azalır. Bu da elbette çok işimize yarar. Ayrıca yazılım daha okunur hale gelir ve anlaşılması da kolaylaşır. Bu becerisi sayesinde Scala da Python ve Ruby gibi diller gibi sade ve yalın gelir göze.

12İşlevler de Birer Nesnedir

İşlevler de Birer Nesnedir

Scala dilinde herşey bir nesnedir demiştik. İşlevler de aynen öyle! Bir işlevi başka işlevlere girdi yapabiliriz. Bir işlevin çıktısı da bir işlev olabilir. Ayrıca değişkenlerin ya da değişmezlerin değeri de bir işlevin kendisi olabilir. Bunların örneklerini biraz sonra göreceğiz. Bu özellikler Scala dilinin çok faydalı bir becerisidir. Karşımıza sık sık çıkan bazı zorlukları çok kısa ve güzel bir şekilde çözmemizi sağlarlar. Bunlar arasında program akışını yönlendirme teknikleri de var. Örneğin, Scala dilinde yazılmış eski adı "Actors" yeni adıyla "Akka" birimi (İngilizce'de module ya da library denen ve başka yazılımlar tarafından kullanılan bir yazılım bütününe birim denir) işlevleri nesne gibi kullanarak eşzamanlı işlevlerin yazılımlarını kolaylaştırır. Ama biz dizinlerin kullanılmasıyla başlayalım. Bakın göreceksiniz işlevlerin nesne olarak kullanılmasına güzel bir giriş olacak.

Nesneleri bir dizi olarak ele almak çok doğal ve faydalı, değil mi? Ne tür örnekler geliyor senin aklına? Her sözcük, örneğin, bir dizi harften oluşur. Gün içerisinde yaptığımız şeyleri bir dizi eylem olarak düşünebiliriz. Scala dili Dizin adını verdiğimiz bir tür tanımlamıştır. Bunu daha önce pek görmedik. Ama aslında çok basit bir kavram. Dizin türü bir dizi nesneyi ele almayı ve işlemeyi çok kolaylaştırır ve hemen hemen her yazılımcık ve daha büyük yazılımlarda sık sık kullanılır. Dizin içindeki nesnelerin belli bir sırası vardır. Dizin türünün sunduğu pek çok yöntem ve komut sayesinde Dizin tanımlamak ve işlemek kolaylaşır. Daha önce Nokta sınıfıyla gördüğümüz gibi Dizin oluşturmanın bir yolu Dizin sınıfının yapıcı yöntemini (constructor) kullanmak. Bu yapıcı yönteme bir ya da daha çok girdi sokarız ve yapıcı o girdilerin oluşturduğu bir Dizin yapıverir. İlk örneğimizle hemen başlayalım. Bir dizi sayı oluşturalım. Bu dizinin türü Dizin[Sayı] olacak. Buradaki köşeli parantezler dizinin içindeki elemanların türünü belirliyor, yani Sayı. Her eleman bir Sayı olduğu için Scala tür çıkarımı yaparak dzn değişmezinin türünü Dizin[Sayı] olarak belirler. Yani bizim bunu açık açık yazmak için zahmet etmemiz gerekmez.

dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
satıryaz(dzn)

Tamam, şimdi elimizde bir dizin var. Dizinleri kullanmanın üç temel yöntemi var: başı, kuyruğu ve ::. Her dizinin bir başı var. başı yöntemi bize ilk elemanı verir. kuyruğu yöntemi, dizinin başı hariç diğer elemanlarından oluşan kısmını verir. Ve son olarak da çift iki nokta üstüste yani :: bir dizinin başına yeni bir eleman, yani yeni bir baş ekler. Bu baş ve kuyruk deyişi sana yılanları anımsattıysa haklısın. Dizinler yılanlara benziyor: hep başından tutmakta fayda var! Şaka bir yana Dizin türü çok gelişmiştir ve daha pek çok kullanışlı yöntemi vardır. Dizinlerin Kullanılışı bölümünde daha pek çok Dizin yöntemi göreceğiz.

dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
// 'başı' ilk yani en soldaki elemanı verir. dzn örneğinde bu '1' olacak:
satıryaz(dzn.başı)
// 'kuyruğu' başı yani ilk elemanı atlar ve dizinin gerisini verir:
satıryaz(dzn.kuyruğu)
// '::' soldaki elemanı sağdaki dizinin başına ekleyerek oluşan yeni dizini verir:
satıryaz(23 :: dzn)

Burada önemli bir gözlem yapalım. Bu yöntemler dzn dizisini değiştirmez! Hep yeni bir Dizin üretirler. Bunun için Dizin türüne değişmez (immutable) bir veri yapısı da denir.

Bu üç temel yöntemle aklına gelen her dizini tanımlayabilir ve dizinlerle yapılabilecek ne varsa yapabilirsin. Bir örnek olarak gel bizim örnek dizinimizin içindeki tek sayıları bulalım:

dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
tanım tek(girdi: Dizin[Sayı]): Dizin[Sayı] = {
    eğer (girdi == Boş) Boş
    yoksa eğer (girdi.başı % 2 == 1) girdi.başı :: tek(girdi.kuyruğu)
    yoksa tek(girdi.kuyruğu)
}
satıryaz(tek(dzn))

Farkettin mi? Bu işlev dizinin başına bakar ve duruma göre kuyrukla özyineler, yani kendi kendini çağırır. Bunu yaparken kuyruğu yöntemini kullanır ve bu sayede dizinin elemanlarını teker teker ele alır. Boş özel bir değer ve içi boş olan diziyi belirtiyor. Unutmadan, içi boş olan tek dizi var aynı yegane boş küme gibi. Boş yerine şöyle de yazabilirdik: Dizin[Sayı](). Neyse ki Boş tanımlanmış. Daha kısa ve anlaşılır oldu değil mi? Dizinin sonuna gelince özyineleme son bulur ve tek sayılardan oluşan yeni bir Dizin çıkar ortaya.

Ne kadar yalın bir çözüm, değil mi? Tek yerine çift sayıları bulmak da artık çok kolay:

dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
tanım çift(girdi: Dizin[Sayı]): Dizin[Sayı] = {
    eğer (girdi == Boş) Boş
    yoksa eğer (girdi.başı % 2 == 0) girdi.başı :: çift(girdi.kuyruğu)
    yoksa çift(girdi.kuyruğu)
}
satıryaz(çift(dzn))

Ama, dur, burada epey bir tekrarlama oldu. Görüyor musun? tek ve çift işlevleri neredeyse aynı. İkisi arasındaki farklılıkları bulalım: Elbette işlevlerin adı farklı. Tek ve çift. Onun dışındaki tek fark eleme için kullandığımız koşul. == iminin sağında 0 ya da 1 var. Bu farkı işlevin içinden çekip çıkarsak ortaya daha genel ve daha sade bir çözüm çıkıverecek! Onun için tek mi, çift mi sorusunu bir girdi olarak düşünelim. Bu eleme koşulunu bir işlev olarak tanımlamak çok basit:

tanım tekMi(s: Sayı): İkil = s % 2 == 1
// % imini anımsadın umarım. Burada sayıyı ikiye bölüp kalanı buluyor.
satıryaz(tekMi(13))
satıryaz(tekMi(24))

Bu arada tekMi adlı işlevin çıktı türünü İkil diye açık açık belirtmeye gerek yok aslında. Sadece Türkçesini görmen ve anımsaman için yazdım. Neyse, biz bu işlevi şimdi nesne olarak nasıl kullanacağız onu görelim. Bunun için yeni bir işlev tanımlayacağız. Adı elemek sözcüğünün kökü olsun. Elemanları eleyerek bize istediğimiz elemanları versin. Ama neye göre eleyecek? Tabii ki yukarıdaki tekMi işlevi gibi bir işlevi kullanarak! Nasıl kullanacak? Yeni bir girdi olarak! İşte başta da bahsettiğimiz konuya geldik. Bir işlev başka bir işleve girdi olacak şimdi.

dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
tanım tekMi(s: Sayı): İkil = s % 2 == 1
tanım ele(girdi: Dizin[Sayı], koşul: (Sayı) => İkil): Dizin[Sayı] = {
    eğer (girdi == Boş) Boş
    yoksa eğer (koşul(girdi.başı)) girdi.başı :: ele(girdi.kuyruğu, koşul)
    yoksa ele(girdi.kuyruğu, koşul)
}
satıryaz(ele(dzn, tekMi))

ele işlevinin ilk girdisinin adı girdi ve bu bizim sayı dizimiz. İkinci argüman ise eleme koşulumuz, yani yeni tanımladığımız işlev. Türünü nasıl yazdık farkedelim: koşulun türü bir sayıyı girdi olarak alıp bir İkil veren, yani koşul sağlanıyorsa doğru yoksa da yanlış diyen bir işlev. ele adlı yöntemimiz her işlevi girdi olarak kabul etmez. Sadece Sayı alıp İkil veren işlev türü ile çalışır. ele yönteminin içinde de koşul adlı değeri aynı diğer işlevler gibi kullandık. Bu ele adlı işlevi daha önceki tek ve çift işlevleriyle karşılaştırmanı öneririm. Genellemenin nasıl yapıldığını iyice özümsemende fayda var. Bu genelleme günlük hayattaki istisnası bol onun için de bizi sık sık yanıltan genellemelere benzemez. Zararı yok sayılır (ikinci bir girdi girmemiz gerekiyor, yani zahmeti biraz arttı) ama kazancı çok. Birazdan bir kaç örnek daha görünce daha iyi anlayacaksın. Bu arada, eğer çok istersek bu zararı iyice yok edebiliriz. Örneğin, bu işlevi sık sık tek sayıları bulmak için kullanacaksak, şöyle yapalım ki iyice kolaylaşsın kullanımı:

tanım tekMi(s: Sayı): İkil = s % 2 == 1
tanım ele(girdi: Dizin[Sayı], koşul: (Sayı) => İkil = tekMi): Dizin[Sayı] = {
    eğer (girdi == Boş) Boş
    yoksa eğer (koşul(girdi.başı)) girdi.başı :: ele(girdi.kuyruğu, koşul)
    yoksa ele(girdi.kuyruğu, koşul)
}
dez ilkOnSayı = Aralık(1, 11).dizine
satıryaz(ele(ilkOnSayı))

Bak bu sayede, eğer işlev girdisi vermezsek, ele adlı işlev tekMi koşulunu kullanacak. Yani varsayılan koşul tekMi ile ifade ettiğimiz tek sayı olmak olacak. Aynı tek adını verdiğimiz işlev gibi çalıştı, değil mi? Ama, ele adlı işlevimiz hala daha genel. İstersek ikinci argüman olarak başka bir koşul girer, başka tür bir eleme yaparız.

Daha önce görmediğimiz ama çok faydalı olan bu Aralık türünün İngilizce adı 'Range'. Bir Aralık yazmak yerine kısa bir yolu var: (1 |- 11).dizine. İngilizce olarak '1 until 11' yazılır. Ya da onun yerine 1 |-| 10, İngilizce'si: '1 to 10' da yazabilirdik. Bunların daha genel hali de var: a |- b adım c ve 'a until b by c' veya a |-| b adım c ve 'a to b by c' yani a sayısından b sayısına kadar c adımlarıyla sayıyoruz ama |- ya da 'until' olursa b sayısından hemen önce duruyoruz. Örneğin:

satıryaz(3 |- 22 adım 3)
// Uzun ve işlev haliyle:
satıryaz(Aralık(3, 22, 3))
// Geri geri de gidebiliriz:
satıryaz(30 |-| -35 adım -5)
satıryaz(Aralık(30, -36, -5))
satıryaz(Aralık.kapalı(30, -35, -5))
// Aralığın sonuna dizine yöntemini ekle bakalım ne olacak. İngilizcesi toList:
satıryaz((1 |- 11).dizine)

Çift sayıları bulmak için de benzer bir yöntem kullanabiliriz elbet. Yani çiftMi diye yeni bir işlev tanımlar ve sonra kullanırız. Ama adsız (anonymous) bir işlev kullanarak çok daha kısaca ifade edebiliriz dileğimizi. Ne demek adsız? Yani tanım özel sözcüğünü kullanmadan ve işlevin adını vermeden bir işlev tanımlayacağız. Bu işlev basitçe girdiyi alacak ve onu değerlendirecek. Bu özel yöntemde => imini kullanıyoruz. Solunda girdiler, sağında da işlevin eylemleri gelecek.

dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
tanım ele(girdi: Dizin[Sayı], koşul: (Sayı) => İkil): Dizin[Sayı] = {
    eğer (girdi == Boş) Boş
    yoksa eğer (koşul(girdi.başı)) girdi.başı :: ele(girdi.kuyruğu, koşul)
    yoksa ele(girdi.kuyruğu, koşul)
}
satıryaz(ele(dzn, (v: Sayı) => v % 2 == 0))

ele komutunu böyle çağırarak çift sayıları buluverdik. Yeni bir çiftMi işlevi tanımlamamıza gerek kalmadı. Adsız işlevler hep böyle kullanılır ve işimizi çok kolaylaştırırlar. O kadar faydalılar ki özel bir adları ve matematik ve bilgisayar biliminde özel bir yerleri bile var. 'lambda kalkülüsü' (lambda calculus) diye bilinir. Bilgisayar bilimi okumak istersen ilerde görür ve daha iyi öğrenirsin.

Genelleyici Programlamanın Tadına Bakalım

Şimdi diyelim ki yeni bir eleme koşulu tanımlamak istiyorsun, çünkü tam sayılarla yetinmek olmaz, kesirli sayıları da herhangi bir koşula göre eleyebilmek istiyorsun. Yukarıda tanımladığımız kısacık ele işlevini kopyalar ve basitçe her gördüğün 'Sayı' sözcüğünü 'Kesir' sözcüğüyle değiştirirsin olur biter. Sonra da adını eleKesir gibi birşey koyarsın. Tabii Kesir de yetmez. Başka her hangi bir tür için eleHerneyse dersin ve bu sayede birbirine çok benzeyen, daha doğrusu neredeyse apaynı bir sürü işlevin olur: ele, eleKesir, eleYazı, vb.. Ne güzel, değil mi? Hiç de güzel değil! Çok tekrar oldu. Nerede çokluk orada çok ayıp...

Scala'yla yazarken bu kadar tekrara, bu zahmete girmeye hiç gerek yok. İşi iyi bilenlerin yaptığı gibi bir 'tür değişkeni' (type variable ya da generic type diye de bilinir) kullanarak genel bir çözüm sayesinde işler kolaylaşıverir. Tür değişkenleri diğer dez değişmezlerine benzer. Gerçek değer yerine değişkenin adını kullanırız her yerde. Sonra Scala derleyicisi gerekli yerleştirmeleri yapıverir.

Aşağıda T harfini kullanacağız Tür anlamında. Ama başka her hangi bir harf ya da sözcük de olur elbette. Nerede Sayı varsa onun yerine T gelecek. Bir de işlevin adının hemen arkasında [T] gelecek. Bu da yeni işlevimizin genelleyici bir işlev olduğunu belirtiyor. Peki o halde yeni halini görelim:

tanım ele[T](girdi: Dizin[T], koşul: (T) => İkil): Dizin[T] = {
    eğer (girdi == Boş) Boş
    yoksa eğer (koşul(girdi.başı)) girdi.başı :: ele(girdi.kuyruğu, koşul)
    yoksa ele(girdi.kuyruğu, koşul)
}
dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
satıryaz(ele(dzn, (v: Sayı) => v % 2 == 0))

// Aynı eskisi gibi çalıştı! Ama işin iyi tarafı çok daha genel oldu bu haliyle.
// Bak şimdi kesirli sayılar arasından sadece beşten büyük olanları seçmek de çok kolay:
dez kesirler = Dizin(
    1.5, 7.4, 2.3,
    8.1, 5.6, 6.2,
    3.5, 9.2, 14.6,
    12.91, 4.23, 10.04)
satıryaz(ele(kesirler, (v: Kesir) => v > 5))

// Ya da bir cümle içindeki uzunca sözcükleri seçmek için de kullanabiliriz
// aynı genellenmiş ele adlı işlevimizi:
dez dizinler = Dizin(
    "Bugün", "sizi",
    "gördüğüme", "çok",
    "memnun", "oldum",
    "Nasılsınız")
satıryaz(ele(dizinler, (v: Yazı) => v.boyu > 4))

Yazı türünün boyu adlı yöntemi yazıda kaç harf olduğunu gösterir. Dizinlerle ilgili bölümde de göreceğimiz gibi Dizin türünün ele adlı bir yöntemi var. Onu kullanarak da benzer eleme işlemlerini yapabiliriz:

dez dizinler = Dizin("Bugün", "sizi", "gördüğüme", "çok", "memnun", "oldum", "Nasılsınız")
satıryaz(dizinler.ele((v: Yazı) => v.boyu < 5))

Bu genelleyici işlevlere daha sonra yine bakacağız ve başka özelliklerini keşfedeceğiz. Ama şimdilik konumuza dönelim ve işlevleri nesne olarak kullanmaya bakalım.

'İşlevler De Birer Nesnedir' Hakkında Başka Birkaç Şey Daha

Biraz önce örneklerini gördüğümüz yönteme 'kapsamalar' (comprehensions) deniyor. Yani, dizin gibi bir sürü elemandan oluşan bir kümenin bütün elemanlarına bir işlevi uygulayıvermek. Kapsamaların ne kadar becerikli olduklarını ve onlar sayesinde kısacık ama çok iş beceren yazılımlar yapabildiğimizi bir kaç örnek daha görerek pekiştireceğiz şimdi.

Yazılımcıklar yazdıkça, bazı çok basit ve çoğunlukla adı olmayan işlevleri başka işlevlere girdi yapmak isteyeceksin. O durumlarda bazı kısa yollar bilmende fayda olacak. Daha önce de üzerinde durduğumuz gibi tür çıkarımı bize fayda sağlıyor. Bakın bir önceki dizinler.ele örneğini tür çıkarımı sayesinde sadeleştirelim. Derleyici adsız işlevin girdisinin Yazı türünde olması gerektiğini biliyor (nasıl biliyor? Adı dizinler olan dez değerinin türünden!). O sayede parantezleri ve tür bilgisini yazmaktan kurtuluyoruz.

Bu adsız işlev gibi iki ve ya bir girdili işlevler çok yaygın olduğu için, Scala dili bize bir kısa yol daha sunuyor. (x, y) => x + y adsız işlevini şöyle yazabiliyoruz: _ + _. Benzer şekilde s => s.yöntem yerine _.yöntem da yazabiliriz. _ imi girdi yerine kullanılıyor yani. O sayede adsız işlevler içinde isimler uydurmak ve kullanmaktan kurtuluyoruz. Bu sayede, son örneğin en kısa halini görelim:

dez dizinler = Dizin("Bugün", "sizi", "gördüğüme", "çok", "memnun", "oldum", "Nasılsınız")
satıryaz(dizinler.ele(v => v.boyu > 3))
satıryaz(dizinler.ele(_.boyu > 3))

Başkalarının yazdığı kodları okumak çok faydalıdır. O durumlarda burada gördüğümüz kısa yolları görünce artık şaşırmazsın. Ama bazı durumlarda uzun ve açık hallerini kullanmak gerekir. Onları da sonra göreceğiz.

Bir kaç tane daha yaygın kullanımı olan örnekler görelim. Bunlar dizinleri işlemekte çok faydalı olurlar. Sen de yakında göreceksin.

düzİşle (flatMap)
dez dizinler = Dizin("Bugün", "sizi", "gördüğüme", "çok", "memnun", "oldum", "Nasılsınız")
satıryaz(dizinler.düzİşle(_.dizine))
satıryaz(dizinler.işle(_.dizine))

Flat İngilizce'de kat kat olmayan yani düz anlamında kullanılır. Bu örnekte bizim yazı dizinimizi aldık, içindeki her bir yazıyı ilk önce dizine yöntemini kullanarak birer harf dizisine çevirdik ve onların hepsini birleştirdik. Sonucunu gördün, değil mi? düzİşle yerine işle (map) yöntemini kullanarak aradaki farkı daha iyi anlarsın (ikinci satır). map İngilizce'de hem harita hem de eşlemek anlamlarına gelir. Ama matematikte ve bilgisayar biliminde yeni bir anlam kazanmıştır. Bir çerçeveden başka bir çerçeveye geçiş, birinci çerçeveyi işleyerek ikinci çerçeveye varış gibi anlamlara gelir. Ama anlatması zor. Nasıl çalıştığını görüp anlayıverelim.

sıralamak (sort)

Sort da sıraya dizmek anlamına gelir. sırayaSok (sortWith) yöntemiyle kendi girdiğimiz bir koşul ile sıralıyoruz. Sözcükleri a'dan z'ye sıralayalım, bir de ters sıraya sokalım:

dez dizinler = Dizin("Bugün", "sizi", "gördüğüme", "çok", "memnun", "oldum", "Nasılsınız")
satıryaz(dizinler.sırayaSok(_ < _))
satıryaz(dizinler.sırayaSok(_ > _))
// Farkettiysen büyük harfliler başta geliyor. Onun yerine harflerin büyük
// küçük olduğuna bakmadan sıralamak istersek şöyle yaparız:
satıryaz(dizinler.sırayaSok(_.büyükHarfe < _.büyükHarfe))

Yani hepsini büyük harfe çeviriverdik karşılaştırmadan önce. İstediğimiz koşulu girerek istediğimiz şekilde sıralama yapıverdik. Yani sıralama yöntemini baştan yazmamız gerekmedi. İşte işlevlerin nesne olmasının faydaları!

katlama (fold)

Soldan ve sağdan katlama bir dizinin elemanlarını bir araya getirmekte kullanılan çok yaygın ve faydalı yöntemlerdir. Elemanları nasıl bir araya getirmek istediğimizi iki girdi alan bir işlev girerek belirtiriz. Bu birleştirme işlemi soldan ya da sağdan başlar. Ve başlarken de yine girdiğimiz bir değer kullanır. Yani iki tane girdisi var bu katlama yöntemlerinin. Bu iki girdiyi daha kolay okunsun diye iki parantez grubuyla gireriz. Birazdan bunu tanım komuduyla işlevi tanımlarken nasıl yapıldığını göreceğiz. Yeni bir örnekle başlayalım:

dez dzn = Dizin(1, 7, 2, 8, 5, 6, 3, 9, 14, 12, 4, 10)
satıryaz(dzn.soldanKatla(0)(_ + _))

Soldan işleyeceğiz. İkinci girdimiz adsız bir işlev ve iki sayıyı topluyor: _+_. Bunun kapsama olduğunu da biliyoruz, yani dizinin içindeki bütün elemanların üstünden geçecek. İlk önce ilk girdisi olan 0 ile dizinin başındaki 1'i toplar ve 1 bulur. Sonra ona ikinci eleman olan 7'yi ekler ve böylece sonuna kadar gider.

Tekrarlardan Kurtulalım

Diyelim ki bir cümlemiz var ve içinde hangi harfleri kullandığımızı bulmak istiyoruz. Örneğin:

dez dilek = Dizin("Haydi", "gelin", "bir",
    "oyun", "oynayalım", "hep", "beraber")
satıryaz(dilek.düzİşle(_.dizine).işle(_.büyükHarfe).yinelemesiz.sırayaSok(_ < _))

düzİşle sözcüklerin bütün harflerini tek bir dizine sokuyor. Daha önce de gördüğümüz işle harfleri büyük harfe çeviriyor. yinelemesiz yöntemi sadece farklı harfleri veriyor, tekrarları vermiyor. En sonunda da a'dan z'ye sıralıyoruz.

Kendi akış yöntemimizi tanımlayalım

İşlevlere girdi olarak adlı ya da adsız başka işlevler girebildiğimiz gibi bir dizi komut da girebiliriz. Bu sayede kendi akış yöntemlerimizi yaratabiliriz. Örneğin, bir kare çizmek için kaplumbağa komutlarını yineleyebilmek iyi olur, değil mi? Daha önce gördüğümüz için yapısını kullanabiliriz:

sil
için (i <- 1 |-| 4) { ileri(100); sağ() }

Ama, aşağıdaki gibi yazabilsek çok daha iyi olmaz mı?

sil
yinele(4) { ileri(115); sağ() }

Scala dili 'eğer/yoksa' ve 'yineleDoğruKaldıkça' gibi yapılar sunar ama yinele yapısı Kojo tarafından tanımlanmıştır. Bunu sen de Scala ile yapabilirsin. Şöyle başlayalım:

tanım yinele2(ys: Sayı, dk: => Her): Birim = {
    için (i <- 1 |-| ys) dk
}
sil
yinele2(4, { ileri(130); sağ() })

ys yineleme sayısının kısaltması. dk de dizi komut anlamında. Şu ana kadar işlevlere girdiğimiz bütün girdiler değer olarak yollandı (pass by value denir İngilizce). Yani işlev çağrılmadan önce değer hesaplanır ve işleve yollanır. yinele2 tanımında kullandığımız kalın ok yani => imi durumu değiştiriyor. dk yani dizi komut olduğu gibi (call by name denir) yollanıyor yinele2 işlevine. Yani dizi komutların değerlendirilmesi geciktirilir. Ancak işlevin işleme sırası geldiğinde işlevin içine girilince dizi komut çalıştırılır. Bu yinele2 işlevimizde için döngüsü ys sayısı kadar çalışacak ve her seferinde dk yani girilen dizi komut çalıştırılacak.

Hiç fena değil. Neredeyse istediğimiz hale geldi. Tek sorun 4 sayısından sonra gelen virgül. Scala istersek her girdiyi kendi parantezi içinde girmemizi destekler. Buna 'curried işlev' de denir. Haskell Curry adında bir matematikçi buna esin kaynağı olduğu için. Bakın şöyle değiştirelim tanımı. Bu sayede tam istediğimiz gibi yazabiliriz:

tanım yinele3(ys: Sayı)(dk: => Her): Birim = {
    için (i <- 1 |-| ys) dk
}
sil
yinele3(4) { ileri(160); sağ() }

Biraz önce soldanKatla örneğinde de bu tekniği kullanmıştık. Daha okunur hale geldi değil mi?

Yazılıma hobi ya da işin olarak devam edersen işlevlerin nesne gibi kullanılabilmesinin daha pek çok yazılım problemini çözmede faydalı olduğunu göreceksin. Başka yolları da var elbet ama buradaki örneklerde de gördüğümüz gibi bu teknikle kodumuz epey kısa ve okunur hale geldi. Daha kapsamlı ve tasarlaması, yazması ve idare etmesi esaslı bir iş olan bazı örnekleri de şimdiden duymuş olman için kısa bir liste vereyim. Merak ettikçe, yeri geldikçe incelersin. Kusura bakma bunlar önce İngilizce olsun: (1) passing callback functions in event driven IO, (2) passing tasks to Akka Actors in concurrent processing environments, (3) in scheduling work loads. Yani, (1) olgu güdümlü girdi/çıktı idaresinde geriçağırım yapan işlevleri dağıtmak (2) eşzamanlı işlemler olan ortamlarda, yani aynı anda birden fazla işlemin paralel yani yanyana çalışması durumunda bağımsız aktörlere yeni işlevler verilmesi, (3) uzun işlerin sıralamasını düzenlerken işlevleri dağıtmak. Bilgisayar bilimi ve mühendisliği artık o kadar gerekli bir hale geldi ki, bu temeli iyi bilmek ilerde çok helal kazanç kazanmana faydalı olacaktır. Tabii eğer gönlünü bu işe verirsen.

Bir Tür Girdiyi Yinelemek

Bazen işlevimize kaç tane girdi gireceğini bilemeyiz. Daha doğrusu kaç tane girilirse girilsin çalışmasını isteriz. Örneğin bu kılavuzda çok kullandığımız satıryaz komutu masaüstünde öyledir. Bir, iki, üç... ne sayıda girdi girersek girelim işini yapar. Bunun yolu da çok kolay:

tanım topla(s: Sayı*) = s.indirge(_ + _)
satıryaz(topla(1, 2, 3))
satıryaz(topla(4, 5, 6, 7, 8, 9, 10))
// Pek toplamaya gerek olmasa da yine de tek değerle de çalışması güzel!
satıryaz(topla(99))

Girdinin türünden sonra gelen yıldız imi, yani * sayesinde s girdisi tek bir sayı değil bir dizi sayı oluveriyor. Onun için de indirge (İngilizcesi reduce) yöntemini kullandık. Bu soldanKatla yöntemine benzer ama daha basittir. Bakın girilen bütün sayıları toplamak bu kadar kolay. Bir istisnaya da bakalım, ne olacak?

tanım topla(s: Sayı*) = s.indirge(_ + _)
satıryaz(topla()) // Bak ne oldu? Bunu onarabilir misin?

Bu yıldızlı girdiden önce yıldızsız yani normal girdiler de tanımlayabiliriz. Ama yıldızlı yani yinelenen girdi en son gelmelidir.

13Sıralamalar (Tuple)

Diyelim ki bir işlevimiz var ve birden çok çıktısı olsun istiyoruz. Ya da bir koleksiyon oluşturacağız ama her elemanı birden fazla değer tutsun istiyoruz. Sözle anlamak zor, sabır, hemen örnek vereceğiz aşağıda. Bu tür durumlarda yeni bir tür tanımlamak zor olmaz elbet. Yeni bir sınıf nasıl oluşturulur gördük. Ama biraz zahmetli elbette. Epey kod yazmak gerekebiliyor, durum sınıf bile olsa. Sadece kod yazmak değil esas zorluk anlamlı isimler bulmak ve onları uzun uzun kullanmak aslında. Gerçekten gerekmedikçe yeni adlar üretmek zorunda kalmasak çok iyi olur, değil mi? Uzun lafın kısası, Scala dili, adsız değerleri gerektiği anda ve gerektiği yerde sıralamamıza izin vererek bu sorunu güzelce çözer. Sıralama dedik ya, İngilizce'si 'tuple' parantez içinde virgülle ayrılan bir ya da daha çok değerin bir araya gelmesinden ibaret. İşin güzel tarafı değerlerin türleri aynı olmak zorunda değil. Örneğin:

satıryaz((3, 'c'))
satıryaz((3.14, "pi sayısının yaklaşık değeri, 22/7 olarak da bilinir"))
satıryaz((22/7, 22/7.0, 3.14, "pi"))

Sıralamalar da birer nesnedir desem artık şaşırmazsın değil mi? Onun için nokta koyup arkasından içinden istediğimiz değere ulaşabiliriz. Ama bir sorun var. Bu iç değerlerin bir adı yok! Sorun değil. Sıralarını biliyoruz. Birinci elemanı okumak için ._1 dememiz yeter. İkinci için ._2 varsa üçüncü için ._3 vb... Hemen deneyelim.

satıryaz((3, 'c')._1)
satıryaz((3, 'c')._2)

Böyle birşey yapmak gereksiz geldi mi size de? Tabii genelde elimizde dez değişmezleri olur. Örneğin:

dez sıralıDörtlü = ("En sevdiğim sayı", 5, "karesi", 25)
satıryaz(sıralıDörtlü._1)
satıryaz(sıralıDörtlü._2)
satıryaz(sıralıDörtlü._3)
satıryaz(sıralıDörtlü._4)
dez sıralıDörtlü = ("En sevdiğim sayı", 5, "karesi", 25)
satıryaz(sıralıDörtlü._5)

Biraz fazla gittik! Hata verdi. "error: value _5 is not a member of (String, Int, String, Int)" yani 5. elemanı yok ki sıralamanın diyor bizim akıllı derleyici.

Bunları bilmekte fayda var ama esas sıralamaların içindeki değerleri toptan çözümlemeyle (tuple deconstruction) okumak çok daha yaygın ve faydalıdır. Örneğin:

dez (i, c) = (3, 'a')
satıryaz(i)
satıryaz(c)

Birazdan bir yazılımcık yazacağız ve onun içinde bu çözümleme yöntemi nasıl işe yarıyor göreceğiz. Diyelim ki bir yazının içinde hangi harften kaç tane var bulmak istiyoruz. İlk önce bir dizi sözcük oluşturalım bir tümceden. Bu hesaplamayı yapmak için uygulamayı düşündüğümüz yöntem şu: ilk önce harfleri büyültelim sonra da sıraya sokalım.

dez yazı = "Kojo ile oyun oynayarak Scala dilini öğrenmek ve hatta işlevsel ve nesneye yönelik yazılım becerisi edinmek harika değil mi "
dez sözcükDizini = yazı.böl(" ")
// Tek satırda epey iş var. Teker teker bak istersen
dez harfler = sözcükDizini.düzİşle(_.dizine).işle(_.büyükHarfe).sırayaSok(_ < _)
// Yani şöyle yapabilirsin:
dez g1 = sözcükDizini.düzİşle(_.dizine); satıryaz("g1", g1)
dez g2 = g1.işle(_.büyükHarfe); satıryaz("g2", g2)
satıryaz(harfler)

Ondan sonra da katlama (fold) yöntemini kullanarak arka arkaya tekrar eden harfleri sayıvereceğiz. Daha önce ne görmüştük? Katlama işlevi iki girdi alıyor: bir başlangıç değeri, bir de bir dizin, yani bizim sıraya sokulmuş harf dizimiz. Katlama ilk başlangıç değerini dizinin ilk elemanıyla bir işleme sokacak. Ne işlemi mi? Biz ne istersek o! Burada teker teker ele aldığımız harfler değişmedikçe sayısını bir artıracağımız bir sayacımız olacak. Yeni gelen harf değişik olursa yeni bir sayaç tanımlayacağız. Her sayaç tabii ki birden başlayacak. Karmaşık mı geldi biraz? Çok doğal. Görüp biraz üstünde düşününce daha anlaşılır olacak. Ne de olsa ileri yazılım tekniği bu!

Kısaca söylemek gerekirse amacımız ikili sıralamalardan oluşan bir dizin oluşturmak. Her ikilinin birinci alanında bir harf ikinci alanında da kaç tane olduğunu tutan sayacı olacak. Bu dizin harflerin sıklığını sunacak bize.

Şimdi de katlama yöntemimiz gelsin bakalım. Başladığımızda sunum boş olacak elbet. Nasıl tanımlarız istediğimiz boş dizini? Dizin[(Harf, Sayı)]() yani bir dizi (harf, sayaç) çifti. soldanKatla yöntemimiz ikinci girdi olarak ne bekler anımsadın mı? Bir işlev! Nasıl bir işlev gerekiyor biraz daha iyi tahmin edebilirsin belki şimdi. Adsız işlev olacak, bir. İki tane girdisi olacak, iki. İlk girdisi bizim çift dizinimiz, ikinci girdi de harflerden biri.

dez yazı = "Kojo ile oyun oynayarak Scala dilini öğrenmek ve hatta işlevsel ve nesneye yönelik yazılım becerisi edinmek harika değil mi "
dez sözcükDizini = yazı.böl(" ")
dez harfler = sözcükDizini.düzİşle(_.dizine).işle(_.büyükHarfe).sırayaSok(_ < _)

dez sıklık = harfler.soldanKatla(Dizin[(Harf, Sayı)]()) {
    durum ((önceki, sayaç) :: kuyruk, harf) eğer (önceki == harf) => (önceki, sayaç + 1) :: kuyruk
    durum (sunum, harf)                                         => (harf, 1) :: sunum
}
// Sonucu okunur bir halde yazalım bakalım ne bulduk:
satıryaz(sıklık.işle{p => s"${p._1}:${p._2}"}.yazıYap(" "))

Bunu anlayamadım diye üzülme sakın! Daha önce görmediğimiz bir kaç becerisi var Scala derleyicisinin burada!

1) adsız işlevimizi tanımlarken durum yani desen/örüntü eşleme yapısı kullanabiliriz. Bunun için normal parantez yerine kıvrık parantez kullanmamız yeter. eşle özel sözcüğüne gerek kalmadı. Ondan önce gelen değişmezlere de! Yani bu epey faydalı bir kısa yol oluyor ve bunu iyi bilmekte fayda var! Normal, yani kısaltılmamış halini anımsayalım hemen: (a, b) => (a, b) eşle {durum ... => ...; durum ... => ...}. eşle ve ondan önceki hiç birşeye gerek kalmıyor!

2) desen eşleme yani örüntülü eşleme yapmak için kullanmıştık bu durum yöntemini. Burada da durum sözcüğünden hemen sonra gelen kısımda elimizdeki iki girdiyi çözümlüyoruz. Biraz önce de dediğimiz gibi ilk girdi ufak ufak oluşturduğumuz yeni dizinimiz. (önceki, sayaç) :: kuyruk yeni kurduğumuz dizinin başı ve kuyruğuyla eşleşiyor ve onların üçüne de isim takıveriyor. kuyruk bariz. baş eleman da bir önceki harf ve ondan şu ana kadar kaç tane saydığımızı tutan sayaç. kuyruk değerinden sonra gelen harf ise katlama işlemini yaptığımız harfler dizinindeki harflerden biri. Katlama işlevi her harfin üstünden teker teker geçecek elbet.

3) ve son! İlk desen/örüntü eşleme satırında bir de koşul girdik eğer diyerek. Bu çok önemli. Yeni bir harfe geçip geçmediğimize dikkat etmemiz gerek! Eğer en son saydığımız harften aynısı geldiyse sayacı arttırmalıyız. Yoksa yeni bir sayaç başlatmalı.

Şimdi, bilgisayarın Scala derleyicisi sayesinde anladığı bu epey karmaşık görünen işlemi okuyalım bakalım daha iyi anlamış mıyız: esas girdimiz olan sıralanmış harfler dizisindeki her harf için teker teker şunu yapalım: eğer sıfırdan oluşturduğumuz sunum adlı yeni çift dizisinin başındaki çiftin harfi ile aynıysa, dizinin başını sayacın bir arttığı yeni bir başla değiştirelim. Yoksa, sunum dizisine elimizdeki harf için yeni bir baş ekleyelim ve harfin sayacını bire eşitleyelim. Çok da karmaşık değilmiş, öyle mi?

İki üç satırda ifade edebildiğimiz bu işlem hem epey düzgün hem de epey etkileyici bence. Ya sen ne dersin?

Elbette ki isteyen daha eski dillerde yapıldığı gibi yazabilir, Basic, Fortran, C, C++, Java örneğin. Yani:

tanım harfSıklığı(girdi: Dizin[Harf]): Dizin[(Harf, Sayı)] = {
    den sunum = Dizin[(Harf, Sayı)]()
    eğer (girdi.boşMu) sunum
    yoksa {
        den önceki = girdi.başı
        den kuyruk = girdi.kuyruğu
        den sayaç = 1
        yineleDoğruKaldıkça (!kuyruk.boşMu) {
            eğer (kuyruk.başı == önceki) sayaç += 1
            yoksa {
                sunum = (önceki, sayaç) :: sunum
                sayaç = 1
                önceki = kuyruk.başı
            }
            kuyruk = kuyruk.kuyruğu
        }
        (önceki, sayaç) :: sunum
    }
}

dez yazı = "Kojo ile oyun oynayarak Scala dilini öğrenmek ve hatta işlevsel ve nesneye yönelik yazılım becerisi edinmek harika değil mi "
dez sözcükDizini = yazı.böl(" ")
dez harfler = sözcükDizini.düzİşle(_.dizine).işle(_.büyükHarfe).sırayaSok(_ < _)
satıryaz(harfSıklığı(harfler))

Bu da çalıştı elbette. Ama bilmem sen hangisini daha çok beğendin. Bak biraz önce işlevsel yazılım esaslarını kullanarak nasıl yaptığımızı hepsi bir arada tekrar görelim ve iki farklı yöntemi yan yana karşılaştıralım:

dez harfSıklığı = "Kojo ile oyun oynayarak Scala dilini öğrenmek ve hatta işlevsel ve nesneye yönelik yazılım becerisi edinmek harika değil mi".
    böl(" ").düzİşle(_.dizine).işle(_.büyükHarfe).sırayaSok(_ < _).
    soldanKatla(Dizin[(Harf, Sayı)]()) {
        durum ((önceki, sayaç) :: kuyruk, harf) eğer (önceki == harf) => (önceki, sayaç + 1) :: kuyruk
        durum (sunum, harf)                                           => (harf, 1) :: sunum
    }

satıryaz(harfSıklığı.
    sırala(p => p._2).tersi.
    işle { p => s"${p._1}:${p._2}" }.
    yazıYap(" "))

Bilmem farkettin mi ama sadece iki komut dizisi yeterli oldu. Verilen cümlenin harflerinin sıklığını hesapladık ve sıraya sokup yazdırdık. Yukarıda adlarını saydığımız daha eski ve daha az becerikli dillerle bilgisayara istediğini yaptırmanın ne kadar zor olduğunu bilenlere bu kısacık yazılım o kadar etkileyici olur ki anlatamam!

14Matematiksel İşlevler

Matematik fonksiyonları bazen işimize çok yararlar. Birkaç tanesini görelim. Tablolardaki komutlara tıklayınca düzenleyicide açılır; sonucu görmek için satıryaz içine aldık.

Değişmez (sabit) Değerler

Matematik sınıfında iki çok meşhur sabit var:

KomutAçıklama
satıryaz(eSayısı)e, meşhur matematikçi Euler'den gelir adı 2.718282... değerindedir, doğal logaritmanın da tabanıdır.
satıryaz(piSayısı)pi sayısı, 3.14159265 .... yani yarıçapı 1 olan dairenin çevre uzunluğunun yarısıdır.

Trigonometri fonksiyonları

Trigonometrik işlevler girdi olarak radyan birimi kullanırlar. Radyan kavramını anlatan çok güzel bir örneğimiz var masaüstü Kojo'da: Örnekler menüsünde en altta Matematik Öğrenme Birimleri menüsü var. Onun en altında 'Açı Nedir?' var. Günlük hayatta biz 90 derece, 180 derece gibi bize daha doğal gelen derece birimini kullanırız açıları ifade etmek için. Radyandan dereceye çevirmek için dereceye yöntemini, tersini yapmak için de radyana yöntemini kullanabiliriz. Bilmemiz gereken tek şey şu: 2*pi radyan 360 dereceye eşittir. Aşağıda sıraladığımız yöntemlerden başka yay yöntemleri de var.

Kısa not: aşağıdaki tanımlarda G1, G2, ... ile fonksiyona girilen değerleri ifade ediyoruz kısaca. Yani işlevAdı(G1, G2, G3, ...).

KomutAçıklama
satıryaz(sinüs(piSayısı/6))G1 girdisinin yani burada Pi/6 değerinin sinüsü.
satıryaz(kosinüs(piSayısı/6))G1 girdisinin kosinüsü.
satıryaz(tanjant(piSayısı/6))G1 girdisinin tanjantı.
satıryaz(radyana(45))G1 (derece olsun) girdisini radyana çevirir.
satıryaz(45.radyana)Kesirlerin 'radyana' yöntemini de kullanabiliriz.
satıryaz(dereceye(piSayısı/2))G1 (radyan olsun) girdisini dereceye çevirir.
satıryaz(piSayısı.dereceye)Bir değişik yöntem de bu aynı radyana yöntemi gibi.

Üst bulma yöntemleri

Logaritma ve üst bulmak için iki temel işlev var. İkisi de e tabanını kullanır (e sayısı).

KomutAçıklama
satıryaz(eüssü(piSayısı))e (2.71...) sayısının G1 üssünü hesaplar.
satıryaz(gücü(6, 3))G1 girdisinin G2 üssünü bulur.
satıryaz(logaritması(10))G1'in E tabanına göre logaritmasını verir.

Başka bazı fonksiyonlar

KomutAçıklama
satıryaz(karekökü(225))G1'in karekökü.
satıryaz(mutlakDeğer(-7))Mutlak değeri verir. Girdinin türü neyse, çıktı da aynı tür sayı olur, Sayı, Uzun, UfakKesir, Kesir.
satıryaz(enİrisi(8,3))G1 ve G2 arasında büyük olanı bulur.
satıryaz(enUfağı(8,3))G1 ve G2 arasında küçük olanı bulur.

Sayı türüyle ilgili yöntemler

Bu işlevler kesirli sayıları tam yani kesirsiz sayıya çevirir. Ama dikkat, çıktı türü hala Kesir olabilir.

KomutAçıklama
satıryaz(taban(3.12))G1 girdisinden küçük ya da ona eşit olan en büyük tam sayıyı hesabeder.
satıryaz(tavan(3.12))G1 girdisinden büyük ya da ona eşit olan en küçük tam sayıyı verir.
satıryaz(yakını(3.51))G1 değerine en yakın tam sayıyı Kesir türünde bir çıktı olarak verir.
satıryaz(yakın(3.48))G1 kesirine en yakın sayıyı Uzun türünde çıktı olarak verir.
satıryaz(yakın(2.6F))G1 UfakKesir'ine en yakın sayıyı Sayı türünde çıktı olarak verir.

Rastgele (Random) sayılar, rastgele başka şeyler...

KomutAçıklama
satıryaz(rasgele)0.0 ile 1.0 arasında rastgele bir sayı verir. Çıktı türü Kesir olur. Gerçekten rastgele olmasa da, çok yakındır. Pseudo-random denir tam doğru anlamıyla. Birkaç kere çalıştır bak ne olacak.. İşte rasgele ya da rastgele.
satıryaz(rastgele(2))G1 girdisinden küçük rastgele bir tam sayı verir, sıfır da dahil.
satıryaz(rastgele(2, 5))G1 girdisinden başlayarak G2 girdisinden küçük rastgele tam sayı verir.
satıryaz(rastgeleSayı)Rastgele tam sayı, Sayı.EnUfağı ile Sayı.Enİrisi arasında.
satıryaz(rastgeleUzun)Rastgele tam sayı, Uzun.EnUfağı ile Uzun.Enİrisi arasında.
satıryaz(rastgeleKesir(3.0))0'dan G1 girdisine kadar rastgele bir kesir.
satıryaz(rastgeleKesir(2.0, 3.0))G1 ve G2 girdileri arasında rastgele bir kesir
satıryaz(rastgeleÇanEğrisinden)Normal dağılım da derler. 0'a yakın sayılar daha çok rastlar, büyüdükçe ve küçüldükçe daha az gelirler. Diğer adları: rastgeleNormalKesir, rastgeleDoğalKesir (ikojo'da bu sonuncusu var).
satıryaz(rastgeleİkil)Rastgele doğru (true) ya da yanlış (false). Bir diğer adı da: rastgeleSeçim (ikojo'da bu ad var).
satıryaz(rastgeleRenk)Matematikle ilgisi yok gibi. Ama adı üstünde. Resim çizerken işe yarayabilir.
satıryaz(rastgeleŞeffafRenk)Deneyerek daha iyi anlarsın.
satıryaz(rastgeleDiziden(Dizi(1, 3, 5)))G1 girdisindeki diziden rastgele seçer.
satıryaz(rastgeleKarıştır((1 |-| 6).dizine))Girilen dizini ya da diziyi karıştırır.

Ağırlıklı seçim: G2 girdisinde verilen ağırlık oranlarını kullanarak G1'den rastgele seçer.

satıryaz(rastgeleDiziden(
  Dizi(1.0, 10.0, 100.0),
  Dizi(0.9, 0.09, 0.01)))

Bir önceki örneği bin kere çalıştıralım ve 10'dan büyük yani 100 gelenleri toplayalım. Birkaç kere çalıştırarak farklı farklı sonuçlar görebilirsin. Bir de sadece 10 gelenleri toplamayı dene istersen. 100 gelme olasılığı %1. Ama deneysel olarak ne çıkıyor nasıl bulabiliriz?

yaz({
    için (i <- 1 |-| 1000) ver
    rastgeleDiziden(
        Dizi(1.0, 10.0, 100.0),
        Dizi(0.9, 0.09, 0.01))
}.
    ele(_ > 10).
    topla)

15İşlem Önceliği ve Birleşmeliği

İşlem Önceliği

Scala'nın işlemleri de birer nesne dersem artık hiç şaşırmazsın değil mi? Belki de şaşırman doğal aslında. Ben de hala hayret ediyorum biraz. Şöyle anımsayalım: Scala'nın sunduğu bütün işlemler de aslında birer yöntem, onun için de birer işlev, onun için de birer nesne! Dolayısıyla işlemlerin adı olarak da her hangi bir ad kullanabiliriz. Yeter ki geçerli bir ad olsun. Geçersiz adlar nasıl olur?

dez 0abc = "olur mu canım?"
dez *olmaz = "bu da yanlış!"

Yani harf ya da özel bir karakterle başlamaması gerek. Ama bu kuralın da istisnaları var.

dez _0abc = "bak bu oldu"
dez _a$b__c_ = "bak bunlar da oldu"
satıryaz(_0abc, _a$b__c_)
dez a*b = "yok yok bu hala problem"

Gördüğümüz gibi bazı özel karakterleri değişken ve değişmez adlarında kullanamıyoruz. Matematik işlemlerinin imleri hiç olmuyor. Ama aşağıdaki yazılımda bir istisna daha göreceğiz.

Peki geçerli ve geçersiz bazı adları görmemiz iyi oldu da esas konumuza dönelim, yani öncelik meselesi. Bakın aşağıdaki tablo öncelik sırasını gösteriyor. Üsttekiler önce geliyor. Eğer bir işlem iminde birden fazla karakter varsa o durumda da ilk karakter önem kazanıyor. Örneğin +* diye bir işlem tanımlarsak, onun önceliği + imininkiyle aynı olur.

İşlem imleriAçıklama
diğer bütün özel karakterlerÖrneğin @ { } ( ) vb...
* / % + - : = ! < > & ^ |Soldakiler sağdakilerden önce gelir.
bütün harflerO halde a, b'den önce gelir. Ya büyük A?
eşitlik işlemleriÖrneğin = += -= *= /= vb...

İşlem Birleşmeliği

Birkaç örnekle daha kolay olacak bu konuyu işlemek:

satıryaz(3 * 5 * 7)
satıryaz((3 * 5) * 7)
satıryaz(3 :: 5 :: Boş)
satıryaz(3 :: (5 :: Boş))
satıryaz((3 :: 5) :: Boş)

Ne oldu? Burada iki değişik işlemle karşı karşıyayız. * imi ile :: imi farklı çalışıyor. Son örneğimiz çalışmadı çünkü :: yani bir dizinin kuyruğuyla başını birleştirip yeni bir dizin oluşturan birleştirme işlemi sağdaki değeri temel alıp onun yöntemi olan :: işlemine soldaki değeri girdi olarak giriyor ve sonunda yeni bir dizin çıktısı veriyor. Son örnekteki hata nereden kaynaklandı şimdi daha iyi anladık. Ama * işlemi öbür taraftan çalışıyor, yani soldaki değer temel alınıyor onun yöntemi olarak * çağrılıyor ve ona sağdaki değer girdi oluyor. Scala bu durumu düzenlemek ve programcıya iki seçenek de sunmak için şunu yapıyor: işlemin adına bakıyor ve son karakteri seçiyor. Eğer son karakter : ise, yani iki nokta üstüste ise, sağdan birleşme yapıyor ve sağdaki değerin üzerindeki yöntemi çağırıyor ve soldaki değeri giriyor o yönteme. Adlarının son karakteri : olmayan yöntemlerse öbür türlü çalışıyor, yani yukarıda gördüğümüz * örneğindeki gibi soldaki değerin yöntemi çağrılıyor ve sağdaki değer girdi oluyor. Yani a * b yazarsak derleyici a.*(b) görmüş gibi çalışıyor, ama a *: b yazarsak b.*:(a) gibi çalışıyor. Bir örnekle alıştırma yapalım ki tam pekişsin. Diyelim ki iki sayıyı önce toplayıp sonra toplamını ikinci sayıyla çarpmak istiyoruz. Bunu da çok yapacağız. O zaman bir nesne türü yani sınıf tanımlayıverelim ve bütün seçenekleri deneyelim:

durum sınıf Deneme(s: Sayı) {
    tanım +*(x: Deneme) = (s + x.s) * x.s
    tanım +:(x: Deneme) = (s + x.s) * x.s
}
dez (a,b) = (Deneme(5), Deneme(3))
satıryaz((a +* b, b +* a, a +: b, b +: a))

Umarım faydalı olmuştur. Sen de birşeyler dene, hem parmakların hem de beynin daha iyi öğrensin!

Bu vesileyle daha büyük bir örnek görelim ve bilgisayarın temeli olan mantığa giriş yapalım. Mantık nedir bilir misin? Belki iyi tanımazsın ama bildiğinden eminim çünkü mantık bizim altıncı hissimiz gibidir. Mantıksızlık hiç hoşumuza gitmez. Bak şöyle yazabiliriz mantık işlemlerinin temelini. Bunu okurken + * x ve ! gibi imlerin tanımlarını nasıl yaptığımıza da dikkat!

durum sınıf Önerge(doğruMu: İkil) yayar BaskınYazıyaYöntemiyle {
    tanım tersi = Önerge(!doğruMu)
    tanım ve(öbürü: Önerge) = eğer (doğruMu) öbürü yoksa bu
    tanım veya(öbürü: Önerge) = eğer (doğruMu) bu yoksa öbürü
    tanım yada(öbürü: Önerge) = (bu veya öbürü) ve (bu ve öbürü).tersi
    tanım eşittir(öbürü: Önerge) = doğruMu == öbürü.doğruMu

    tanım unary_!(): Önerge = bu.tersi  // ! imi nesnenin önüne gelsin ve girdisiz yöntem olsun istiyoruz. Onun için bu 'unary_' gerekli.
    tanım *(öbürü: Önerge) = bu ve öbürü
    tanım +(öbürü: Önerge) = bu veya öbürü
    tanım x(öbürü: Önerge) = bu yada öbürü
    tanım ==(öbürü: Önerge) = doğruMu == öbürü.doğruMu
    tanım ==>(öbürü: Önerge) = bu.tersi veya öbürü
    tanım <=>(öbürü: Önerge) = (bu ==> öbürü) ve (öbürü ==> bu)

    baskın tanım yazıya = eğer (doğruMu) "doğru" yoksa "yanlış"
    tanım to01 = eğer (doğruMu) "1" yoksa "0"
}

tanım tersi(x: Önerge) = x.tersi
tanım ve(x: Önerge, y: Önerge) = x ve y
tanım veya(x: Önerge, y: Önerge) = x veya y
tanım yada(x: Önerge, y: Önerge) = x yada y
tanım eşittir(x: Önerge, y: Önerge) = x eşittir y
tanım ise(x: Önerge, y: Önerge) = x ==> y
tanım gvy(x: Önerge, y: Önerge) = x <=> y // gerek ve yeter

tanım deneme() = {
    tanım çizgi = satıryaz("-" * 34)
    tanım çift = {çizgi; çizgi}
    dez ara = " " * 10
    çift
    dez (d, y) = (Önerge(doğru), Önerge(yanlış))
    dez (dt, yt) = (tersi(d), tersi(y))
    satıryaz(s"  $d'nun tersi == $dt")
    satıryaz(s"  $y'ın tersi == $yt")
    çizgi
    satıryaz(s"$ara !${d.to01} == ${(!d).to01}")
    satıryaz(s"$ara !${y.to01} == ${(!y).to01}")
    çift
    dez seçenek = Dizin(d, y)
    için ((bağlam, adı) <- Dizin((ve _, "ve"), (veya _, "veya"), (yada _, "ya da"))) {
        için (a <- seçenek; b <- seçenek) {
            satıryaz(f"  $a%6s $adı%6s $b%6s == ${bağlam(a, b)}")
        }
        çizgi
    }
    çizgi
    için ((bağlam, adı) <- Dizin((ve _, "*"), (veya _, "+"), (yada _, "x"), (ise _, "==>"), (gvy _, "<=>"))) {
        için (a <- seçenek; b <- seçenek) {
            dez c = bağlam(a, b)
            satıryaz(f"$ara ${a.to01}%s $adı%s ${b.to01}%s == ${c.to01}")
        }
        çizgi
    }
    çizgi
    için (a <- seçenek; b <- seçenek) {
        dez (ab, a_b, axb) = (a ve b, a veya b, a yada b)
        satıryaz(f"  $a%6s ve    $b%6s == $ab")
        satıryaz(f"  $a%6s veya  $b%6s == $a_b")
        satıryaz(f"  $a%6s ya da $b%6s == $axb")
        çizgi
    }
    çizgi
    için (a <- seçenek; b <- seçenek) {
        dez (ab, a_b, axb) = (a * b, a + b, a x b)
        satıryaz(s"$ara ${a.to01} * ${b.to01} == ${ab.to01}")
        satıryaz(s"$ara ${a.to01} + ${b.to01} == ${a_b.to01}")
        satıryaz(s"$ara ${a.to01} x ${b.to01} == ${axb.to01}")
        çizgi
    }
    çizgi
}
deneme

16Yazıların (String) Kullanılışı

Yazı Türü

Ekrana yazı yazmak bilgisayar programlamada epey sık karşılaşılan bir sorun! Bu bölümde bazı tanımlar ve yazı türünün faydalı yöntemlerinden bazılarını göreceğiz. Bu yöntemlerin benzerleri Dizin türünde ve ona benzeyen sıra sıra elemanlar içeren başka türlerde de var, Dizim (Array), Yöney (Vector), Dizi (Seq) gibi hafifçe başkalaşmış ama benzer türler. Bir sonraki bölümde Dizin yöntemlerini görünce benzerliği farkedeceksin.

Aşağıdaki tanımlarda G1, G2, ... ile fonksiyonlara girilen değerleri ifade ediyoruz kısaca. Yani G1.işlevAdı(G2, G3, ...). Tablolardaki komutlara tıklayınca düzenleyicide açılır; sonucu görmek için satıryaz içine aldık.

Yazılar için kaçış karakterleri

KaçışAnlamıKaçışAnlamı
\nyeni satır\bgeri adım
\tbüyük ara\fbir sayfa at
\rsatır başı\"çift tırnak
\'tek tırnak\\geri taksim işareti

Ekleme

Yazıları aynı toplama yapıyormuş gibi + imiyle ekleyebiliriz. Girdi olan yazılar değişmez. Yeni bir çıktı oluşur sadece. Yazı değerlerini hep dez kullanarak sabit tutmakta fayda var. Ama çok istersek den anahtar sözcüğü kullanarak değerini değiştirebileceğimiz yazı değişkenleri de tanımlayabiliriz.

dez a = "Büyük"
dez b = "Patlama"
dez c = a + " " + b
satıryaz(a, b, c)
sil
den gerekYokAslında = "böyle değişkenlere\n"
gerekYokAslında += "pek de gerek yok"
yazı(gerekYokAslında)

Sanırım hemen hemen bütün nesnelerin yazıya diye bir yöntemi var. Bu yöntem nesnenin neye benzediğini yazı olarak ortaya koyar ve çok faydalıdır. Mantık önermeleri için tanımladığımız sınıfı anımsadın mı? Bir önceki bölüme bakıver istersen. Orada kendi yazıya yöntemimizi tanımlamış ve kullanmıştık.

dez x = (2).yazıya + " " + (3.1F).yazıya
satıryaz(x)

Uzunluk veya Boy

KomutAçıklama
satıryaz("dört".boyu)G1 yazısının uzunluğu yani kaç karakterden oluştuğunu bildirir.

Karşılaştırma

KomutAçıklama
satıryaz("uzun".kıyasla("uzuner"))G1 ile G2'yi karşılaştırır. Eğer G1 G2'den önce geliyorsa eksi bir sayı, aynıysa sıfır, sonra geliyorsa artı bir sayı verir. A ile a'yı karşılaştır istersen
satıryaz("uzun".kıyaslaKüçükHarfBüyükHarfAyrımıYapmadan("Uzun"))Karşılaştırma yaparken harflerin büyük ya da küçük olmasını göz ardı eder
satıryaz("kitap".eşitMi("film"))İki yazı aynı ise doğru der, yoksa yanlış
satıryaz("kitap".eşitMiKüçükHarfBüyükHarfAyrımıYapmadan("KITAP"))Eşitliğe bakarken harflerin büyük ya da küçük olmasını göz ardı eder
satıryaz("kitaplık".başındaMı("kitap"))G1 yazısının başında G2 var mı?
satıryaz("kalınkitap".başındaMı("kitap", 5))G1 yazısının G3. harfinden itibaren G2 geliyor mu?
satıryaz("kitaparası".sonundaMı("arası"))G1'in sonunda G2 var mı?

Aramak ve bulmak

Bir yazı içinde başka bir harfi ya da yazıyı arayıp bulmak için kullanabileceğimiz yöntemler bunlar. Eğer aradığımızı bulamazsa -1 çıkar. İlk harfin konumu 0 kabul edilir. Gariptir biraz ama bilgisayarda hep sıfırdan saymaya başlarız.

KomutAçıklama
satıryaz("imrendim".içeriyorMu("ren"))G2, G1'in içinde geçiyor mu?
satıryaz("imrendim".sırası("nd"))G2, G1'in içinde kaçıncı konumda? Birden fazla varsa, ilk konumu verir.
satıryaz("imrendirdim".sırası("di", 7))G2, G1'in G3'üncü harfinden sonra hangi konumda?
satıryaz("imrendirdi".sırası('r'))G2 karakterinin G1 içindeki ilk konumu?
satıryaz("imrendirdik".sırası('r', 4))G2, G1'in G3'üncü harfinden sonra hangi konumda?
satıryaz("imrendiler".sırasıSondan('e'))G2'nin G1 içindeki son konumu nedir?
satıryaz("imrenerek".sırasıSondan('e', 6))G2'nin G1 içindeki G3. harf veya daha önceki son konumu nedir?
satıryaz("dipdirildi".sırasıSondan("di"))G2'nin G1 içindeki son konumu
satıryaz("dipdirildi".sırasıSondan("di", 5))G2'nin G1 içindeki G3. harf ya da daha önceki son konumu

Yazıdan parça çıkarmak

KomutAçıklama
satıryaz("aslangibi".harf(3))G1'in G2 konumundaki harfi
satıryaz("aslangibi".parçası(3))G1'in G2 konumundaki harfinden sonuna kadar olan parçası
satıryaz("aslangibi".parçası(3, 5))G1'in G2 konumundan G3'e kadarki parçası. G3. harf hariç.

Yazıdan başka bir yazı türetmek

KomutAçıklama
satıryaz("Merhaba Kardeş".küçükHarfe)Bütün harfleri küçük olacak şekilde G1'in yeni bir kopyası
satıryaz("Merhaba Kardeş".büyükHarfe)Bütün harfleri büyük olacak şekilde G1'in yeni bir kopyası
satıryaz(" Merhaba Kardeş ".kısalt)G1'in başındaki ve sonundaki boşlukların silinmiş kopyası
satıryaz("Savar".değiştir('a', 'e'))G1'in içindeki bütün G2 harflerinin G3 ile değiştirilmiş kopyası
satıryaz("Saye saye".değiştir("ay", "ev"))G1'in içindeki bütün G2 yazılarının G3 ile değiştirilmiş kopyası

Yazıya çeviren yöntemler

KomutAçıklama
satıryaz(Yazı.olarak(Dizin(1,2,3)))G1'i yazıya çevirir. G1 herhangi temel bir tür ya da herhangi bir sınıfın nesnesi olabilir.
satıryaz(Dizin(1,3,3,1).yazıYap("-ve-"))G1 dizisinin elemanlarını aralarına G2 ekleyerek yazıya çevirir
satıryaz(Küme(1, 3, 5, 3, 1).yazıYap("{", " ", "}"))Bir öncekinde olduğu gibi G1'in elemanlarının aralarına G3'ü ekleyerek yazıya çevirir ama en başa G2, en sona da G4 yazılarını ekler.

17Dizinlerin (List) Kullanılışı

Dizinler

Dizinler, algoritmaların işlevsel olarak tanımlanmasında en çok kullanılan veri yapısıdır. Bu bölümde dizinleri kolaylıkla tanımlamak ve kullanmak için en faydalı olan yöntemleri göreceğiz. En başta bir örnek Dizin tanımlayacağız. Sonra da onu diğer örnek yazılımcıklarda kullanacağız.

Daha önce görmüştük ama anımsatmakta fayda var: sık sık göreceğimiz üç im: _ + _ şu adsız işlevin kısa yolu: x,y => x + y. Böyle iki girdili işlemler çok yaygın. Onun için bu kısa yol işimize yarayacak. Tabii toplama olması şart değil. Çarpma da olur: _ * _. Benzer şekilde _.yöntem ile de e => e.yöntem adsız işlemini kısaca ifade ediyoruz. Bu kısa yollarda kullandığımız alt çizginin her biri yeni bir girdiye karşılık geliyor. Birinci altçizgi ilk girdiyi, varsa ikinci altçizgi ikinci girdiyi, üçüncü altçizgi üçüncü girdiyi, vb.

Örnek dizinimiz şu (her örnekte bu tanımı yineliyoruz ki tek başına çalışsın):

dez dzn = "Gün" :: "bu gün" :: "." ::
  "An" :: "bu an" :: "." :: Boş
satıryaz(dzn)

İçinde "Gün", "bu an", "." gibi 6 tane değer olan yeni bir Dizin[Yazı] tanımladık. Şimdi dizinlerle yapılabilecekleri örneklerle görelim. Her satırın yanındaki açıklamayı okuyup çalıştır:

dez dzn = "Gün" :: "bu gün" :: "." :: "An" :: "bu an" :: "." :: Boş
// Bomboş bir Dizin. Ya da Boş değerini de kullanabilirsin:
satıryaz(Dizin())
// İçinde dört eleman olan bir Dizin[Yazı]:
satıryaz(Dizin("Zaman", "ok", "gibi", "uçar mı?"))
// Üç tane iki nokta üstüste işlemi dizinleri birleştirerek yeni bir dizin türetiyor. :: ile karşılaştır:
satıryaz(Dizin("tik", "tok") ::: Dizin("ding", "dong"))
// Dizinin 3. elemanını verir (sıfırdan başlarsak üçüncü):
satıryaz(dzn(3))
// Dizinin içinde tek harften oluşan kaç sözcük var?
satıryaz(dzn.say(söz => söz.boyu == 1))
// Dizinin içinde "bu an" elemanı var mı?
satıryaz(dzn.varMı(söz => söz == "bu an"))
// Dizinin ilk üç elemanı düşmüş kopyasını verir:
satıryaz(dzn.düşür(3))
// Dizinin son dört elemanı düşmüş kopyasını verir:
satıryaz(dzn.düşürSağdan(4))
// Dizinin 2 veya 3 harfli elemanlarını verir:
satıryaz(dzn.ele(söz => 1 < söz.boyu && söz.boyu < 4))
// Verilen işlevi dizinin elemanlarına uygular sonra da hepsini birleştirir:
satıryaz(dzn.düzİşle(_.dizine))
// Dizinin bütün elemanları noktayla bitiyor olsaydı doğru derdi:
satıryaz(dzn.hepsiİçinDoğruMu(söz => söz.sonundaMı(".")))
dez dzn = "Gün" :: "bu gün" :: "." :: "An" :: "bu an" :: "." :: Boş
// Dizinin elemanlarını sırayla yazar:
dzn.herbiriİçin(söz => yaz(söz))
satıryaz()
// Bir öncekinin kısa hali:
dzn.herbiriİçin(yaz)
satıryaz()
satıryaz(dzn.başı)    // İlk elemanı verir
satıryaz(dzn.kuyruğu) // İlk eleman hariç gerisini verir
satıryaz(dzn.önü)     // Son eleman hariç gerisini verir
satıryaz(dzn.boşMu)   // boş olsaydı doğru derdi
satıryaz(dzn.sonu)    // Son elemanı verir
satıryaz(dzn.boyu)    // Kaç eleman olduğunu söyler
// dizinin her sözcüğünün sonuna soru işareti ekleyerek yeni bir dizi oluşturur:
satıryaz(dzn.işle(söz => söz + "?"))

Bir önceki gibi ama noktaları soru işaretiyle değiştirip yazalım. İkinci hali birincinin kısa yazılışı:

dez dzn = "Gün" :: "bu gün" :: "." :: "An" :: "bu an" :: "." :: Boş
dzn.işle { söz =>
    söz eşle {
        durum "." => "?"
        durum s   => s
    }
}.herbiriİçin(satıryaz)

dzn.işle {
    durum "." => "?"
    durum s   => s
}.herbiriİçin(satıryaz)
dez dzn = "Gün" :: "bu gün" :: "." :: "An" :: "bu an" :: "." :: Boş
// Diziden elemanları arasına virgül koyarak bir yazı yapar:
satıryaz(dzn.yazıYap(", "))
// Dizinin bir kopyasını verir ama bir harfli elemanları atlar:
satıryaz(dzn.eleDeğilse(söz => söz.boyu == 1))
// Tekrar eden elemanları atlar. Tekrarları bulmak için == işlemini kullanır:
satıryaz(Dizin(1,6,2,1,6,3).yinelemesiz)
// Elemanlarını ters sırada olan bir kopya verir:
satıryaz(dzn.tersi)
// A'dan Z'ye sıraya sokulmuş bir kopya verir ama büyük küçük harf ayırımı yapmadan:
satıryaz(dzn.sırayaSok((söz, t) => söz.küçükHarfe < t.küçükHarfe))

Katlama işlemleri de çok işe yarar! Bu sefer sayı dizisi kuralım:

dez sayılar=Dizin(1,7,2,8,5,6,3,9,14,12,4,10)
// Soldan sağa elemanları topluyoruz. Başlangıçta soldan -81 giriyoruz:
satıryaz(sayılar.soldanKatla(-81)(_ + _))
// Sağlamasını yapalım:
satıryaz(sayılar.topla)
// Şimdi de sağdan sola mantıksal veya işlemiyle birleştiriyoruz sayıların
// parçacıklarını. Başlangıçta en sağdan onaltılık tabanda 20 giriyoruz:
satıryaz(sayılar.sağdanKatla(0x20)(_ | _))

18Çizim ve Oyun

Kojo'nun Scala üzerine yaptığı katkılardan biri kaplumbağacıklarla çizimler yapmak ve bilgisayar oyunları yazmak. Ama Kojo'nun bir de Resim adlı bir birimi var ki, onunla daha da kolaylıkla bilgisayar grafikleri çizip onları canlandırabilir ve istersen oyunlar programlayabilirsin. Kojo'ya bu becerileri kazandıranlardan birisi de Peter Lewerin adlı bilgisayar aşığı. Lalit ve Peter sayesinde bilgisayar oyunları yazman ve güzel bilgisayar çizimleri yapman çok daha kolay. Resim biriminin becerilerinin tarihçesi Processing adlı bir Java projesine kadar uzanır. Processing birimini de Kojo'ya Peter eklemişti. Bu Resim birimi o kadar becerikli ki sırf onun üzerine büyük bir kılavuz bile yazılabilir. Burada bir kaç örnekle neler yapılabilir görelim ve tadında bırakalım istersen. Kojo ile öğren ve Benzetim savı sayfalarında başka pek çok örnek bulacaksın. Buradakileri deneyip anladıktan sonra onlara da bakmanda fayda var.

Eğer ilgilenip daha çok örnek görmek istersen, Kojo'nun web sayfasına da bak. Henüz sadece İngilizce. Resim yerine Picture diyecek. Ama oradaki örnekler de sana bir fikir verecektir.

Resim adlı birim birçok komut, işlem ve bir de çizim döngüsü sunuyor bize. Bunları kullanarak epey gelişmiş şekiller, resimler ve grafikler çizebiliriz. Çizim döngüsünü kullanarak grafikleri canlandırabilir ve istersek fare ve tuşlarla kontrol ekleyerek oyuna çevirebiliriz.

İlk örnekle başlayalım. Önce ekranı temizliyoruz. Sonra bir top çiziyor ve canlandırma döngüsünün içinde topun nasıl hareket edeceğini tanımlıyoruz. Resim birimi, bu döngüyü yaklaşık yirmibeş milisaniyede bir yineleyerek çalıştırıyor. Bilgisayarın hızına göre saniyede yaklaşık olarak 40 kere tekrar tekrar resim çizebiliyor yani. Bu da bizim gözümüzün değişiklikleri görme hızından fazla olduğu için bize canlı ve hareketli gibi görünüyor. Bu temel kavramları ve komutları kullanarak pek çok ilginç canlı, hareketli grafik çizebilirsin.

silVeSakla()
dez yç = 10 // topumuzun yarıçapı
dez top = Resim.daire(yç) // resmi
çiz(top) // bu da tuvale çiziveriyor topu
// topun içinde kalmasını istediğimiz sahayı da tanımlayıp çizelim
dez en = 400; dez boy = 200 // çizmesek de çalışır ama görmek faydalı:
çiz(kalemRengi(siyah) * götür(-en / 2, -boy / 2) -> Resim.dikdörtgen(en, boy))
// topun şu ufak ve iri sayılar arasında kalmasını istiyoruz
dez (ufakX, iriX) = (-en/2 + yç, en/2 - yç)
dez (ufakY, iriY) = (-boy/2 + yç, boy/2 - yç)
den x = 0; den y = 0 // topun konumunu bu değişkenlerle belirleyeceğiz
den dx = 2; den dy = 4 // topun hızını da x ve y'deki değişikliklerle belirleyeceğiz
// Bu dx ve dy'i birazcık değiştirip tekrar çalıştır. Hemen anlarsın ne oluyor.
// Bize bir de döngü gerek. Bu komut çok becerikli:
canlandır { // İçindeki komutlar saniyede yaklaşık 40 kere yinelenir.
    top.kondur(x, y) // bu yöntem resimlerin yani burda bizim topun yerini değiştirir
    // topun konumunu yani x ve y'yi günceliyoruz:
    // Zıplama alanının dışına çıkmasın diye sınıra gelince
    // hızının doğrultusunu tersine çeviriyoruz:
    dx = eğer (x <= ufakX || x >= iriX) -dx yoksa dx
    dy = eğer (y <= ufakY || y >= iriY) -dy yoksa dy
    x += dx
    y += dy
}

Masaüstünde bu örnek silipSakla(); ızgarayıGöster(); eksenleriGöster() ile başlıyordu; ikojo'da silVeSakla var, ızgara ve eksen komutları henüz yok.

İkinci örneğimiz küçük ve basit bir oyun. Duymuş olabilirsin: en eski bilgisayar oyunlarından duvara karşı pinpon (İngilizce adıyla Pong) oyunu. Tek kişilik bir oyun. Yapmamız gereken topa raketle vurup geri yollamak. Raketi fareyle yönetiyoruz. Top kaçarsa bir puan kaybediyorsun. İyi eğlenceler!

silipSakla()
çiz(götür(-200, -100) -> Resim.düz(0, 200)) // Üç duvar çizelim. Bu dik ön duvar
çiz(götür(-200, -100) -> Resim.düz(400, 0)) // Bu alt duvar
çiz(götür(-200,  100) -> Resim.düz(400, 0)) // Bu da üst duvar
dez rb = 80 // raketin boyu
dez raket = kalemRengi(mavi) -> Resim.düz(0, rb)
dez top = kalemRengi(mavi) -> Resim.daire(5)
dez skor = kalemRengi(siyah) * götür(-50, 150) -> Resim.yazı("Raketi fareyle yönet")
çiz(raket, top, skor)
den x = 0.0; den y = 0 // topun konumu
den dy = 8; den dx = -8.0 // topun hızı: d delta yani değişim ya da derivative yani türev demek
den rx = 0.0; den ry = 0.0 // raketin konumu
den ıska = 0 // top kaç kere kaçtı, saymak için
den vuruş = 0 // kaç kere raketle vurduğumuzu da sayalım
canlandır {
    raket.kondur(fareKonumu)  // raketi fareyle kontrol ediyoruz
    top.kondur(x, y) // topun yerini değiştirelim
    // top rakete çarpıyor mu?
    rx = fareKonumu.x; ry = fareKonumu.y
    dx = eğer ((dx > 0) && (mutlakDeğer(rx - x) < 10) &&
        (y > ry) && (y < ry + rb)) {vuruş += 1; -1.1*dx} yoksa dx
    // topun konumunu güncelleyelim, duvarlara bakalım
    dx = eğer (x + dx < -200) -dx yoksa dx  // ön duvardan sekti mi?
    dy = eğer ((y + dy < -100) || (y + dy > 100)) -dy yoksa dy // üst ve alt duvarlardan sekti mi?
    eğer (x + dx > 200) { x = -200; dx = 8; ıska += 1 } // ıskaladı
    x += dx; y += dy // topun gideceği noktayı hesaplayalım
    eğer (ıska > 0 || vuruş > 0) {  // skoru güncelleyelim
        dez mesaj = eğer (vuruş == 0) "Fareyi tuvale getir!" yoksa s"$vuruş kere vurdun"
        skor.güncelle(s"$mesaj\n$ıska kere ıskaladın")
    }
}
oyunSüresiniGeriyeSayarakGöster(60, "Süre bitti", yeşil) // oyun 60 saniye sürsün
Editörde aç →masaüstüikojo'da yok: Resim.düzResim.çizgi, fareKonumu, oyunSüresiniGeriyeSayarakGösteroyunSüresiniGöster

Temel kavramlar ve komutlarla artık tanıştın. Bir kaç tane daha top eklemeye ne dersin? Topların hızını rastgele değiştirerek oyunu daha eğlenceli kılabilirsin istersen. Raketin boyunu kısaltabilir ya da uzatabilirsin. Bir de programımızın bir (belki kimbilir daha çok) hatası var! İngilizce'de bug yani böcek denir. Ama nerden çıktı deme. Programı yazanın hatası de. Sen de yapacaksın bol bol hata. Hiç dert etme. Her hata aslında daha usta olmak için bir fırsat. Fırsatları hiç kaçırma! Hatayı farkettin mi? Bazen top raketin içinden geçiyor sanki elektron tünelleme yapıyormuş gibi (kuvantum mekaniği değil ki bu)! Bazen de ıskalaması gerekirken geri yolluyor.. Bakalım yazılımcığın içinde nerede? Sen bulup düzeltebilecek misin?

Klavye ve tuşlarla komut girişi

Pek çok oyun klavyeden komut bekler. Minecraft oynadın mı hiç? Hem fare hem de klavye komutları oyunu iyice eğlenceli kılar. Bak bu küçük oyun sağ/sol/yukarı ve aşağı ok olan tuşlarla kaplumbağacığa yön veriyor.

Her zamanki gibi aşağıdaki yazılımcığı çalıştır. Sonra da tuvale tıkla ki klavyeye bastığın zaman farketsin kaplumbağa ve söz dinlesin, senin komutlarını yerine getirsin.

sil()
görünür()
canlandırmaHızınıKur(100)
den (zıpladı, bilmem) = (yanlış, yanlış)
tuşaBasınca { t =>
    t eşle {
        durum tuşlar.sol    => açıyaDön(180)  // sola git
        durum tuşlar.sağ    => açıyaDön(0)    // sağa git
        durum tuşlar.yukarı => açıyaDön(90)   // yukarı
        durum tuşlar.aşağı  => açıyaDön(270)  // aşağı
        durum tuşlar.z      => zıpla(20); zıpladı = doğru
        durum tuşlar.a      => atla(fareKonumu.x,fareKonumu.y); zıpladı = doğru
        durum tuşlar.n      => noktayaGit(fareKonumu.x,fareKonumu.y); zıpladı = doğru
        durum tuşlar.boşluk => // büyük boşluk tuşu sadece ilerletsin
        durum _             => bilmem = doğru // diğer tuşlar
    }
    eğer (bilmem) { bilmem = yanlış } yoksa {
        eğer (zıpladı) { zıpladı = yanlış } yoksa { ileri(20)}
    }
}
tuvaliEtkinleştir()
Editörde aç →masaüstüikojo'da yok: tuşaBasıncaonKeyPress, fareKonumu

Hayal gücünü kullan, yazılımcığı değiştir (örneğin 45 derece döndürmek, yay ya da çember çizmek için komutlar ekleyebilirsin), tekrar çalıştır. Masaüstü düzenleyicisinde tuşlar. yazdıktan sonra (ama tırnak işaretleri olmadan!) kontrol tuşunu basık tutup büyük boşluk tuşuna bas ki başka hangi tuşları kullanabileceğini gör. ikojo'da tuşları tuşBasılıMı(tuşlar.sol) gibi bir canlandır döngüsü içinde sorgulayabilirsin; Kojo ile öğren sayfasındaki klavye oyununa bak.

Saat

Bir saat yapalım mı? BuAn adlı birimi kullanacağız. O da içinde Java'nın takvim, tarih ve zaman adlı kütüphane birimlerini kullanıyor.

silipSakla
dez yç = 150 //             saatin yarıçapı
dez pi2 = 2.0 * piSayısı // 2*pi radyan tam 360 derece dönüş demek

// saatin her unsuru için değişik bir renk kullanalım:
//   pembe, kırmızı, mavi, yeşil, turuncu ve siyah
// böylece hem renkli olur hem de programı anlamak kolaylaşır
tanım renk(r: Renk) = kalemRengi(r)

tanım saat = { // bu işlev saatin değişmeyen kısımlarını çizer
    için (i <- 0 |-| 59) { // dakika ve saat çentikleri
        dez ra = pi2 * i / 60
        dez (x, y) = (yç * sinüs(ra), yç * kosinüs(ra))
        dez çentikBoyu = eğer (i % 5 == 0) 0.9 yoksa 0.95
        dez (llx, lly, urx, ury) = (çentikBoyu * x, çentikBoyu * y, x, y)
        dez (en, boy) = (urx - llx, ury - lly)
        çiz(renk(pembe) * götür(llx, lly) -> Resim.düz(en, boy))
    }
    çiz(renk(kırmızı) -> Resim.daire(yç))
}

canlandır { //     bu döngü her saniyede yaklaşık 40 kere yinelenir
    Resim.sil() // bütün resimleri silelim
    saat //        tekrar çizelim
    dez buan = BuAn()
    dez s = pi2 * buan.saniye / 60 //         saniye kolu
    dez m = pi2 * buan.dakika / 60 //         dakika kolu
    dez h = pi2 * buan.saat / 12 + m / 12 //  saat kolu
    çiz(
        renk(mavi) -> Resim.düz(0.9 * yç * sinüs(s), 0.9 * yç * kosinüs(s)),
        renk(yeşil) -> Resim.düz(0.8 * yç * sinüs(m), 0.8 * yç * kosinüs(m)),
        renk(turuncu) -> Resim.düz(0.6 * yç * sinüs(h), 0.6 * yç * kosinüs(h)),
        renk(siyah) * götür(-yç - 50, -yç - 20) -> Resim.yazı(buan)
    )
}
Editörde aç →masaüstüikojo'da yok: BuAnbuSaniye, Resim.düzResim.çizgi, Resim.silresimleriSil

Saatin kaç tane çizimden oluştuğunu hesabedebilir misin? Yanıt pek çok ülkedeki emeklilik yaşı! Neyse ki bu 65 çizimi yapmak hem de saniyede 40 kere tekrar tekrar bizim bilgisayarımızı hiç yormuyor! Peki belki de yok, yok 63 tane çizgi, bir çember ve bir kaç da yazı diyerek itiraz edeceksin. Çok haklısın. Ve tebrikler!

Conway'in Yaşam Oyunu

İngilizce adıyla "The Game of Life" o kadar meşhur ki, bilgisayarcılar arasında basitçe Life yani Yaşam adıyla tanınır! Aslında o bir hücresel otomaton yani basit hücrelerden oluşan ve onların yerel etkileşimleri sayesinde kendi kendine devinen en basit program türlerinden biri. İngiltere doğumlu Amerika'da Princeton üniversitesinde matematik araştırmaları yapan John Horton Conway tarafından 1970 yılında icat edilmiş. Belki de keşfedilmiş demek lazım. Kimbilir. Sen ne dersin? Vikipedi ansiklopedisinden bakabilirsin.

Ana fikir çok basit. Basit bir avuç kurala göre hücreler canlanır ya da can verir. Her hücrenin sekiz komşusu var. Doğru mu? Canlı olanlara arkadaş diyelim. Bakın dört kural var:

  • Canlı bir hücrenin ikiden az arkadaşı varsa canı çıkar. Canı sıkılmış sanki.
  • Eğer iki ya da üç arkadaşı varsa hayatta kalır.
  • Eğer başına üçten fazla arkadaş toplanırsa canı çıkar! Bilmem neden. Sanki çok kalabalık olmuş gibi.
  • Cansız bir hücrenin tam üç tane arkadaşı varsa kendisi de canlanır. Allah'ın hakkı üç denir ya!

Bu yazılımcık soldanKatla adlı yöntemi kullanarak çok önemli bir kavram olan üst derece işlevlere örnek oluyor. Ne demek üst derece işlev? Başka işlevleri girdi olarak kabul eden onları kullanarak akıl almaz derecede becerikli olan komutlar. En başlarda gördüğümüz için yapısı yerine soldanKatla kullanarak bütün dünyayı baştan çiziveriyoruz. Bu arada bir de sağdanKatla var. O da esaslı bir işlev. Dünyayı temsil eden kümenin hücrelerinin hepsini işleyiveriyor.

Bu yaşam ya da hayat oyunu sıfır oyuncuyla oynanıyor! Çok sıkıcı mı dedin? Yok, çok ilginç aslında. Aslında sen çok önemlisin. Çünkü bu oyunun başlaması için en başta canlı hücrelere gerek var. Bunları sen belirleyebilirsin. Ama önce hazır bazı desenlerle başlamak daha kolay olur. başlangıç adlı komudu bul. Onun ikinci girdisi desen. Deseni seçmek için yapman gereken tek şey seç adındaki değeri değiştirmek. Birinciyle başlıyoruz. Ama sıfırdan ona kadar hepsini deneyebilirsin. Sonra hatta kendin de yeni desenler ekleyebilirsin. Bu simulasyonun hızını oran değerini değiştirerek ayarlayabilirsin.

silipSakla(); kalemRenginiKur(mavi)
// bu oyunun dünyası yani tahtası büyük bir kare. Kenarı KU uzunluğunda olsun
// Nasıl satranç tahtası 8x8, bu tahta da 128x128 kare.
dez KU = 128
// karenin kenarı kaplumbağanın on adımına denk

// ilk önce, bütün kareler cansız olmalı
den dünya = (0 |- KU * KU).soldanKatla(Sayılar())((x, y) => x :+ 0)
satıryaz(s"Dünyamızda $KU'in karesi yani ${dünya.boyu} tane hane var.")
yaz(s"Ekranımız ${(tuvalAlanı.eni / 10).sayıya} kare eninde ")
satıryaz(s"ve ${(tuvalAlanı.boyu / 10).sayıya} kare boyunda.")

dez oran = 5 // canlandırmayı yavaşlatmak için bunu arttır.
// En hızlısı 1. 40'a eşitlersen saniyede bir nesil ilerliyor yaklaşık olarak.
// Nasıl mı? Aşağıdaki canlandır adlı döngü komutu saniyede yaklaşık 40 kere yineleniyor.

dez gösterVeDur = yanlış // bunu doğru yaparsan deseni gösterip dururuz
dez sonundaDur = doğru // her desenin bir durağı var. Ondan sonra fazla bir şey değişmiyor.
// Ama, yine de çalışmaya devam etsin isterse, bunu yanlışa çevir.

// deseni seçelim:
dez seç = 1
// blok1 ve blok2 bir kaç füze yolluyor ve sonra 1000. nesil civarı gibi duruyor.
dez (desen, adı, durak) = seç eşle {
    durum 0 => (üçlüler, "üçlüler", 20)
    durum 1 => (kayGit, "kayGit", 500) /* makineli tüfek gibi */
    durum 2 => (esaslı, "esaslı", 1111) /* Yaklaşık 1000 nesil canlı sonra peryodik */
    durum 3 => (dokuzcanlı, "dokuzcanlı", 130) /* 131 nesil sonra can kalmıyor */
    durum 4 => (blok1, "blok1", 1200) //
    durum 5 => (blok2, "blok2", 1200) //
    durum 6 => (küçücük, "küçücük", 700) //
    durum 7 => (ü2a, "ü2a", 60) // üçlülere ek
    durum 8 => (ü2b, "ü2b", 60) // benzeri
    durum 9 => (dörtlü, "dörtlü", 30) // üçlü üretiyor
    durum _ => (tohum, "tohum", 2200) // ne muhteşem bir meşe palamudu!
}

dünya = başlangıç(dünya, desen)

yaz(s"$seç. desende ${desen.boyu} tane canlı kare var. Adı $adı.\nNesilleri sayalım: ")

den zaman = 0
dez z0 = buSaniye // şimdiki zamanı (geçmişte bir ana göre) anımsayalım
canlandır {
    dez nesil = zaman / oran + 1
    eğer (zaman % oran == 0) {
        Resim.sil()
        çizim(dünya)
        dünya = (0 |- KU * KU).soldanKatla(Sayılar())((x, y) => x :+ yeniNesil(dünya, y))
        yaz(s"$nesil ")
        eğer (gösterVeDur) durdur
    }
    zaman += 1
    eğer (sonundaDur && nesil == durak) {
        dez z1 = buSaniye - z0
        satıryaz(s"\n${yuvarla(z1, 2)} saniye geçti. Durduk.")
        durdur()
    }
}

// deseni kuralım
tanım başlangıç(v: Sayılar, desen: Dizin[(Sayı, Sayı)]) = desen.
    soldanKatla(v) {
        (x, y) => x.değiştir((y._1 + KU / 2) * KU + y._2 + KU / 2, 1)
    }

// yeni nesli bulalım
tanım yeniNesil(v: Sayılar, ix: Sayı) = {
    dez kural = Yöney(0, 0, 0, 1, 1, 0, 0, 0, 0, 0) // oyunun kuralları
    dez x = ix / KU; dez y = ix % KU
    dez t = (0 |- 3).soldanKatla(0)((st, i) => {
        st + (0 |- 3).soldanKatla(0)((s, j) => {
            dez xt = x + i - 1; dez yt = y + j - 1
            s + (eğer ((xt < 0) || (xt >= KU) || (yt < 0) || (yt >= KU)) 0 yoksa v(xt * KU + yt))
        })
    })
    eğer (v(ix) == 1) kural(t) yoksa { eğer (t == 3) 1 yoksa 0 }
}
// canlı kareleri çizelim. Can mavi çember içi kırmızı daire. Yarıçapı 5
dez yarıçap = 5
tanım çizim(v: Sayılar) = için (i <- 0 |- KU * KU)
    eğer (v(i) == 1) çiz(götür(
        (i / KU) * 2 * yarıçap - KU * yarıçap,
        (i % KU) * 2 * yarıçap - KU * yarıçap
    ) * kalemRengi(mavi) * boyaRengi(kırmızı) -> Resim.daire(yarıçap))

// Meşhur olmuş desenlerden birkaçı
tanım esaslı = Dizin((0, 1), (1, 0), (1, 1), (1, 2), (2, 2)) // orijinal adı: fpent
// İki küçücük grup var ve kolay kolay ölmüyor
tanım dokuzcanlı = Dizin((0, 1), (1, 0), (1, 1), (5, 0), (6, 0), (7, 0), (6, 2)) // diehard
tanım tohum = Dizin((0, 0), (1, 0), (1, 2), (3, 1), (4, 0), (5, 0), (6, 0))
// glider adlı meşhur üretken desen
tanım kayGit = Dizin((-18, 3), (-18, 4), (-17, 3), (-17, 4), (-8, 2), (-8, 3), (-8, 4), (-7, 1), (-7, 5),
    (-6, 0), (-6, 6), (-5, 0), (-5, 6), (-4, 3), (-3, 1), (-3, 5), (-2, 2), (-2, 3), (-2, 4),
    (-1, 3), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6), (4, 3), (4, 7),
    (6, 2), (6, 3), (6, 7), (6, 8), (16, 5), (16, 6), (17, 5), (17, 6))
tanım blok1 = Dizin((0, 0), (2, 0), (2, 1), (4, 2), (4, 3), (4, 4), (6, 3), (6, 4), (6, 5), (7, 4))
tanım blok2 = Dizin((0, 0), (0, 3), (0, 4), (1, 1), (1, 4), (2, 0), (2, 1), (2, 4), (3, 2), (4, 0),
    (4, 1), (4, 2), (4, 4))
tanım küçücük = Dizin((-18, 0), (-17, 0), (-16, 0), (-15, 0), (-14, 0), (-13, 0), (-12, 0), (-11, 0), (-9, 0), (-8, 0),
    (-7, 0), (-6, 0), (-5, 0), (-1, 0), (0, 0), (1, 0), (8, 0), (9, 0), (10, 0),
    (11, 0), (12, 0), (13, 0), (14, 0), (16, 0), (17, 0), (18, 0), (19, 0), (20, 0))
tanım üçlüler = Dizin((0, 2), (0, 3), (0, 4), (0, -2), (0, -3), (0, -4),
    (-2, 0), (-3, 0), (-4, 0), (2, 0), (3, 0), (4, 0))
// üçlülerden dikey olanları bağlayalım
tanım ü2a = Dizin((0, 0), (0, 1), (0, -1)) ++ üçlüler
// öbür türlü, yani yatay olanları bağlayalım
tanım ü2b = Dizin((0, 0), (1, 0), (-1, 0)) ++ üçlüler
tanım dörtlü = Dizin((0, 0), (1, 0), (-1, 0), (0, 2)) // dokuzcanlı'nın altkümesi

// sepet sepet yumurta
// sakın beni unutma
// şimdilik bu kadar
// yaşamın tadını çıkar
masaüstüikojo'da yok: silipSaklasilVeSakla, tuvalAlanıtuvalSınırları, durdurcanlandırmayıDurdur, Resim.silresimleriSil

Masaüstünde bu örnek çıktıyıSil ile başlıyor; ikojo'da o komut yok, her çalıştırma çıktıyı zaten temizler. Aynı oyunun ikojo için uyarlanmış sürümü Benzetim savı sayfasında var.

Düğüm açma oyunu

Bu oyun bize iki şey gösterecek: 1) Scala'nın bize sunduğu veri yapılarından Yöney ne işlere yarıyor. 2) Fareye tıklayıp bırakmadan sürüklersek neler yapabiliyoruz. Tabii daha da güzeli eğlenceli bir oyun yazabiliyoruz bu sayede.

Bu oyunu internetteki eski bir oyundan esinlenerek yazdık. Oyunun adı Planarity yani düzlemsellik. Mavi toplardan herhangi birinin üzerine tıklayıp bırakmadan tuvalde başka bir yere taşı. Göreceksin ki bağlı olduğu iki çizgi sanki lastik gibi hareket ediyor ve topu bırakmıyor. Bu bulmacanın amacı topları güzelce yerleştirerek çizgilerin birbirini kesmesine engel olmak. Yani bu düğümü çözmek. Çok zor sayılmaz. Biraz dene kolaylaşacak. Kırmızı kareye tıklarsan yeni bir düğüm oluşur.

Yazılımcığa bakarsan en başta KS adında bir değişmez (dez) göreceksin. Onu değiştirerek oyunun zorluğunu ayarlayabilirsin. Ne kadar büyütürsen o kadar zorlaşır! Bize ilham veren oyunun adı Planarity. Daha çok bilgi için buna tıkla.

// KS arttıkça oyun zorlaşır. Bir kenarda kaç tane nokta olsun?
dez KS = 4; dez AS = KS * KS
dez YÇ = 20 // bu da noktaların yarıçapı
durum sınıf Çizgi(n1: Nokta, n2: Nokta) { // her çizgi iki noktayı bağlar
    den çizgi = birDoğruÇiz(n1.x, n1.y, n2.x, n2.y) // bir doğru çizer
}
tanım birDoğruÇiz(llx: Kesir, lly: Kesir, urx: Kesir, ury: Kesir) = {
    dez (en, boy) = (urx - llx, ury - lly)
    dez r = götür(llx, lly) -> Resim.düz(en, boy)
    r.çiz
    r
}
// bütün çizgiler. boş küme olarak başlarız
den çizgiler = Yöney[Çizgi]()
// Noktayı tuvalde kaydıracağız. Yeri değişince ona bağlı çizgileri tekrar çizmemiz gerek
durum sınıf Nokta(den x: Kesir, den y: Kesir) {
    dez n = götür(x, y) * boyaRengi(mavi) -> Resim.daire(YÇ)
    n.çiz
    tanım yeniKonum(yeniX: Kesir, yeniY: Kesir) {
        x = yeniX; y = yeniY
        n.kondur(yeniX, yeniY)
    }
    // fareye tıklayıp çekince bu çalışacak
    n.fareyiSürükleyince { (mx, my) => { n.kondur(mx, my); x = mx; y = my; çizelim(çizgiler) } }
}
// Bütün noktaları (0,0) yani orijine üstüste koyalım. Merak etme birazdan dağıtacağız
silipSakla()
dez noktalar = (0 |- AS).soldanKatla(Yöney[Nokta]())((v, i) => { v :+ Nokta(0, 0) })

// çizgileri tanımlar ve noktalara bağlarız. Bir balık ağı gibi. KS * KS düğümlü
çizgiler = (0 |- AS).soldanKatla(Yöney[Çizgi]())(
    (çv, i) => {
        dez (x, y) = (i / KS, i % KS)
        dez çzg = eğer (y < KS - 1) { çv :+ Çizgi(noktalar(i), noktalar(i + 1)) } yoksa çv
        eğer (x < KS - 1) { çzg :+ Çizgi(noktalar(i), noktalar(i + KS)) } yoksa çzg
    })
serpiştir(noktalar) // noktaları yerleştir ve çizgileri çiz

// noktaları rastgele yerleştir
tanım serpiştir(hepsi: Yöney[Nokta]) {
    hepsi.herbiriİçin(nkt => nkt.yeniKonum(KS * YÇ * 6 * (rasgele - 0.5), KS * YÇ * 6 * (rasgele - 0.5)))
    çizelim(çizgiler)
}

// noktalar arasındaki çizgileri çizelim. Her çizgi, iki noktasının çemberine kadar gelsin
tanım çizelim(hepsi: Yöney[Çizgi]) {
    hepsi.herbiriİçin(çzg => {
        dez (x1, y1) = (çzg.n1.x, çzg.n1.y)
        dez (x2, y2) = (çzg.n2.x, çzg.n2.y)
        dez boy = karekökü(karesi(x2 - x1) + karesi(y2 - y1))
        dez (xr, yr) = (YÇ / boy * (x2 - x1), YÇ / boy * (y2 - y1))
        çzg.çizgi.sil
        çzg.çizgi = birDoğruÇiz(x1 + xr, y1 + yr, x2 - xr, y2 - yr)
    })
}

// kırmızı kareye basınca yeni bir düğümle baştan başlarız
tanım kare(x: Kesir, y: Kesir, en: Kesir) = {
    dez k = götür(x, y) * boyaRengi(kırmızı) -> Resim.dikdörtgen(en, en)
    k.çiz
    k
}
dez b = kare(-KS * 35, -KS * 35, 20)
b.fareyeTıklayınca { (x, y) => serpiştir(noktalar) }
Editörde aç →masaüstüikojo'da yok: silipSaklasilVeSakla, Resim.düzResim.çizgi

19Daha Çok Öğrenelim

Bundan sonra neler yapabilirsin

Bu kılavuzda, daha çok şeylere kadir, profesyonel yazılımcılar ve bilgisayar mühendislerinin de kullandığı ve çok derin bir yazılım dili olan Scala'nın bazı temel becerilerini gördük. Artık bu dilin temellerini tanıyorsun. Oldukça sofistike, yani acemilerin kolay kolay anlayamayacağı programlar yazabilir, Kojo'nun görsel ortamında çizimler ve oyunlarla iyi vakit geçirebilirsin.

Aranızda Java diliyle program yazmış olanlar varsa, Java'da kullandığınız birimleri (Java libraries), hatta Minecraft birimlerini de Scala kullanarak geliştirebilirsin. Scala ve Java'yı entegre etmek, yani birleştirmek işten değil.

Daha iyi, daha çok öğrenmek için bence iyi bir kitap gibisi yoktur. İngilizce bilenler için çok iyi bir kitap olarak şunu öneririm: Programming in Scala. Yazarları Martin Odersky, Lex Spoon ve Bill Venners. Bu bağlantıya tıklayarak daha çok bilgi edinebilirsin.

Çok yeni ve yine çok güzel bir e-kitap da şu: Hands-on Scala Programming. Yazarı genç ama usta programcı Haoyi Li. Pratik ve proje-temelli bir yol izliyor. Ben ikisini de çok beğendim. O da burada.

Türkçe bir kaynak da var: Koco ile programlamayı adım adım anlatan Programlamaya ve Algoritmalara Keyifli ve İşlevsel Bir Giriş kitapçığı. Yardım sayfasından tarayıcıda okuyabilir, PDF ya da EPUB olarak indirebilirsin.

Scala'nın bir ailesi ve topluluğu var ve onlar da senin gibi yeni öğrenenlere yardımcı olmayı çok severler. Buna tıklayarak scala-lang.org sitesinin belgelerine, bedava kitaplarına, dokümanlarına ulaşabilirsin.

Bu işin uzmanları, para kazanmak için yapanlar Scala, Akka ve Eclipse IDE (birleşik geliştirme ortamı) ve diğer açık kaynaklara bakabilirler. Hepsi burada.

Netbeans IDE de Scala'yı destekliyor. Hatta Kojo'nun yazarı Lalit Pant, Kojo'yu Scala ile Netbeans kullanarak yazdı! Bu konuda daha çok bilgi ararsan buna tıkla.

Bu kılavuzla Scala'nın kütüphanesinin küçük bir altkümesini gördük. Bütün kitaplığı görmek istemez misin? Buna tıklayıver.

ikojo'daki Türkçe komutların tam listesi için Koco Komutları kılavuzuna, adların İngilizce karşılıkları için Koco Sözlüğü'ne, masaüstü ile tarayıcı arasındaki farklar için Farklar sayfasına bakabilirsin.

Scala kullanırken hep çok keyif alman, güzel anlar, güzel günler yaşaman ve yaşatman dileğiyle!