Haskell’de List Comprehension
Ne zamandır bir şeyler yazmak istiyordum ama hep ilhamdan şikayetçiydim. Yazılım Mühendisliği üçüncü sınıf öğrencisiyim ve almış olduğum “Principles of Programming Languages” dersinde Haskell üzerine bir ödevim vardı. Ödev, Haskell’de verilen soruyu çözmeye yönelik ufak fonksiyonlar oluşturmak üzerineydi ve Haskell’i öğreneceğim bir kaynak aramaya başlamıştım.
Bir arkadaşımın tavsiyesi üzerine “learnyouahaskell.com” adında bir site üzerinden çalışmaya başladım. ”Learn You a Haskell for Great Good!” adında kitabı da bulunan bu kaynağa, internet üzerinden benim gibi ücretsiz erişebiliyorsunuz. Espirili anlatımı ve örnekler üzerinden konunun ilerleyişi çok hoşuma gitmişti. Sonra “Neden yararlandığım kısımları Türkçe’ye çevirmiyorum?” diye düşündüm. İşte şu an yazıyor oluşumun nedeni bu düşünceydi. (ekosisteme katkı!)
Bu ilk yazımda List Comprehension kısmını elimden geldiğince çevirmeye çalışacağım. Zaman buldukça ve Haskell üzerindeki çalışmamı ilerlettikçe (olur da çalışırsam) yeni kısımlar ekleyerek seri halinde bir yazı ortaya çıkarabilirim. Fakat şu an final sınavları dönemindeyim :(
Konuya hızlı bir şekilde girmeden önce biraz Haskell’den bahsedeyim diyorum.
Öncelikle Haskell tamamen fonksiyonel (Functional) bir dil. İmperatif (imperative) dillerde, programa hangi adımları izleyeceğini adım adım anlatıp, neyin nasıl yapılacağını güzelce yazabiliyoruz. Fonksiyonel dillerde ise, neyin ne olduğunu belirtiyoruz, nasıl yapılacağına program kendi karar veriyor.
Haskell tembel bir dil. Size bir sonuç göstermek zorunda kalmadıkça, fonksiyon çalıştırmaz. Örneğin, bir liste döndüren fonksiyonu çalıştırıp, ilk iki elemanını ekrana basmak istediğinizde, Haskell ilk iki elemanı bulacak kadar fonksiyonu çalıştırıp, orada bırakıyor.
Haskell zarif ve ifade gücü yüksek bir dil. Python’da da bulunan ve bu yazıda bahsedeceğim, list comprehension yapısı Haskell temelli. Ne kadar az kod, o kadar kolay bakım gereksinimi ve o kadar az bug demek.
Liste Kapsamları
Eğer daha önce matematik dersi aldıysanız, küme kapsamlarıyla da karşılaşmışsınızdır. Normalde, genel kümelerden daha özel ve spesifik kümeler oluşturmak için kullanılırlar. Mesela, ilk on çift doğal sayıyı gösteren basit bir küme kapsamı,
Çubuktan önceki kısma çıkış fonksiyonu denir, x bir değişken, N ise doğal sayılar kümesini ifade eder. x≤10 ifadesi de, x değişkenimiz için bir koşuldur. Bu tanıma göre oluşan bir küme, belirtilen koşulu sağlayan çift doğal sayıları içerir.
Eğer bu gösterimi Haskell’de yapmak isteseydik;
take 10 [2,4..]
ifadesini yazabilirdik. Peki ya ilk on doğal sayının iki katını daha karmaşık bir yoldan bulmak istersek? İşte bunun için liste kapsamlarını kurcalayabiliriz. Liste kapsamları, küme kapsamlarıyla çok benzerdir. Şimdilik aynı örnek üzerinden devam edelim. Kullanacağımız liste kapsam gösterimi;
[ x*2 | x <- [1..10] ]
şeklinde olacaktır. x değişkenimiz [1..10] arasındaki sayılardan ve aynı zamanda bu aralıktaki ikinin katı olan sayılardan oluşmaktadır.
Gördüğünüz gibi istenilen sonucu alıyoruz. Şimdi bu kapsama bir de koşul ekleyelim. Koşul, bağlayıcı kısımdan sonra gelir ve virgül ile ayrılır. Diyelim ki, yine ikinin katlarını istiyoruz ama bu sefer 12'ye eşit ve 12'den büyük sayıları yazdırsın.
Güzel, çalıştı. Peki ya aralığımızı 50 ile 100 arası yapsak ve 7 ile bölündüğünde 3 kalanını veren sayıları yazdırmak istesek? Vee geliyor,
İşte bu kadar! Listeleri, koşullar ile düzenlemenin filtrelemek anlamına da geldiğini de unutmayalım. Bir sayı listesi aldık ve bunu koşula göre filtreledik.
Şimdi bir başka örnek. Diyelim ki, 10'dan büyük tek sayıları “BANG!” ile ve 10'dan küçükleri ise “BOOM!” ile değiştiren bir kapsam yazmak istiyoruz. Eğer sayı tek değilse onu listeden atsın. Tekrar kullanmak adına kolaylık sağlaması için, bu kapsamı bir fonksiyon olarak yazalım.
Kapsamın son kısmı, koşuldur. odd
fonksiyonu tek değerler için True
değerini, çift değerler içinse false
değerini döndürür. True
olan değerler listeye dahil edilir.
Birçok koşul ekleyebiliriz. Mesela 10 ile 20 arasındaki tüm sayıları yazdırmak istesek ama bunların içinde 13,15 ve 19 olmasa. Şöyle olurdu:
Bir liste kapsamında birçok koşul tanımlayabilmenin yanında, birçok liste de tanımlayabiliriz. Birkaç listeden değer tanımlarken kapsamlar, verilen listelerin tüm kombinasyonunu üretir ve bunları çıkış fonksiyonuna ekler. Uzunluğu 4 olan iki listeden üretilip kapsam tarafından tek bir liste haline gelen listenin uzunluğu 16 olur ve bunu şart kullanmadan elde etmiş oluruz.
[2,5,10] ve [8,10,11] şeklinde iki listemiz olsun ve bu listelerdeki sayıların çarpımlarının tüm olası kombinasyonlarını elde etmek istersek şunu yapabiliriz:
Beklediğimiz gibi yeni listenin uzunluğu 9.
Peki ya 50'den büyük tüm olası çarpımları istersek?
Peki ya isim ve sıfat listesi oluşturup bunları liste kapsamları yoluyla kullansak… güzel bir cümbüş bizi bekliyor.
İşte bu kadar!
Hadi şimdi de length!
fonksiyonunun bize özel halini yazalım ve adı da Length' olsun.
Burada kullandığımız alt çizgi ( _
) , listeden gelecek elemanın bir önemi olmadığı anlamına geliyor. Yani hiç kullanmayacağımız bir değişken ismi tanımlamaktansa _
yazmayı tercih ettik. Yazdığımız length'
fonksiyonu listedeki her bir elemanı 1 ile değiştirir ve sonra bunları toplar. Çıkan bu toplam, listemizin uzunluğu verir.
Ufak bir hatırlatma: karakterler de bir dizi belirttiğinden, liste kapsamları yöntemlerini onların üzerinde de uygulayabiliriz. Mesela bir örnek;
removeNonUppercase
fonksiyonu karakter dizisinde bulunan büyük harfli karakterleri yazdırır.
Şu şekilde:
Buradaki tüm işi koşul bölümü yapıyor. Fonksiyon, girilen karakter dizisinin elemanlarını kontrol ediyor ve her bir karakterin['A'..'Z']
kümesinin içinde olup olmadığını kontrol ediyor eğer bu kümenin bir elemanıysa ekrana yazdırıyor.
Liste içeren listeleri yani iç içe listeleri kullanıyorsanız, liste kapsamlarıyla onlara da müdahale etmek mümkün.
Elimizde bir liste olsun ve bu listede bulunan tek rakamları çıkaralım.
Elimden geldiğince çevirmeye çalıştım. Özellikle List Comprehension’u çevirmekte çok kararsız kaldım. Liste Manipulasyonu olarak yazmayı düşündüm fakat Türkçe literatürde Liste Kapsamı olarak geçiyordu ben de aslına bağlı kalmaya karar verdim. Elbette hatalarım olabilir. Böyle bir durumda yorumda belirtmeniz yeterli.
Yararlandığım kaynaklar: