Merhaba sevgili e-bergi okurları! Bu ay sizlere yine dergimizde adından sıkça söz ettirdiği halde fazla detaylıca anlatılmayan bir konudan, mantık programlamadan ve özel bir mantık programlama dili olan Prolog'dan bahsedeceğim. Aslında mantık programlamada sadece varsayımlarınızı ve 'kurallarınızı' bildirirsiniz. Ardından program sizin ona öğrettiğiniz doğrulara dayanarak temel mantık kurallarının yardımıyla soracağınız sorulara cevap vermeye çalışır. Mantık programlama; doğal dil işleme, yapay zeka uygulamaları, teorem ispatlama, otomatik cevap veren sistemler, uzman sistemler gibi ilgi çekici konularda kullanılmaktadır. Yine bu konulardan bazılarını kullanarak mantık programlamada ne gibi işler yapıldığına dair daha açıklayıcı örnekler vermeye çalışayım.

Yapay zeka uygulaması dediğimizde herhalde ilk akla gelen uygulamalardan biri satranç oynayabilen bir program olacaktır. Prolog benzeri diller bunun gibi oyunlarda yapay zeka yapmaya çok müsaittir; çünkü oyunda hangi hamlelerin yapılabileceğini ve hangi durumlarda oyunun kazanıldığını yazmanız iyi bir yapay zeka yazmış olmanız için neredeyse yeterli olacaktır. Neredeyse dememin sebebi, Prolog'un performans ile ilgili bazı problemleri. Prolog elindeki kurallara göre mümkün olan tüm hamle ve kombinasyonları deneyeceğinden, hele bir de satranç gibi çok sayıda durumun ve farklı olasılıkların olduğu bir oyunda, çözüme ulaşması oldukça uzun sürebilir. Bu süreyi kısaltmak için, üreteceğiniz algoritmaya göre, çeşitli optimizasyonları uygulatmak size kalıyor. Prolog'un gücünün biraz daha teorik olduğu bu örnekten de anlaşılıyor. Teori demişken, teorem ispatlamada nasıl kullanıldığından da biraz bahsedeyim. Matematikte bilinen tüm teoremler ve gerçekler, en başta seçilen varsayımlar yardımıyla türetiliyor ya da bulunuyor; yani varsayımsal olarak bir doğrular kümemiz mevcut. Bir doğrudan diğerine ulaşmak da yine mantığın temel kuralları sayesinde yapılıyor; dolayısıyla Prolog bunu yapmayı zaten biliyor. Bu nedenle "Prolog'a sadece nereden başlayıp nereye gitmek istediğinizi ve bildiklerinizi vermeniz yetiyor." diyeceğim; ama aslında iş, anlattığım kadar kolay değil. Zira ispatlamak istediğiniz teoremleri bildikleriniz türünden yazmanız gerekmekte; ki bu durumda da ispatı siz yapmış olursunuz zaten. :) Bu durumu aşabilmek için aslında yapılan, ulaşmak istediğiniz yeni sonucu tam olarak eskiler türünden değil de, onlara bir şekilde benzeterek yazmaktır.

Az sonra bazı kod örnekleri verdiğimde daha rahat anlaşılacağını düşündüğüm otomatik cevap veren sistemlerde nasıl uygulanabileceğinden de bahsetmek istiyorum. Otomatik cevap veren sistem dediğimizde örnek olarak bir bankanın telefon şubesini düşünebiliriz. Aradığınızda sizi tuşlara bastırtarak yönlendirir ve sonunda ulaşmak istediğiniz noktaya ulaşıp bu konuyla ilgilenen görevlilere bağlanmanıza yardımcı olur. Ancak bu örnek biraz sıkıcı olduğundan ve yeterince geniş bir yelpazeye sahip olmadığından, ben daha ziyade “her şeyi bilen kadın” gibi, aklınızdan geçen bir nesnenin ne olduğunu tahmin etmeye çalışan, eğlence amaçlı yazılmış programların nasıl çalıştığından bahsetmek istiyorum. Tabii ki bu programlarda genelde çok fazla nesne göz önünde bulunduruluyor; 225, yaklaşık olarak 33 milyon farklı nesnenin birçok özellik arasından hangilerine sahip olup hangilerine sahip olmadığı bu program tarafından biliniyor. Program başladığında düşünülen nesnenin, daha önceden çeşitli kategorilere ayrılmış özelliklerden sırayla hangilerine sahip olup olmadığı sorulmaya başlanıyor. Bu sayede sadece evet-hayır soruları olsa bile 25 soru sonra yaklaşık 33 milyon objeden sadece bir tanesi cevapların hepsiyle parelellik gösteriyor. Aslında, sorgulama sayesinde örnek vermek gerekirse: uzun, ağır, renksiz, vb. özelliklerin hepsine birden sahip olan nesne aranıyor; yani tüm özellikler mantıktaki “ve” operatörüyle birleştiriliyor. Bunun sonucunda da sadece aranan özelliklere sahip nesne kalmış oluyor. Bunu Prolog'da yapmak oldukça kolay. Mantık programlama bu uygulamada isteneni tam olarak sağlıyor.

Şimdi Prolog'da bunların nasıl yapıldığına bakalım. Prolog'un internette kolayca bulabileceğiniz ücretsiz birçok dağıtımı mevcut. Denemek isterseniz programı bulmanız çok zor olmayacaktır. Prolog'da girilen her satır ya kural ya da gerçektir (varsayımdır). Kurallar bir baş ve gövdeden oluşur; baş, gövde doğruysa doğrudur. Gerçekler ise kendi başlarına doğrulardır. Aslında gerçekler gövdelerinde 'true' (doğru) yazan kurallar olarak da düşünülebilir. Örnekle belirtmek gerekirse:

insan(ali).

bir gerçektir; ancak:

insan(ali) :- true.

bir kuraldır. Aslında bu kuralın gövdesi şu haliyle her zaman doğru olduğu için gerçek olarak da düşünülebilir. Bu iki satır da aslında Prolog'un nasıl bir girdi beklediğinin somut birer örnekleridir. Bu örneklerde insan(X) bir fonksiyondur. Tabi ki Prolog yapısı ve modeli gereği tüm fonksiyonları kabul etmemektedir. Beklediği fonksiyonların görüntü kümesi sadece doğru ve yanlıştan oluşmaktadır. Bu tür fonksiyonları Türkçe'deki yüklemler olarak düşünebiliriz. Mesela insan(ali). gerçeğini “ali insandır.” şeklinde düşünebiliriz veya insan(ali) :- true. kuralını “ali gövdede belirtilenler(yani 'true') doğruysa insandır” olarak yorumlayabiliriz. Buradan da tahmin edebileceğiniz üzere :- karakterleri “eğer” ifadesine karşılık gelmektedir. Hemen bu noktada Prolog'un 'evet' diye cevap verebileceği sorular sorabiliriz. Gerçeği verdikten sonra gerçeği yazmamız 'evet' veya 'doğru' anlamına gelen cevaplar almamızı sağlar. Tabi bu çok işimize yaramaz. Onun yerine insan(X). şeklinde bilinmeyen içeren bir soru sorarsak, Prolog bize bu sorgunun cevabını doğru yapmaya çalışacak bir biçimde cevap dönecektir. Bu durumda sadece ali'nin insan olduğunu belirttiğimiz için X = ali ve 'doğru' cevaplarını alabiliriz. Eğer daha fazla seçeneğimiz olsaydı ';' (noktalı virgül) kullanarak onları da öğrenebilirdik.

Sonuçta “yüklemler” fonksiyon olduklarından dolayı birden fazla argüman da alabilirler. Örnek olarak 'babası olması' ilişkisini kullanabiliriz.

baba(ali, veli).
baba(ali, fatma).
baba(mehmet, ali).

Gerçeklerine bakacak olursak baba(x, y). dediğimizde "x, y'nin babasıdır" şeklinde ifade ettiğimizi varsaydım. Mesela 'baba' ilişkisini kullanarak yeni bir ilişki, 'dede' ilişkisini tanımlayalım. x'in, y'nin dedesi olabilmesi için y'nin z diye bir babası olmalı ve x de z'nin babası olmalıdır. Prolog'da “ve” demek yerine ',' (virgül) kullanırız (“veya” için ';' kullanılır). Örnek olarak;

dede(X,Y) :- baba(X,Z), baba(Z,Y).

Bu durumda Prolog, eğer iki gerçeği de sağlayan bir Z değeri varsa, kendisi Z'yi yerine koyup size X ve Y'leri dönecektir. Yukarıda da denediğimiz gibi mesela burada "mehmet kimlerin dedesi?" sorusunu sormak için dede(mehmet,X). şeklinde sorgulamamız gerekmektedir. X yerine muhtemelen önce “veli” ve noktalı virgüle basıldıktan sonra sıradaki doğru cevap olan “fatma” atılacaktır. Eğer bir daha noktalı virgüle basarsanız başka bir doğru cevap olmadığından Prolog artık 'yanlış' dönecektir.

Prolog'da listeleri kullanmak da tabi ki mümkün; ancak Prolog listeyi başından itibaren bölmenize izin vermektedir. Başı ve geri kalanı şeklinde ayırabilirsiniz. Ufak bir örnekle açıklamak gerekirse;

listeayir( [Bas|KalanListe], Bas, KalanListe).

gerçeğine ilk argüman olarak bir liste verdiğinizde 2. argüman başı ve 3. argüman devamıysa ancak doğru dönecektir. Mesela listeayir([1, 2, 3], 1,[2, 3]) sorgusuna Prolog'un cevabı 'doğru' olacaktır.

Bu noktada da aslında bu fonksiyona bilinmeyenler verebilirsiniz. Hatta bu sayede listenin başına bir eleman eklemesini sağlayan fonksiyon bile tanımlayabilirsiniz. Enteresan gelse de gördüğünüzde mantıklı gelecektir. Fonksiyonumuzu şu şekilde tanımlayalım:

elemanekle( Eleman, Liste, YeniListe) :- listeayir(YeniListe, Eleman, Liste).

elemanekle'yi alışıldığı anlamında bir fonksiyon olarak kullanmak için ilk iki argümanını verin ve 3. argüman yerine bir bilinmeyen girin. Bu arada Prolog'da bilinmeyenler büyük harfle başlar (neredeyse geri kalan her şey de küçük harfle). Örnek kullanımını gösterecek olursak;

elemanekle(ali, [veli, fatma], X).

sorgusu ancak listeayir(X, ali, [veli, fatma]). doğru döndüğünde doğru dönecektir. Onun doğru dönmesi için ise X'in [ali| veli, fatma] olması gerekmektedir. Prolog bilinmeyen olduğunda daima sorguyu doğru olarak döndürmeye çalışır. Dolayısıyla elemanekle fonksiyonuna verdiğiniz bilinmeyen [ali, veli, fatma] olarak geri döner. Bu arada bütün argümanlara bilinmeyenler verseniz bile Prolog yine de doğru bir cevap dönecektir; ama atanan değerler muhtemelen sizin işinize yaramayacaktır.

Prolog'un en önemli özelliklerinden biri aslında “geri takip” denilen bir yeteneği. Prolog'a bir soru sorduğunuzda Prolog doğru cevabı bulana kadar muhtemel cevaplar üzerinde arama yapar. Basit bir örnek olduğu için bunu az önce yazdığım 'dede' fonksiyonu üzerinde anlatmam daha iyi olacaktır. "mehmet kimlerin dedesi?" diye sorduğumuzda:

dede(mehmet, X) :- baba(mehmet, Z), baba(Z, X).

satırı, dedeyi ifade eden ilk satır (aslında tek satır) olduğu için canlandırılır. Şimdi bu iki 'baba' ilişkisi de virgülle yani “ve” ile bağlandığı için doğru değerini dönmeliler. O zaman öncelikle baba(mehmet,Z). doğru olmalıdır. Bu noktada aslında tüm 'baba' ilişkileri denenir; ancak sadece en sonuncusunda baba mehmet olduğu için o seçilir ve Z'ye 'ali' atanır. Daha sonra ise tıpkı “mehmet”te olduğu gibi baba(ali,X). araması başlar ve ilk 'baba' fonksiyonunun ilk argümanı 'ali'dir yani aranılan argümandır. Dolayısıyla bu ilişki var olduğu için bilinmeyen X'e 'veli' atanır ve ilk cevabımızı, X = 'veli' yi, bulmuş oluruz. Eğer noktalı virgül ile başka bir cevap istersek son bulduğumuz doğru değerinin yanlış olduğunu varsayarız ve aramaya kaldığımız yerden devam ederiz. Hemen sonraki baba ilişkisindeki baba da 'ali' olduğu için bu sefer baba(ali, fatma).'yı doğrumuz olarak seçeriz ve X = 'fatma' deriz. Eğer yine farklı bir doğru aranırsa tekrar bu ilişkinin de o anlık yanlış olduğunu varsayıp kaldığımız yerden aramaya devam ederiz. Ancak ali'nin baba olarak ifade edildiği başka bir kural olmadığından artık baba(ali, X)'i sağlayan başka bir kural olmadığı için bir önceki kuralla yani baba(mehmet, Z)'ye yeni bir Z aramaya başlarız ancak yine başka bir Z olmadığı için artık buradan da geriye döneriz ve artık cevabımızı yanlış olarak sonuçlandırmış oluruz.

Aslında Prolog ile aritmetik işlemler yapmak, girdi okumak, çıktı vermek gibi tüm dillerde mümkün olan bazı temel işlevleri yerine getirmek de mümkün; ancak bunlar dilin saf mantık yapısını bozduğu için bunlardan bu yazıda bahsetmeme çok gerek olmadığını düşünüyorum. Ne de olsa Prolog'u Prolog yapan toplaması, çıkarması, dosya okuması değil; mantık programlama özelliğine sahip olması. İyi günler dilemeden önce Einstein'ın sorusunu duymuş olanlar ve üzerinde uğraşmış olanlar için özellikle hatırlatmak istediğim bir şey var: Einstein'ın sorusunda aslında toplamda 15 temel kural var ve Prolog'ta o soruyu çözdürmek için sadece 16 satır kod yazmak yeterli oluyor. Bir dahaki sayımızda görüşmek üzere, kendinize iyi bakın.