Tablo 7.3: Liste eşleştirme örnekleri
Liste 1 |
Liste 2 |
Değişken eşleştirme |
7.2.4. Listelerin Kullanılması
Listeler gerçek anlamda rekursiv bileşik veri yapıları olduklarından bunların kullanılmaları için rekursiv algoritmaların kullanılması gerekir. Liste işlemesinin en temel yöntemi, listenin son elemanına ulaşıncaya kadar listenin her elemanını incelemektir. Bu tür işlemde kullanılması gereken algoritmalar genelde iki cümleden oluşurlar. Bir cümle, baş ve kuyruk olarak ikiye bölünebilen listeler için, ikincisi ise boş listeler için kullanılır.
Örneğin aşağıdaki programda bir listenin elemanlarını nasıl görüntüleyeceğimizi görelim:
DOMAINS
benim_listem = string*
PREDICATES
benim_listemi_yaz(benim_listem)
CLAUSES
benim_listemi_yaz ([]). /*Liste boş ise yapılacak bir şey yok.*/
benim_listemi_yaz ([Bas|Kuyruk]):-write(Bas), nl,
benim_listemi_yaz (Kuyruk).
GOAL benim_listemi_yaz([“Visual”, “Prolog”, “4.0”]).
Bu programdaki benim_listemi_yaz ([“Visual”, “Prolog”, “4.0”] sorgusuyla Bas=”Visual”, Kuyruk=[“Prolog”,”4.0″] değerlerini alır ve Visual değeri yazılır. Daha sonra benim_listemi_yaz yüklemi rekursiv olduğu için [“Prolog”, “4.0”] kısmı yeniden bölünür. Bu kez Bas=Prolog, Kuyruk=4.0 olur ve Prolog değeri görüntülenir. Rekursiv işlem bir kez daha “4.0” için uygulanır ve bu defa Bas=4.0, Kuyruk=[] olur. Kuyruk kısmı boş liste olduğundan sadece 4.0 görüntülenir. Rekursiv çağrı bu kez boş liste için yapılır, fakat listenin Baş ve Kuyruk kısımlarının eşleşebilecekleri değer olmadığından, program akışındaki benim_listemi_yaz([]) cümlesi çağrılır ve program bir şey yapmadan normal şekilde durur. benim_listemi_yaz ([]) şeklindeki cümle, programın normal bir biçimde durmasını sağlar.
7.2.5. Liste Elemanlarının Sayılması
Bir listenin kaç elemandan oluştuğunu nasıl bulabiliriz? Bunun için kullanılması gereken temel mantık şudur.
Liste boş [] ise, listedeki toplam eleman sayısı 0’dır.
Bunun dışındaki listelerin eleman sayısı 1+ Kuyruk Uzunluğu ile bulanabilir. Prolog’da karşılığı aşağıda verilmiştir.
DOMAINS
liste=integer*
PREDICATES
liste_uzunlugu(liste, integer)
CLAUSES
liste_uzunlugu([], 0).
liste_uzunlugu([_|Kuyruk],Eleman_sayisi):-liste_uzunlugu(Kuyruk, Kuyruk_uzunlugu),
Eleman_sayisi=Kuyruk_uzunlugu+1.
GOAL liste_uzunlugu([1, 2, 3], Eleman_sayisi).
İlk cümledeki [_|Kuyruk] boş olmayan bütün listelerle eşleşebilir. Bizim için önemli olan kısım listenin kuyruk kısmı olduğu için baş kısmı yerine anonim değişken kullanılmıştır.
GOAL liste_uzunlugu([1, 2, 3], Eleman_sayisi).
sorgusu ikinci cümle ile eşleşir ve Kuyruk=[2, 3] olur. Daha sonraki adım Kuyruk uzunluğunu hesaplamaktır. Bu yapıldığı zaman Kuyruk=2 olur. Uzunluk=kuyruk_uzunluğu+1 olduğundan Uzunluk=3 olur.
Liste_uzunlugu yüklemi kendisini çağırarak [2, 3] listesinin uzunluğunu bulur. Bunun için
· Cümledeki kuyruk=[3] değerini alır.
· Kuyruk_uzunlugu=Eleman_sayisi değerini alır.
Her rekursiv cümlenin kendisine ait değişken kümesi olduğundan, cümledeki kuyruk_uzunlugu ve sorgudaki kuyruk_uzunlugu birbirine karışmadığı unutulmamalıdır.
Bu durumda bütün mesele [3] uzunluğunu bulmaktır. Bu 1 olduğu için buna 1 ilave edilirse [2, 3] için toplam uzunluk 2 olur. [3] listesinin uzunluğu için liste_uzunlugu yüklemi kendisin tekrar çağırır. Bu kez [3] listesinin kuyruk uzunluğu Kuyruk=[] olur. Kuyruk uzunluğunu hesaplamak için ise liste_uzunlugu([], Kuyruk_uzunlugu) ilk cümle ile eşleşir ve Kuyruk_uzunlugu=0 olur. Şimdi bilgisayar bu değere, yani 0’a 1 ilave ederek [3]’ün uzunluğunu bulur. Buna 1 ilave ederek [2, 3]’ün uzunluğunu bulur. Nihayet buna da 1 ilave ederek [1, 2, 3] listesinin toplam uzunluğunu bulur.
Şimdi bu işlemlerin tamamını sıralayarak konuyu biraz daha netleştirelim.
Liste_uzunlugu([1, 2, 3], Eleman_sayisi1).
Liste_uzunlugu([2, 3], Eleman_sayisi2).
Liste_uzunlugu([3], Eleman_sayisi3).
Liste_uzunlugu([], 0).
L3=0+1=1
L2=L3+1=2
L1=L2+1=3
7.2. Sondan Rekursiyona Yeniden Bakış
Rekursiv bir çağrı, cümledeki son adım olamayacağı için liste_uzunlugu’nun sondan rekursiv olamayacağı bellidir. Bunu sondan rekursiv yapmanın yolu vardır.
Burada problem olan şey, kuyruk uzunluğu bilinmeden bir listenin toplam uzunluğunun hesaplanamayışıdır. Yani bu probleme bir çözüm bulunabilirse, liste_uzunlugu yüklemini sondan rekursiv yapmak mümkündür. Bunun için liste_uzunlugu yükleminin üç argümanının olması gerekir.
1. Birincisi, her seferinde kırpılarak sonunda boş bir liste elde edilecek listenin kendisi.
2. Bir diğeri, liste uzunluğunu saklayacak boş bir değişken
3. Sonuncusu ise 0 ile başlayan ve her seferinde değerinin 1 arttığı bir sayaç değişken.
Geriye sadece boş olan liste kaldığı zaman bu sayaç hiçbir değişkene atanmamış olan sonucu alır.
DOMAINS
liste=integer*
PREDICATES
liste_uzunlugu(liste, integer, integer)
CLAUSES
liste_uzunlugu([], Sonuc, Sonuc).
liste_uzunlugu([_|Kuyruk], Sonuc, Sayac):-
Yeni_sayac=Sayac+1,
liste_uzunlugu(Kuyruk, Sonuc, Yeni_Sayac).
GOAL liste_uzunlugu([1, 2, 3], Uzunluk, 0), write (“Uzunluk =”, Uzunluk), nl.
Verilen bir listedeki elemanlar üzerinde işlem yaptıktan sonra bu elemanların yerine hesaplanan elemanlardan oluşan başka bir liste oluşturmak mümkündür. Aşağıdaki örnekte listenin her elemanını 1 ilave ederek yeni bir liste elde edilmiştir.
DOMAINS
liste = integer*
PREDICATES
yeni_deger_ilave_et(liste, liste)
CLAUSES
yeni_deger_ilave_et([], []). /* İlk şart*/
yeni_deger_ilave_et([Bas|Kuyruk],[Bas1|Kuyruk1]):- /* Bas ve Kuyruk ayrılması*/
Bas1=Bas+1, /* Listenin ilk elemanına 1 ilave et*/
yeni_deger_ilave_et(Kuyruk, Kuyruk1). /* elemanı listenin geriye kalanıyla çağır*/
GOAL yeni_deger_ilave_et([1, 2, 3], Yeni_Liste).
Yukarıda yapılan işlemler, sözel olarak aşağadaki şekilde yazılır.
Boş bir listenin bütün elemanlarına 1 ilave etmek için sadece başka bir boş liste oluştur.
Boş olmayan herhangi bir listenin bütün elemanlarına 1 ilave etmek için, listenin baş kısmına 1 ilave et ve ilave edilen bu değeri yeni listenin başı olarak al. Daha sonra kuyruk kısmının bütün elemanlarına 1 ilave et ve yeni değerleri de yeni listenin kuyruk kısmı olarak al. Sonucu Yeni_liste olarak ekranda görüntüle.
Verilen liste [1, 2, 3] olduğu için:
1. Önce Baş ve Kuyruk kısımları ayrılır ve sırasıyla [1] ve [2, 3] olurlar.
2. Sonuç listenin baş ve kuyruk kısımlarına Bas1 ve Kuyruk1 değerlerini ata. Burada Bas1 ve Kuyruk1’in henüz değer almadığına dikkat edilmelidir.
3. Bas kısmına 1 ilave et ve Bas1’i elde et.
4. Rekursiv olarak Kuyruk kısmındaki bütün elemanlara 1 ilave et ve Kuyruk1’i elde et.
Bu yapıldığı zaman Bas1 ve Kuyruk1 kendiliğinden sonuç listesinin Bas ve Kuyruk kısmı olur. Bunları birleştirmek için ayrı bir operasyon gerekmez. Dolayısıyla rekursiv çağrı gerçekten de prosedürün son adımı durumundadır.
Örnek:
Bir listedeki sayıları tarayıp negatif olanları eleyen program
DOMAINS
liste=integer*
PREDICATES
negatifleri_ele(liste, liste)
CLAUSES
negatifleri_ele([], []).
negatifleri_ele([Bas|Kuyruk], IslenmisKuyruk):-
Bas<0, !, negatifleri_ele(Kuyruk, IslenmisKuyruk).
negatifleri_ele([Bas|Kuyruk], [Bas|IslenmisKuyruk]):-
negatifleri_ele(Kuyruk, IslenmisKuyruk).
GOAL negatifleri_ele([2, -45, 3, 4, -5, -45], Yeni_Liste).
Aşağıdaki yüklem, bir listenin her elemanını başka bir listeye iki kez aktarmaktadır.
elemanlari_ikile([], []).
elemanlari_ikile([Bas|Kuyruk], [Bas, Bas|Ikilenmis_Kuyruk]):-
elemanlari_ikile(Kuyruk, İkilenmis_Kuyruk).
7.3. Liste Elemanlığı
Ahmet, Mehmet, Hasan ve Nejla isimlerini eleman olarak içeren bir listede, örneğin Ahmet isminin var olup olmadığını öğrenilmek istensin. Yani isim ve bir isim arasında bir ilişki sorgulansın. Bunun için kullanılan bir yüklem vardır.
uye(isim, isimlistesi). /*Burada ‘isim’ listede geçen bir isimdir.*/
DOMAINS
isim_listesi = isim*
isim = symbol
PREDICATES
nondeterm uye(isim, isim_listesi)
CLAUSES
uye(Isim, [Isim|_]).
uye(Isim, [_|Kuyruk]):- uye(Isim, Kuyruk).
GOAL uye(ahmet, [ mehmet, ahmet, hasan, nejla]).
Yukarıdaki örnekte önce birinci cümleyi inceleyelim. uye(Isim, [Isim|_]) cümlesindeki Isim değişkeni listenin öncelikle baş kısmında araştırılır. Eğer eşleşme sağlanırsa üyeliğin var olduğu sonucuna varılır ve olumlu sonuç görüntülenir. Listenin kuyruk kısmı bizi ilgilendirmediği için burada anonim değişken kullanılmıştır.
Eğer aradığımız isim listenin baş kısmı ile eşleşmezse bu kez listenin kuyruk kısmını incelemek için ikinci cümle kullanılır.
7.4. Listeleri Birleştirme
Aşağıdaki iki cümleyi tekrar inceleyelim. Bu iki cümleye prosedürel ve dekleratif olarak bakmak mümkündür.
uye(Isim, [Isim|_]).
uye(Isim, [_|Kuyruk]):- uye(Isim, Kuyruk).
Bu cümlenin dekleratif olarak anlamı şudur:
Eğer cümlenin baş kısmı Isim değişkenine eşitse, bu durumda Isim, listenin bir elemanıdır. Bu durum doğru değilse, Isım değişkeni kuyruk kısmının üyesi ise Isim listenin bir elemanıdır.
Prosedürel olarak bu iki cümle şöyle yorumlanabilir.
Bir listedeki herhangi bir elemanı bulmak için, listenin baş kısmını; aksi takdirde, bu listenin kuyruk kısmının bir üyesini bulunuz.
Bu iki durumu denemek için uye(2, [1, 2, 3, 4]) ve uye[X, [1, 2, 3, 4]) sorgularını kullanınız. İlk sorgu, bir durumun doğru olup olmadığını sorgulamak için kullanılırken, ikinci sorgu listenin bütün üyelerini bulmak için kullanılmaktadır.
7.5. Rekursiyona Prosedürel Bir Bakış
Bu kısımda bir listeyi başka bir listeye ekleyen bir yüklem oluşturulacaktır. Ekle yükleminin üç argümanla birlikte tanımlanması gerekir.
Ekle(Liste1, Liste2, Liste3)
Ekle yüklemi Liste1’i Liste2’ye ilave ederek Liste3’ü elde eder. Eğer Liste1 boş ise, bu durumda 1. Listeyi 2. Listeye ilave etmek bir şeyi değiştirmez. Yani:
Ekle([], Liste2, Liste2).
Eğer liste1 boş değilse,
Ekle([Bas|Kuyruk1], Liste2, [Bas|Kuyruk3]):-ekle (Kuyruk1, Liste2, Kuyruk3]).
Liste1 boş değilse, rekursiv olan yüklem her seferinde bir elemanı Liste3’e transfer eder. Liste1 boş olduğunda ilk cümle Liste2’yi liste3’ün sonuna ilave eder.
Örnek:
DOMAINS
sayilar=integer*
PREDICATES
ekle(sayilar, sayilar, sayilar)
CLAUSES
ekle([], Liste, Liste).
ekle([Bas|Kuyruk1], Liste2, [Bas|Kuyruk3]):-
ekle (Kuyruk1, Liste2, Kuyruk3).
GOAL ekle ([1, 3, 5], [2, 4, 6], Yeni_Liste).
Yukarıdaki programı sadece birleştirilen iki listenin sonucunu almak için değil, aynı zamanda sonuç listesini yazıp ilk iki liste için geçerli bütün alternatifleri bulmak için kullanmak mümkündür. Örneğin GOAL ekle (Birinci_Liste, Ikinci_liste, [2, 4, 5, 6]). Denendiğinde toplam 5 çözüm bulunur. Ayrıca GOAL ekle ([3, Ikinci_eleman],Liste_2, [3, 4, 5, 6]) şeklindeki bir sorgu ile birinci listenin, örneğin ikinci elemanı ve ikinci listenin tamamını bulmak da mümkündür.
7.6. Bütün Çözümleri Bir Defada Bulma
Rekursiyon ve geriye iz sürme işlemlerini karşılaştırırken rekursiyonun daha avantajlı olduğu daha önce belirtilmişti. Bunun nedeni, rekursiyon esnasında argümanlar vasıtasıyla aradaki adımlarda elde edilen verilerin saklanabilmesidir. Öte yandan geriye dönüş işlemi bir sorguyu sağlayan bütün çözümleri bulabilirken, rekursiyon bunu yapamaz.
Bunun için Prolog’un hazır yüklemlerinden olan findall yüklemi kullanılır. Findall bir sorguyu kendi argümanlarından biri olarak alır ve bu sorgunun bütün çözümlerini tek bir liste altında toplar. Findall yükleminin toplam 3 argümanı vardır.
· İlk değişken, örneğin Degisken_Ismi, yüklemden listeye aktarılacak değişkenin hangisi oldugunu gösterir.
· İkinci değişken, örneğin yeni_yuklem, değerlerin alınacağı yüklemi gösterir.
· Üçüncü argüman, örneğin Yeni_Degisken, geriye dönüş işlemiyle elde edilen değerlerin listesi tutan bir değişkendir. Yeni_degisken değerlerinin ait olduğu bir tip tanımının kullanıcı tarafından yapılmış olması lazımdır.
Bir gruptaki yaş ortalamasını bulan bir program, aşağıdaki şekilde yazılabilir.
DOMAINS
isim, adres = string
yas = integer
liste = yas*
PREDICATES
nondeterm kisi(isim, adres, yas)
toplam_liste(liste, yas, integer)
calistir
CLAUSES
toplam_liste([], 0, 0).
toplam_liste([Bas|Kuyruk], Toplam, N):-
toplam_liste(Kuyruk, S1, N1),
Toplam=Bas+S1, N=1+N1.
kisi(“Oktay DUYMAZ”, “Cumhuriyet Cad.”, 36).
kisi(“O.Faruk AKKILIÇ”, “Nail Bey Mah. “, 30).
kisi(“Hakay TAŞDEMİR”, “Firat Cad. No: 17”, 28).
calistir:-
findall(Yas, kisi(_,_, Yas), L),
toplam_liste(L, Toplam, N),
Ortalama=Toplam/N,
write(“Ortalama = “, Ortalama), nl.
GOAL calistir.
Programdaki findall cümlesi L listesini oluşturarak kisi yükleminden elde edilen bütün yaşları buraya aktarır.
7.7. Bileşik Listeler
Şimdiye kadar oluşturulan listelerde daima aynı türden olan elemanlar saklanmıştır. Listeler tamsayı, symbol vs.den oluşuyordu. Bir liste içerisinde farklı tipte elemanları bir arada yazmak oldukça faydalı olur. Birden fazla tipte olan elemanları bir arada tutmak için özel tanımlamaların yapılması gerekir. Bu da farklı operatörler tanımlamakla olur.
Örnek:
Domains.
Benim_listem = 1(liste); i(integer); c(char); s(string)
Liste=benim_listem*
[i(2), i(9), 1([s(“araba”), s(“bilgisayar”)]), s(“kalem”)]
Örnek:
DOMAINS
benim_listem =l(liste); i(integer); c(char); s(string)
liste=benim_listem*
PREDICATES
ekle(liste, liste, liste)
CLAUSES
ekle([], L, L).
ekle([X|L1], L2, [X|L3]):-
ekle(L1, L2, L3).
GOAL
ekle([s(sever), l([s(ahmet), s(deniz)])], [s(ahmet), s(ayse)], Sonuc),
write(“Ilk Liste : “, Sonuc, “\n”),
ekle([l([s(“Bu”), s(“bir”), s(“listedir.”)]), s(test)], [c(‘c’)],Sonuc2),nl,
write (“İkinci Liste: “, Sonuc2,’\n’).
8. AKIŞ DENETİMİ
Bir yüklem içinde değeri bilinen değişkenlere input (giriş değişkenleri), bilinmeyenlere ise output (çıkış değişkenleri) denir. Bu argümanların, input argümanları ise başlangıç değeri verilerek, output argümanları ise çıktı almak üzere uygun biçimde kullanılmasına akış biçimi denir. Örneğin bir argümanın iki değişkenle çağrılması durumunda 4 farklı akış biçiminden söz edilebilir.
(i, i) (i,o) (o,i) (o, o)
Programlar derlendiği zaman yüklemlerin global bir akış analizi yapılır. Ana sorgu ile başlayıp bütün programın değerlendirmesi yapılır. Bu esnada programdaki bütün yüklemlere akış biçimleri atanmış olur.
Akış analizi oldukça basittir. Çünkü program yazarken farkında olmadan aynı şey tarafımazdan da yapılmaktadır.
Örnek:
GOAL cursor(R, C), R1=R+1, cursor(R1, C).
Cursor yüklemine yapılan ilk çağrıda R ve C değişkenlerinin hiçbir değeri olmadığı için serbest değişken durumundadırlar. Dolayısıyla akış biçimi cursor(o, o) olur. R1=R+1 ifadesinde R değişkeninin değeri cursor yükleminden geleceği için, R değişkenin bağlı olduğu bellidir. Bu çağrıdan sonra R1 değişkeni değer almış olur. Eğer R değişkeni boş olsaydı, bu durumda bir hata mesajı görüntülenirdi.
Cursor yükleminin son kez çağrılmasında R1 ve C değişkenlerinin ikisi de önceden çağrıldığı için artık giriş değişkenleri olarak işlem görürler. Yani çağrının akış biçimi cursor(i, i) olur.
Burada sadece DOS Metin Modu ortamında çalışan aşağıdaki örnekler irdenelecektir.
Predicates
ozellik_degistir(Integer, Integer)
Clauses
ozellik_degistir(Yeni_ozellik,Eski_ozellik):-ozellik(Eski_ozellik), ozellik(Yeni_ozellik).
GOAL ozellik_degistir(112, Eski), write(“Merhaba”), ozellik(Eski, write(” millet”).
GOAL kısmındaki ilk çağrı ozellik_degistir(i, o) ile yapılır. Burada 112 bilinen, Eski ise bilinmeyen değişkendir. Bu durumda ozellik_degistir cümlesi Yeni_ozellik değişkeni ile çağrıldığında bunun değeri belli olduğu için input, Eski_ozellik’in değeri belli olmadığı için output olacaktır. Akış denetçisi ilk alt hedef olan ozellik(Eski_ozellik) cümlesine geldiği zaman ozellik yüklemi ozellik(o) akış biçimi ile çağrılır. Ozellik yükleminin ikinci çağrılışı ozellik(i) şeklinde olacaktır. Ana sorgudaki ozellik yüklemine yapılan çağrı input olacaktır, çünkü ozellik_degistir yükleminden alınır.
8.1. Bileşik Akış
Bir yüklemdeki değişken bileşik bir nesne ise, akış biçimi bileşik bir şekilde olabilir. Şimdi aşağıdaki örnekte olduğu gibi, bir ülke hakkında bilgilerin verildiği bir veritabanı düşünelim. Yeni bilgileri rahatlıkla ilave edebilmek için her bilgiyi kendi tipiyle saklamak istenebilir.
DOMAINS
ulke_bilgileri=alan(string, ulong); nufus(string, ulong);baskent(string, string)
PREDICATES
nondeterm ulke(ulke_bilgileri)
CLAUSES
ulke(alan(“Türkiye”,876000)).
ulke(nufus(“Türkiye”, 65000000)).
ulke(baskent(“Türkiye”, “Ankara”)).
ulke(alan(“Almanya”,840000)).
ulke(nufus(“Almanya”, 50000000)).
ulke(baskent(“Almanya”, “Bohn”)).
GOAL ulke(alan(Ad, Alan)), ulke(nufus(Ad, Nuf)).
Sorguyu aşağıdaki cümlelerle deneyiniz:
ulke (C) (o)
ulke(alan(Ulke_adi, Alani)) (o,o)
ulke(nufus(“Türkiye”, Nuf)) (i, o)
ulke(baskent(“Türkiye”, “Ankara”)) (i)
Son örnekteki bütün terimler bilindiği için akış biçim düz metindir.
8.2. Yüklemlerin Akış Biçimlerini Tanımlama
Yüklemler için uygun bir akış biçimi tanımlamak bazen daha güvenlidir. Yüklemlerin sadece özel akış biçimleri durumunda geçerli olacağı biliniyorsa, önceden akış biçimi tanımlamak faydalıdır. Çünkü bu durumda akış denetçisi bu yüklemlerden yanlış kullanılanı çok rahatlıkla bulabilir. Tip tanımı yapıldıktan sonra ‘-‘ işareti yazarak akış biçimi vermek mümkündür.
PREDICATES
musteri_bilgi_listesi(string, string, slist) -(i, o, o)(o, i, o)
8.3. Akış Analizini Kontrol Etmek
Analiz mekanizması, standart bir yüklemin yanlış bir akış biçimi ile çağrıldığını tesbit ettiği an hata mesajı verir. Bu hata mesajı, standart yüklemleri çağıran yüklemler tanımladığımız zaman, bunlardan akış biçimi anlamsız olanları tesbit etmede bize yardımcı olur.
Örnek:
C=A+B
ifadesinde A ve B serbest değişken olduğundan, akış denetçisi bu yüklem için akış biçimi olmadığını bildiren bir hata mesajı verecektir. Bu durumu kontrol etmek için free ve bound standart yüklemleri kullanılır.
İki sayı arasında toplama yapmak veya toplam ile ilk sayısı verilen bir durumda ikinci sayıyı bulan, bütün akış biçimleriyle çağrılabilen topla adında bir yüklem tanımlayalım.
Örnek:
PREDICATES
nondeterm topla(integer, integer, integer)
nondeterm sayi(integer)
CLAUSES
topla(X,Y,Z):-
bound(X),
bound(Y),
Z=X+Y. /* (i,i,o) */
topla(X,Y,Z):-
bound(Y),
bound(Z),
X=Z-Y. /* (o,i,i) */
topla(X,Y,Z):-
bound(X),
bound(Z),
Y=Z-X. /* (i,o,i) */
topla(X,Y,Z):-
free(X),
free(Y),
bound(Z),
sayi(X),
Y=Z-X. /* (o,o,i) */
topla(X,Y,Z):-
free(X),
free(Z),
bound(Y),
sayi(X),
Z=X+Y. /* (o,i,o) */
topla(X,Y,Z):-
free(Y),
free(Z),
bound(X),
sayi(Y),
Z=X+Y. /* (i,o,o) */
topla(X,Y,Z):-
free(X),
free(Y),
free(Z),
sayi(X),
sayi(Y),
Z=X+Y. /* (o,o,o) */
/* 0’dan başlayan sayıları bulma*/
sayi(0).
sayi(X):-
sayi(A),
X = A+1.
GOAL topla(Ilk_sayi,7,10).
8.4. Referans Değişkenler
Akış denetçisi bir cümleyi incelerken, bu cümlenin başındaki bütün çıktı değişkenlerinin cümlenin gövdesinde bağlı olup olmadığını kontrol eter. Bir cümlede bir değişken bağlı değilse, bu değişkenin referans değişkeni olarak işlem görmesi gerekir. Bu karmaşayı gösteren bir örnek aşağıda verilmiştir.
Predicates
p(integer)
Clauses
p(X):-!.
Goal p(V), V=99, write(V).
Sorgudaki p yüklemi çıktı biçiminde çağrılır fakat clauses bölümündeki p yükleminde bulunan X değişkeni bağlı değişken değildir. Akış denetimi sırasında bu fark edildiğinde, değişkenin domains bölümündeki tip tanımına bakılır. Eğer değişken tipi referans olarak tanımlıysa problem çıkmaz. Tanımsızsa uyarı mesajı görüntülenir.
Bir cümledeki bir değişken bağlı değilse, bu durumda cümlenin herhangi bir değer aktarması mümkün değildir. Bunun yerine referans değişkenine bir pointer yollayarak daha sonra bu noktaya gerçek değerin yazılması sağlar. Bu, bu tipteki bazı değişkenlere değer aktarmak yerine, tip tanımının tamamına aynı işlemin yapılmasını gerektirir. Kayıtlara gönderilen pointerlar referans tipe ait argümanlara iletilir. Yani bileşik bir tip referans bir tip haline gelirse, bu durumda bütün alt tiplerin de referans tip olarak işlem görmesi gerekir. Bileşik bir tipin referans tip olarak tanımlanması durumunda, derleyici diğer bütün alt tipleri de referans tip olarak kabul eder.
8.4.1. Referans Tip Tanımı
Akış denetçisi program içerisinde bağımsız bir değişken bulduğunda değişken sadece bir cümleden dönüş sırasında bağımlı değilse uyarı verir. Bu durum sizin için uygunsa, bu tip tanımı otomatik olarak referans tip olarak kabul edilir. Bununla birlikte referans tip olarak tanımlamak istenen bir tipi domains bölümünde net olarak tanımlamak daha mantıklıdır.
8.4.2. Referens Tip ve İzleme Dizileri(array)
Zorlama ve ekstra eşleştirme gerektirdiği için, referans tipler programın çalışma hızında genel bir azalmaya neden olur. Fakat referans tip tanımının neden olduğu problemler daha etkili biçimde kullanılabilir ve bu tip tanımlarının etkileri azaltılabilir.
Referans tipler kullanıldığı zaman, Visual Prolog izleme dizini kullanır. Bu izleme dizini referans değişkenlerin değer aldıkları anı bildirmek için kullanılırlar. Referans bir değişkenin oluşturulması ve değer alması arasındaki herhangi bir noktaya geriye dönüş yapıldığı zaman, bu değişkenin yeniden değer almamış hale getirilmesi gerekir. Fakat bu problem düz değişkenlerle uğraşırken meydana gelmez. Çünkü bunların oluşturulması ve değer alma noktaları aynıdır. İzlemede kaydedilen her bir çağrı 4 byte (32 bit bir pointerin büyüklüğü) kullanır.
Gerektiğinde kuyruk büyüklüğü otomaki olarak arttırılır. İzin verilen maksimum büyüklük 16-bit Visual Prolog için 64K, 32-bit için ise sınırsızdır.
Standart tipleri referans tip olarak kullanmak iyi bir fikir değildir. Çünkü program kullanıcının tanımladığı bu referans tipi, aynı tip için daima geçerliymiş gibi kullanır. Bunun yerine, istenilen temel tip için referans bir tip tanımlamak daha uygundur. Örneğin aşağıdaki program parçasında kullanıcının tanımladığı tamsayi_referans_tipi tamsayılar için referans tiptir. Dolayısıyla tamsayi_referans_tipi her kullanımda referans tip olarak işlem görür. Fakat tamsayı tipindeki değişken referans tip olarak değil, normal olarak integer olarak işlem görür,.
Domains
tamsayi_referans_tipi= reference integer
Predicates
P(tamsayi_referans_tipi)
Clauses
P(_).
8.5. Referans Tip Kullanımı
Referans tip kullanımının en doğru biçimi, sadece gerekli olan birkaç yerde kullanıp geri kalan kısımların tamamında referans olmayan tipi kullanmaktır. Zaten gerekli olan durumlarda referans ve referans olmayan tipler arasında dönüşüm yapmak mümkündür. Şimdi referans olan bir tamsayıyı referans olmayan bir tamsayıya dönüştürelim.
Domains
Referans_tamsayi=reference integer
Predicates
Donustur(referans_tamsayi, tamsayi)
Clauses
Donustur(X, X).
İsmi aynı olan bir değişken referans ve referans olmayan tipte kullanıldığı zaman dönüşüm otomatik olarak yapılır. Yukarıdaki örnekte referans_tamsayi ve tamsayi arasında dönüşüm otomatik olarak yapılır. Referans bir değişkenin, referans olmayan bir değere dönüştürülebilmesi için öncelikle bir değer almış olması gerekir. Yani referans tip olarak tanımlı bir değişkeni dönüştürmek için (örneğin referans tamsayılardan referans karaktere) öncelikle bu değişkenin bir değer almış olduğundan emin olmak gerekir. Aksi takdirde serbest değişken kullanılamaz şeklinde hata mesajı görüntülenir. Referans tip tanımlarının nasıl çalıştığını tam olarak anlamak için aşağıdaki programı değişik sorgularla çalıştırılmalıdır.
DOMAINS
referans_tamsayi = integer
referans_liste= reference referans_tamsayi*
PREDICATES
nondeterm eleman(referans_tamsayi, referans_liste)
ekle(referans_liste, referans_liste, referans_liste)
CLAUSES
eleman(X, [X|_]).
eleman(X, [_|L]):-
eleman(X, L).
ekle([], L, L).
ekle([X|L1], L2, [X|L3]):-
ekle(L1, L2, L3).
GOAL eleman(1, L).
Aşağıdaki sorguları da deneyin
eleman(X, L), X=1. Elemanları arasında 1 olan bütün listeleri bul.
eleman(1, L), eleman(2, L). Elemanları arasında 1 ve 2 olan bütün listeleri bul
X=Y, eleman(X, L),eleman(Y, L), X=3. X ve Y’nin eleman olduğu listeler
eleman(1, L), ekle(L, [2, 3], L1).
ekle(L, L, L1), eleman(1, L). 1’in iki kez eleman olduğu listeler.
8.6. Akış Biçimine Yeni Bir Bakış
Referans bir değişken serbest halde olmasına rağmen, bir yüklem çağrısı içinde çağrıldığı anda mevcut olabilir. Ülkeler hakkındaki programda ayni_baskentler:-ulke(baskent(Kent, Kent), write(Kent, ‘\n’), fail şeklindeki bir sorguyla, başkentleri ülke ismiyle aynı olan bütün ülkeleri bulmak isteyelim. Burada kent değişkeni iki kez çıktı akışı ile kullanılmıştır. Fakat bu sorgu satırının söylediği şey; Kent değişkeni değer aldığı anda ikinci değişken olan Kent’in de aynı değeri alması gerektiğidir. Bu yüzden her iki değişken çağrı yapılmadan önce yaratılıp eşleştirilir. İşte bunu yapabilmek için bunların tipi referans tipe dönüştürülür ve iki değişken çağrı anından itibaren kullanıma girerler.
Standart tip tanımlarının referans tip haline getirmenin çok yanlış bir kullanım olacağı daha önce söylenmişti. Eğer böyle bir şey yapılmak isteniyorsa, uygun bir referans tip tanımlamak daha mantıklıdır.
8.7. İkili (Binary) Ağaç Yapısının Referans Tip İle Kullanımı
Önceki sıralama işlemleri ikili ağaç yapısı ile çözülmüştü. Aynı şeyi referans tip tanımı ile daha güzel biçimde yapmak mümkündür. Ağaç yapısındaki bir dalı, bir değer aldıktan sonra değiştirmek mümkün değildir. Ağaç oluşturulurken pek çok noktanın kopyası oluşturulur. Sıralama işlemini büyük miktarda veri üzerinde yapıldığı düşünülürse, bunun hafıza taşmasına neden olabileceği görülür. Referans bir tip, ağacın dallarını serbest değişken olarak bırakarak, bu durumu düzeltebilir. Bir referans tipi bu şekilde kullanarak yeni bir dalın ilave edileceği noktanın üzerindeki dalı kopyalamaya gerek kalmaz.
Örnek:
Domains
agac = reference t(isim, agac, agac)
isim = string
PREDICATES
araya_ekle(isim, agac)
CLAUSES
araya_ekle(ID, t(ID,_,_)):-!.
araya_ekle(ID, t(ID1, Agac,_)):-
ID<ID1, !, araya_ekle(ID, Agac).
araya_ekle(ID, t(_,_,Agac)):-
araya_ekle(ID, Agac).
GOAL
araya_ekle(“Alper”, Agac),
araya_ekle(“Kasim”, Agac),
araya_ekle(“Paki”, Agac).
İlk araya_ekle(“Alper”, Agac) alt hedefi ilk kural ile eşleşir ve bileşik nesne t(“Alper”,_,_) biçimini alır. T’deki son iki argüman bağlı değişken olmasalar da t diğer alt hedefe iletilir:
araya_ekle(“Kasim”, Agac).
Bu, agac değişkenini t(“Alper”, t(“Kasim”,_,_),_) değerini aldırır. Son olarak son alt hedef araya_ekle(“Paki”, Agac) agac değişkenini t(“Alper”, t(“Kasim”,_, t(“Paki”,_,_)),_) değerine atar ve sonuçta bu değer görüntülenir.
8.8. Referans Tip Kullanarak Sıralama
Daha önce ikili ağaç kullanarak yapılan sıralama örneğini, referans ve referans olmayan tipler arasında ayrım yaparak nasıl yapabileceğini gösteren bir örnek, aşağıda verilmiştir.
DOMAINS
agac=reference t(deger, agac, agac)
deger=integer
liste = integer*
PREDICATES
araya_ekle(integer, agac)
agac_ekle(liste, agac)
nondeterm agacin_elemanlari(integer, agac)
sirala(liste, liste)
CLAUSES
araya_ekle(Deger, t(Deger,_,_)):-!.
araya_ekle(Deger, t(Deger1,Agac,_)):-
Deger<Deger1,!,
araya_ekle(Deger, Agac).
araya_ekle(Deger, t(_,_,Agac)):-
araya_ekle(Deger, Agac).
agac_ekle([],_).
agac_ekle([Bas|Kuyruk],Agac):-
araya_ekle(Bas,Agac), agac_ekle(Kuyruk, Agac).
agacin_elemanlari(_,Kuyruk):-
free(Kuyruk),!, fail.
agacin_elemanlari(X,t(_,L,_)):-
agacin_elemanlari(X, L).
agacin_elemanlari(X,t(RefStr,_,_)):-
X=RefStr.
agacin_elemanlari(X,t(_,_,R)):-
agacin_elemanlari(X, R).
sirala(L,L1):-
agac_ekle(L, Agac),
findall(X, agacin_elemanlari(X,Agac), L1).
GOAL sirala([10,9,17,21,114,5], L), write(“Liste= “, L),nl.
Bu örnekte referans tipler sadece agac değişkeninde kullanılmıştır. Diğer argümanların tamamı referans olmayan tiplerdedir.
8.9. Binary (İkili) Tip
Visual Prolog’da ikili verileri kullanmak için özel tip, ikili terimlerin elemanlarına erişmek için özel yüklemler vardır. İkili terim kullanmanın tek amacı, başka türlü anlamlı bir şekilde gösterilemeyen verileri kullanmak ve bunları saklamaktır. Binary terimleri okuma, bunları dosyaya aktarma işlemi için özel yüklemler vardır.
Binary terimlerin temel amacı mantıksal olmayan nesneler diğer programlama dillerinin içine kolayca yerleştirilmesine imkan tanımaktır. Binary terimler, geriye iz sürme mekanizması çerçevesinde Prologdaki diğer terimlerden farklı davranırlar. Geriye iz sürme mekanizması, binary terimin oluşturulduğu noktadan öncesine giderse, binary terimlerin o ana kadar aldıkları değerleri kaybederler. Geriye dönüş, herhangi bir binary terimin oluşturulduğu noktadan öncesine dönmezse, binary terimdeki değişikliklere bir şey olmaz.
8.9.1. Binary Terimlerin Kullanılması
Bir binary terim byte biçiminde bir dizi ve bu dizinin büyüklüğünü saklayan word (16 bit) veya dword(32 bit ortam) değişkenden oluşur.
Diğer dillerden çağrı yapıldığında, binary tipindeki bir terim (diğer bir dildeki fonksiyonun çağrısayla aktarılan değişken) gerçek içeriğe işaret eder. Buradaki büyüklük alanı, alanın kendisinin sahip olduğu büyüklüktür. Binary terimler 16 bit platformlarda genelde 64K ile sınırlıdır.
8.9.2. Binary Terimlerin Yazım Biçimi
Binary terimler okunup metin biçimde yazılabilir. Visual Prolog’da program satırları gibi yazılabilir. Yazılım biçimi:
$[b1, b2, ….., bn]
Burada b1, b2 vs. ilgili terimin byte olarak yazılmış halidir. Program akışı içerisinde yazıldığı zaman, buradaki her byte uygun bir pozitif biçimde desimal, hegzadesimal, oktal veya karakter olarak yazılabilir. Karakter olarak yazılan byte, program çalışırken daima hegzadesimal biçime dönüştürülür ve “0x” kısımları bulunmaz.
Örnek:
GOAL write(“Binary terimi metin biçiminde yaz: “, $[‘C’, 12, 15, 0x14, ‘e’, 5], ‘\n’).
8.9.3. Binary Terimlerin Oluşturulması
Binary terim oluşturmak için Visual Prolog’da mevcut olan yüklemler sırayla aşağıda verilmiştir.
8.9.3.1. makebinary(1)
makebinary, tanımlanan byte sayısında bir binary terim oluşturur ve içeriğini binary 0 olarak ayarlar.
…., Bin=makebinary(10), ….
Burada byte sayısı, alan büyüklüğü hariç, net büyüklük olmalıdır.
İki argüman alabilen biçiminde eleman büyüklüğü tanımlanabilir.
…., Usize=sizeof(unsigned), Bin=makebinary(10, Usize), ….
Bu yüklem, terim büyüklüğü eleman sayısının eleman büyüklüğüyle çarpımıyla belirtilen bir binary terim oluşturur. Örnekte eleman sayısı 10, eleman büyüklüğü ise sizeof(unsigned) olarak tanımlıdır. Terim içeriği 0 olur.
8.9.3.3. composebinary(2)
Mevcut olan bir pointer ve bir uzunluktan bir binary terim oluşturur. Kullanım biçimi:
…, Bin=composebinary(StringVar, Buyukluk), ….
8.9.3.4. getbinarysize(1)
getbinarysize, verinin önündeki alan büyüklüğünü hariç tutarak, bir binary terimin net büyüklüğünü byte türünden verir.
…, Buyukluk=getbinary(Bin), …
8.9.4. Binary Terimlere Erişim
4’ü giriş, diğer 4’ü de çıkış elde etmek için kullanılan 8 yüklem binary terimlere erişmek için kullanılabilir. Bunların hepsi binary terim büyüklüğü, tanımlı indeks ve istenilen maddenin büyüklüğüne (byte, word, dword veya real) bağlı olarak bu değişkenlerin doğru aralıklarda olup olmadıklarını kontrol eder. Bu yüzden bu girişleri, ikili terimlerin tanımlı sınırları dışında tanımlamak ve çağırmak hataya neden olur.
İndislerin (eleman numaraları) 0’a göre değişir. Yani binary bir terimin ilk elemanının indisi 0, en son elemanın da N-1 olur.
8.9.4.1. getentry(2)
getentry yüklemi getbyteentry, getwordentry, getdwordentry veya getrealentry biçimlerinden biri olur ve sırasıyla byte, word, dword veya real tiplerinde giriş yapıp, giriş alabilir.
Deger= getbyteentry(Bin, 3), ….
8.9.4.2. setentry(3)
getentry’ye karşılık gelen bir yüklem olup byte, word, dword eya real girişleri yapar.
…., setbyteentry(Bin, 3, Baytlar), ….
8.9.5. Binary Terimleri Eşleştirme
Binary terimler de diğer terimler gibi eşleştirilebilir. Kullanım biçimi:
…, Bin1=Bin2, ….
Eşleştirme anında terimlerden biri serbest halde ise, birbirine eşitlenirler. Her ikisi de bağlı ise, bu durumda binary terimlerin birbirine eşit olup olmadıkları kontrol edilir.
8.9.6. Binary Terimleri Karşılaştırma
Binary iki terim eşleştirilirken şu sonuçlara dikkat edilir:
Eğer büyüklükleri farklı ise, büyük olan büyük kabul edilir, değilse byte bazında karşılaştırılırlar. İki farklı byte bulunduğu anda karşılaştırma durur, sonuç toplam terimin karşılaştırılması olarak gönderilir. Örneğin $[1, 2] $[100]’den daha büyüktür fakat $[1,3]’den daha küçüktür.
Binary terimlerin bazı özelliklerini gösteren bir program aşağıda verilmiştir.
PREDICATES
binary_karsilastir_ve_eslestir
binary_karsilastir(binary, binary)
al(binary)
CLAUSES
binary_karsilastir_ve_eslestir:-
Bin=makebinary(5),
binary_karsilastir(Bin, _),
binary_karsilastir($[1,2], $[100]),
binary_karsilastir($[0], Bin),
binary_karsilastir($[1, 2, 3], $[1, 2, 4]).
binary_karsilastir(B,B):-!,
write(B, ” = “, B, ‘\n’).
binary_karsilastir(B1, B2):-
B1>B2, !,
write(B1, ” > “, B2, ‘\n’).
binary_karsilastir(B1, B2):-
B1<B2, !,
write(B1, ” < “, B2, ‘\n’).
al(Bin):-
setwordentry(Bin, 3, 255), fail.
al(Bin):-
Buyukluk=getbinarysize(Bin), X=getwordentry(Bin, 3),
write(“\nBuyukluk= “, Buyukluk, “X = “, X, ” Binary= “, Bin, ‘\n’).
GOAL
binary_karsilastir_ve_eslestir, % Binary kaşılaştırma ve eşleştirme için
KelimeBuyuklugu=sizeof(word), Bin=makebinary(4,KelimeBuyuklugu), al(Bin),
write(“Run-time hataları yanlış indeksten kaynaklanıyor:\n”), Indeks=4,
trap(Setwordentry(Bin, Indeks, 0), E,
write(Bin, “teriminin kelime indeksi”, Indeks, ” olustururken “, E, ” hatasi olustu”, ‘\n’)).
8.9.7. Terimleri Binary Terimlere Dönüştürme
Bileşik bir terimin argümanları belleğin değişik yerlerinde olabilir. Basit tipleri doğrudan kayıtlı terimde tutulurken karmaşık olanlar (pointer ile erişilenler ve global bir stack’da olanlar) içindeki göründükleri terimin yakınlarında olmayabilir. Böyle bir terimi programdan dışarıya yollamak zor olur. Çünkü terimin bütün içeriğinin kopyasını almanın açık bir yolu yoktur. Dolayısıyla bir terimdeki değişkeni başka bir değişkenle eşleştirirken sadece bu terime giden pointerin bir kopyası alınmış olur.
term_str yüklemini kullanarak, bir terimi diziye ve diziden tekrar terime dönüştürmek mümkündür. Bu ise, sadece terimi içeriğini kopyalamak için gerektiğinde, oldukça yetersiz kalır. İşte bu problemi term_bin yüklemini çözer.
8.9.7.1. term_bin(3)
term_bin, herhangi bir tipteki terim ve binary veri bloku arasında dönüşümü, terim içeriği ve pointer sabitle bilgisini tutarak, yapar. Pointerin sabitleme bilgisi, binary veriye dönüştürülmüş terimi yeniden diziye dönüştürmek için kullanılır. Kullanımı şöyledir:
term_bin(tip, Terim, Bin) /* (i,i,o), (i,_,i) */
Tip, Terimin ait olduğu tip, Bin ise Terimin içeriğini tutan binary terimdir.
8.10. Hatalar ve İstisnalarla Uğraşma
Kaliteli yazılımlar geliştikçe, güvenilir programlar üretmek için hataların bulunması ve tek tek ayıklanması da önemli hale gelmektedir. Visual Prolog, program çalışırken meydana gelen hataları kontrol etmek için standart yüklemlere sahiptir. Programın işleyişi esnasında meydana gelebilecek bütün hatalar DOS ortamıda PROLOG.ERR, UNIX ortamında PDCProlog.err dosyasında saklanır. Hata mesajı numarası 10000 ve yukarısı kullanıcının programında kullanması için exit kodları olarak ayrılmıştır.
Hata ve istisna durumlarıyla uğraşmak için Visual Prolog’da temel olarak trap yüklemi kullanılır. Bu yüklem, run-time hatalarını ve exit yüklemiyle harekete geçirilen istisna durumları yakalayabilir. Bu yüklem kullanarak, örneğin Ctr+Break tuşlarına basılıp basılmadığını da kontrol edilebilir.
8.10.1. exit(0), exit(1)
exit yüklemine yapılan bir çağrı, run-time hataya eşdeğerdir. Kullanım biçimleri:
exit ve exit(CikisKodu)
Argüman kullanmadan exit yüklemini kullanmak, exit(0) gibi çalışır. exit’e giden çağrı trap yükleminde doğrudan veya dolaylı olarak çalıştırılırsa, CikisKodu trap yüklemine geçer.
8.10.2. trap(3)
Üç argüman alan bu yüklem hata yakalama ve istisna yönetimini gerçekleştirir. İlk ve son argümanlar yüklem çağırma, ikinci argüman ise değişkendir. Kullanım biçimi:
trap(YuklemCagirma, CikisKodu, HataDurumundaCagrilacakYuklem)
Örneğin trap(islem(P1,P2,P3), CikisKodu, hata(CikisKodu,P1)),…. şeklindeki bir çağrı göz önüne alınırsa, işlem yüklemi çağrılırken hata oluşursa CikisKodu ilgili hatayı verirken hata yönetme yüklemi olarak tanımladığımız hata yüklemi çağrılmış olur. Hata yükleminden dönüşte ise trap yüklemi başarısız olur.
Metin modu ortamında BREAK açıkken BREAK yapılırsa, yani Ctrl+Break tuşlarına basılırsa, trap yüklemi bunu yakalar ve CikisKodu değerini olarak 0’ı görüntüler.
Örnek:
Açılmamış olan bir dosyadan dolayı hata mesajı yakalayan program aşağıda verilmiştir.
include “c:\\vip\\include\\error.con”
DOMAINS
file = giris_dosyasi
PREDICATES
hata_yakalama(integer, file)
satir_al(file, string)
CLAUSES
hata_yakalama(err_notopen, Dosya):-!,
write(Dosya, ” isimli dosya açık değil\n”),
exit(1).
hata_yakalama(Hata, Dosya):-!,
write(Dosya, “adli dosyada”,Hata, ” hatasi “, ‘\n’),
exit(1).
satir_al(Dosya, Satir):-
readdevice(Eski),
readdevice(Dosya),
readln(Satir),
readdevice(Eski).
GOAL trap(satir_al(giris_dosyasi, Ilk), Hata, hata_yakalama(Hata, giris_dosyasi)), write(Ilk).
8.10.3. errormsg(4)
errormsg yüklemi, Visual Prolog hata mesajları dosyasıyla aynı biçimde olan dosyalara erişmek için kullanılabilir. Kullanım biçimi:
errormsg(DosyaAdi,HataNo,HataMesaji,YardimMesaji) / (i, i, o, o)/
Bu işlem için aşağıdaki program örneği kullanılabilir:
PREDICATES
hata(integer)
ana_kisim
/* ………. */
CLAUSES
hata(0):-!. % Ctrl+Break tuşuna basılınca bir şey yapma
hata(H):-
errormsg(“prolog.err”, H, HataMesaji, _),
write(“\nSayin Kullanici, Programınızda”, H, “nolu “, ErrorMsg, “hatasi meydana geldi”),
write(“\nDosyanizi”, hata.txt,” dosyasina yaziyorum”),
save(“hata.txt”).
GOAL trap(ana_kisim, CikisKodu, hata(CikisKodu)).
8.10.4. Hataların Bildirilmesi
Visual Prolog’daki bazı derleyici direktiflerini kullanarak, programlarınız içindeki run-time hataları kontrol edilebilir. Bu direktifler şu amaçlarla kullanılır:
· Tamsayı taşma(overflow) hatalarını kontrol edip etmeme durumu
· run-time hataları hangi ayrıntı düzeyinde verileceği
· Yığın taşması(Stack overflow) kontrolünü yapma
Bu direktifler programın baş tarafında verilebileceği gibi, VIP çalıştırıldıktan sonra Compiler Options seçeneğiyle de verilebilir.
8.11. Hata Düzeyi
Visual Prologun, run-time hatanın oluştuğu yeri göstermeye yarayan çok güzel bir mekanizması vardır. Meydana gelen hatanın hangi düzeyde bildirileceğini, hatanın meydana geldiği yeri, derleyicinin errorlevel direktifi belirler. Kullanım biçimi şöyledir:
errorlevel=n
n’nin alabileceği değerler 0, 1 veya 2 olabilir. Bu değerlere göre derleyicinin sunacağı hata düzeyi şunları kapsar:
0 Bu durumda en küçük ve en etkin hata mesajı verilir. Fakat hatanın yeri kaydedilmez, sadece hatanın numarası verilir.
1 Default olarak kabul edilen düzey budur. Hata meydana geldiğinde hatanın meydana geldiği nokta, dosyanın başlangıcından itibaren byte türünden gösterilir.
2 Bu düzey seçildiğinde, 1 durumunda gösterilmeyen stack overflow, heap overflow, trail oveflow vs. gibi hataları görülebilir. Burada da hatanın meydana geldiği yer bildirilir.
Proje bazında çalışırken hata mesajı düzeylerini ayarlarken dikkatli olmak gerekir. Bir projede birden fazla dosya bulunacağı için, birbirine bağlı olan dosyalardaki hata düzeyleri farklı biçimde ayarlanırsa, errorlevel=0 olan bir alt dosyada hata meydana gelirse ve ana dosyada errorlevel=1 veya 2 olursa, bu durumda hatanın meydana geldiği yeri tam olarak bulmak imkansız olur.
8.11.1. lasterror(4)
lasterror, en son olan hatayla ilgili bütün bilgiyi verir. Kullanım biçimi:
lasterror(HataNo,Modul,IncDosyasi,HataYeri)
Burada Modul, hatanın meydana geldiği dosya adı, IncDosyasi ise include dosyasıdır. Bellek taşması hatalarının doğru olarak alabilmek için programın errorlevel=1 olarak derlenmesi gerekir. Sadece basit hataların mesajları alınması isteniyorsa errorlevel=1 yeterlidir.
8.11.2. Terim Okuyucudan Gelen Hataları Görme
consult veya readterm yüklemleri çağrıldığında okunacak satırda bir hata var ise, bu yüklemlerin hata mesajı vererek duracakları bilinmektedir. Hata nedenleri ise okunacak satırın uygun biçimde hazırlanmamış olmamasıdır.
Örneğin:
· Bir diziyi sonlandırmamak
· Symbol türünde bir karakter yerine integer bir karakter yazmak
· Yüklem adı için büyük harfler kullanmak
· Sabit değerleri “ “ içinde yazmamak vs.
readtermerror ve consulterror yüklemlerini kullanarak readterm veya consult yüklemlerinin okumaya çalıştığı dosyalarda ne tür hatalar meydana geldiğini kontrol edebililir.
consult ve readterm yüklemlerinden gelen hatalar trap yüklemi tarafından yakalanırsa, consulterror ve readtermerror yüklemlerini kullanarak hatanın nedenini görmek ve yazmak mümkündür.
8.11.3. consulterror(3)
consulterror, hatalı yazımın bulunduğu satırdaki hata hakkında bilgi verir. Kullanım biçimi
consulterror(Satir, HataYeri, DosyadakiYeri)
Satir, hatanın bulunduğu satır, HataYeri hatanın bulunduğu nokta, 3. parametre ise hatalı satırın bulunduğu yer verilir.
Örnek:
CONSTANTS
yardim_dosyasi=”prolog.hlp”
hata_dosyasi=”prolog.err”
DOMAINS
dom = a(integer)
liste= integer*
DATABASE – firat_dba
p1(integer, string, char, real, dom, liste)
PREDICATES
consult_hatalarini_ayiklama(string, integer)
CLAUSES
consult_hatalarini_ayiklama(Dosya, Hata):-
Hata>1400, Hata<1410, !,
retractall(_, firat_dba),
consulterror(Satir, Hatanin_Yeri,_),
errormsg(hata_dosayasi, Hata, Mesaj, _),
str_len(Bosluklar, Hatanin_Yeri),
write(Dosya, ” dosyasinin “, Satir, ” satırında “, Bosluklar, Mesaj, “meydana gelmistir”),
exit(1).
consult_hatalarini_ayiklama(Dosya, Hata):-
errormsg(hata_doyasi, Hata, Mesaj, _),
write(Dosya, ” dosyasi açılmaya çalışırken “, Mesaj, ” hatası oluştu”),
exit(2).
GOAL Dosya=”test.dba”,
trap(consult(Dosya, firat_dba), Hata, consult_hatalarini_ayiklama(Dosya, Hata)),
write(“\n Tamam \n”).
8.11.4. readtermerror(2)
readtermerror, readterm yükleminin okuduğu satırdaki hatayı verir. Kullanım biçimi:
readtermerror(Satir, HataYeri)
8.12. Break Kontrolü (Sadece Metin Modunda)
Visual Prolog’daki break mekanizmasının nasıl çalıştığına bakalım. Genelde, break için gerekli komutlar o anda çalışan programı hemen durdurmazlar. Prolog’da, istisnai durumları yöneten bir birim vardır. Sinyal ile aktif edilen bu parça bir flag yerleştirir. Visual Prolog bu flagı iki farklı durumda kontrol eder.
· Yazılan program, break-kontrolü açık halde derlenirse, her yüklem girildiğinde break-flag durumu kontrol edilir. Break-kontrol seçeneği, VIP seçeneklerinden iptal uygun direktif seçilerek (Options/Compiler Directives/Run-time check) iptal edilebilir.
· Library rutinlerinin bazıları break-flag kontrolü yaparlar.
8.12.1. break(1)
break, bir program çalışırken break-flag kontrolünün yapılıp yapılmayacağını belirtir. Kullanım biçimleri şöyledir.
break(on), break(off)
break(BreakDurumu)
DOS tabanlı pgogramlar için break komutundan kaynaklanan çıkış kodları daima 0 olur.
8.12.2. breakpressed(1)
break-flag kurulmuşsa, break(off) olsa veya program nobreak seçeneğiyle derlense bile, breakpressed yüklemi başarılı olur. Başarılı olunca, yakalanan en son sinyale göre bir çıkış kodu verir ve break-flagı siler.
8.13. DOS Metin Modunda Kritik Hata Kontrolü
Bu bölüm sadece DOS metin modu ortamında geçerlidir. Dolayısıyla VPI programlarına uygulanamaz.
Visual Prolog’un DOS versiyonu hata durumlarıyla ilgilenen bazı rutinler içerir. Bir DOS hatası olduğu zaman DOS, criticalerror rutinini çağrır. Visual Prolog’daki sistem ise, run-time editörü de bir dosya hatası bulduğunda fileerror yüklemini çağırır. Bu yüklemler global olarak tanımlanır ve kendinize ait cümlecikler kullanırsanız, library’deki rutinler yerine size ait rutinleri programa bağlar. Dolayısıyla hataları daha iyi kontrol etmek mümkündür. Bu durumda .EXE programlarının büyüklüğü büyük oranda azalır. criticalerror ve fileerror için global deklerasyon include dosyasında error.pre içinde hazır olarak bulunmaktadır.
8.13.1. criticalerror(4)
Visual Prolog, bu rutini DOS kritik hatalarıyla uğraşmak için tanımlar. criticalerror yüklemi kullanılmak istenirse, ERROR.PRE dosyası programa dahil edilmelidir. Tanımlanması şöyledir:
global predicates
criticalerror(HataNo, HataTuru, DiskNo, Eylem)
criticalerror yüklemi daima başarılı olmalıdır. Bu yüklem sadece .EXE dosyasından çalışır ve DOS kritik hata interrupt tutucusuyla yer değiştirir. Aşağıdaki tabloda criticalerror yükleminin argümanlarının aldığı değerler verilmiştir.
Tablo 8.4. Criticalerror yükleminin argümanlarının aldığı değerler
Argüman |
Değer |
Anlamı |
HataNo |
= 0 = 1 = 2 = 3 = 4 = 5 = 6 = 7 = 8 = 9 = 10 = 11
|
Yazma korumalı diskete yazma teşebbüsü Bilinmeyen ünite Sürücü hazır değil Bilinmeyen komut Veri içinde CRC hatası Yanlış sürücü isteği yapı uzunluğu Arama hatası Bilinmeyen medya türü Sektör bulunamadı = 12 Yazıcıda kağıt bitmiş Yazma hatası Okuma hatası Genel hata |
HataTürü |
= 0 = 1 = 2 |
Karakter araç hatası Disk okuma hatası Disk yazma hatası |
DiskNo |
= 0-25 |
A-Z’ye kadar sürücü |
Eylem |
= 0 = 1 = 2 |
Çalışmayı durdur İşlemi yeniden dene İşlemi iptal et (Tehlikeli olabilir ve tavsiye edilmez) |
8.13.2. fileerror(2)
Metin modundaki bir dosya eylemi başarısız olunca fileerror yüklemi harekete geçirilir. Kendinize ait fileerror yüklemini tanımlarsanız, bu yüklemin başarısız olmasına izin verilmez ve bu yüklem sadece .EXE uygulamalarından çalışır. fileerror’un ERROR.PRE dosyasındaki tanımlanması şöyledir:
global predicates
fileerror(integer, string) – (i, i) language c as “_MNU_FileError”
Bu tanım tipi doğrudur. Kaynak kod Prolog’da olsa bile language c mutlaka belirtilmelidir.
8.14. Dinamik Cut
Prolog’daki cut statiktir. Geleneksel cut işleminde, program akışı ancak ! sembolüne geldiği zaman cut devreye girer ve sadece içinde bulunulan cümleleri etkiler. Dolayısıyla bir cut komutunun etkisini başka bir yükleme aktarmak mümkün değildir. Normal cut komutunun diğer bir dezavantajı da; yüklemde cut komutunu takip eden cümlelerdeki geriye dönüş noktalarını ortadan kaldırmadan, bir alt hedefteki diğer çözümleri arama ihtimalini ortadan kaldırmanın mümkün olmamasıdır.
Visual Prolog’da dinamik kesme mekanizmasında getbacktrack ve cutbacktrack yüklemleri kullanılır. Bu mekanizma sayesinde bu iki dezavantaj ortadan kalkmış olur. getbacktrack, geriye iz sürme noktalarının yığını içerisinde en üstteki pointer’i verir. Bu noktanın üstünde kalan bütün geriye dönüş noktaları silinebilir.
Bu iki yüklemin kullanımı hakkındaki örnekler, aşağıda verilmiştir.
1. Elimizdeki veritabanında sahislarin isim ve aylık gelirleri var. Bu sahısların arkadaşlarını kaydetmiş durumdayız.
database
sahis(symbol, income)
arkadas(symbol, symbol)
Arkadaşı olan veya az bir vergi ödeyen insanların listesini görmek için aşağıdaki cümlecikleri kullanabiliriz:
sansli_insanlar(aradasi_var(P)):-sahis(P,_), arkadas(P,_).
sansli_insanlar(zengindir(P)):-sahis(P, AylikGelir), not(zengin(AylikGelir)).
Bir şahsın birden fazla arkadaşı varsa, ilk cümlecik birden fazla çözüm getirir. Bu arada dinamik cut kullanabiliriz.
sahsli_insanlar(arkadasi_var(P)):-
sahis(P,_), getbacktrack(BTOP), arkadas(P,_), cutbacktrack(BTOP).
Geriye dönüş mekanizması yapılırsa, arkadaş yüklemi birden fazla çözüm sunabilir. Fakat cutbacktrack yüklemi çağrılarak bu ihtimal ortadan kaldırılır.
Dinamik cut kullanmanın daha önemli avantajı, geriye dönüş pointerini başka bir yükleme geçirmek ve cut komutunu şartlı olarak çalıştırmaktır. Pointer pozitif tipte olup yine pozitif tipteki değişkenlere aktarılabilir.
Örnek:
Bir tuşa basıncaya kadar ekrandan girilen rakamları okuyan bir program, aşağıda sunulmuştur.
PREDICATES
sayi(integer)
sayilari_yaz(integer)
kullanici_komutu(unsigned)
CLAUSES
sayi(0).
sayi(N):-sayi(N1), N=N1+1.
sayilari_yaz(N):- getbacktrack(BTOP), sayi(N), kullanici_komutu(BTOP).
kullanici_komutu(BTOP):- keypressed, cutbacktrack(BTOP).
kullanici_komutu(_).
Derleyici, cümleleri determinism bakımından kontrol eden yüklemdeki cutbacktrack yüklemini tanımaz. Yani check_term direktifini kullanırken non-deterministic cümle uyarısı alınabilir. Dinamik kesme kullanırken çok dikkatli olmak gerekir. Çünkü programın tamamını tahrip etmek, programı çalışamaz hale getirmek mümkündür.
8.15. Programlama Stilleri
Visual Prolog’da program yazarken dikkat edilmesinde fayda olan bazı temel özellikler vardır. Bu özellikler artık kural haline gelmiştir. Etkili programlama yapabilmek için şu kurallara dikkat edilmelidir:
Kural 1. Fazla yüklem yerine daha fazla değişken kullanın
Bu kural programın okunabilirliği ile ters orantılıdır. Genelde Prolog’un dekleratif olan stili, diğer konvansiyonel yaklaşımlara göre daha az verimlidir. Örneğin, bir listenin elemanlarını tersine çeviren bir yüklem yazmak için aşağıdaki program parçası kullanılabilir:
tersine_cevir(A, B):- tersine_cevir1([], A, B).
tersine_cevir1(B, [], B).
tersine_cevir1(A1, [U|A2], B):- tersine_cevir1([U|A1], A2, B).
Kural 2. Çözüm olmadığı zaman program çalışmasının etkili bir biçimde bittiğinden emin olun
Yazdığımız bir maksimum_deger yüklemiyle bir listedeki tamsayıların büyük bir sayıya kadar düzenli olarak arttığını, daha sonra yine düzenli bir şekilde azaldığını kontrol etmek istiyoruz. Bu yüklemi kullanarak
maksimum_deger([1, 2, 5, 7, 11, 8, 6, 4]) şeklindeki bir sorgu başarılı olur. Fakat
maksimum_deger([1, 2, 3, 9, 6, 8, 5, 4, 3]) başarısız olur.
Kural 3. Geriye İz Sürme mekanizmasını mümkün olduğunca çok kullanın.
esitlik seklinde tanımlanan bir yüklemin iki listenin elemanlarını karşılaştırmak için kullanalım. Bunun için:
esitlik([], []).
esitlik([U|X], [U|Y]):- esitlik(X, Y)
kullanmak gereksizdir. Çünkü bunun yerine daha basit olan esitlik(X, X) kullanılabilir.
Kural 4. Rekursiyon veya Tekrarlama Yerine Geriye İz Sürme Mekanizmasını Kullanın
Geriye İz Sürme mekanizması bellek ihtiyacını azaltır. Bu yüzden rekursiyon yerine repeat-fail kombinasyonunu kullanmak gerekir. Bu konu hakkında birkaç örnek verelim.
Alt hedefleri tekrarlı bir şekilde kullanmak için genelde basla gibi bir yüklem tanımlayıp sonuçları hesaplamak gerekir.
Örnek:
basla:-
readln(X), islem_yap(X, Y), write(Y), basla.
Bu tür bir tanımlama gereksiz yere rekursiyon yapar. islem_yap(X,Y) non-deterministic ise sistem tarafından gereksiz olan bu rekursiyon otomatik olarak ortadan kaldırılabilir.
Bu durumda repeat…fail ikilisi kullanmak gerekir. Bunun için yukarıdaki işlemleri şöyle yazmak mümkündür.
basla:-
tekrar, readln(X), islem_yap(X, Y), write(Y), fail.
fail komutu, islem_yap yükleminine geriye dönüş yapar, buradan da tekrar yüklemine geçer, bu yüklem daima başarılı olur. Burada önemli olan şey bu döngünün dışına çıkmaktır. Genelde döngü dışına çıkmak için belli şartlar aranıldığı için, döngü içine bu şartı yazmak mümkündür. Yani:
basla:-
tekrar, readln(X), islem_yap(X, Y), write(Y), bitisi_kontrol_et(Y), !.
8.15. Cut Yüklemini Yerleştirmek
check_determ direktifi, cut yükleminin nereye yerleştirilmesi gerektiği konusunda karar verirken çok faydalıdır. Non-deterministic davranışı olan cümlecikleri deterministic hale getirip geriye dönüşü engellemek için cut kullanılmadır.
Böyle bir durumda genel bir kural olarak şunu söyleyebiliriz: cut komutunu, programın mantığını bozmayacak şekilde, mümkün olduğuca bir kuralın başına yakın bir yere konulmalıdır. Derleyici aşağıdaki iki kuralı dikate alarak bir cümleciğin deterministic veya non-deterministic olduğuna ;
2. Cümlede cut yoksa ve cümlenin başındaki argümanlarla eşleşebilecek başka bir cümlecik varsa
3. Cümle gövdesinde non-deterministic başka bir yükleme çağrı varsa ve non-deterministic olan bu çağrıdan sonra cut yoksa bulunarak karar verilir.
9. ÖZEL GELİŞTİRİLMİŞ PROLOG ÖRNEKLERİ
9.1. Küçük bir Uzman Sistem örneği
Bu örnekte, bazı özellikleri hakkında kullanıcıya soru sorarak 7 hayvandan biri bulunacaktır. Bu hayvanlar hakkında bilinen olgulardan hareketle geriye dönüş mekanizması kullanılarak doğru cevap bulunmaya çalışılacaktır.
DATABASE
xpositif(symbol,symbol)
xnegatif(symbol,symbol)
PREDICATES
nondeterm aranan_canli(symbol)
nondeterm canli_turu(symbol)
soru_sor(symbol,symbol,symbol)
sakla(symbol,symbol,symbol)
positif(symbol,symbol)
negatif(symbol,symbol)
olgulari_sil
basla
CLAUSES
aranan_canli(cita):-
canli_turu(memelil),
canli_turu(etobur),
positif(sahiptir,sari_kahverengi_renklere),
positif(sahiptir,siyah_benekler).
aranan_canli(tiger):-
canli_turu(memeli),
canli_turu(etobur),
positif(sahiptir, sari_kahverengi_renkler),
positif(sahiptir, siyah_seritli).
aranan_canli(zurafa):-
canli_turu(tirnakli),
positif(sahiptir,uzun_boyunlu),
positif(sahiptir,uzun_bacakli),
positif(sahiptir, siyah_benekler).
aranan_canli(zebra):-
canli_turu(tirnakli),
positif(sahiptir,siyah_seritli).
aranan_canli(devekusu):-
canli_turu(kus),
negatif(eylem,ucar),
positif(sahiptir,uzun_boyunlu),
positif(sahiptir,uzun_bacakli),
positif(sahiptir, siyah_beyaz_renk).
aranan_canli(penguen):-
canli_turu(kus),
negatif(eylem,ucar),
positif(eylem,yuzer),
positif(sahiptir,siyah_beyaz_renk).
aranan_canli(albatros):-
canli_turu(kus),positif(eylem,iyi_ucar).
canli_turu(memeli):-
positif(sahiptir,tuylu).
canli_turu(memeli):-
positif(eylem,sut_verir).
canli_turu(kus):-
positif(sahiptir,kus_tuyleri).
canli_turu(kus):-
positif(eylem,ucar),
positif(eylem,yumurtlar).
canli_turu(etobur):-
positif(eylem,et_yer).
canli_turu(etobur):-
positif(sahiptir,sivri_disleri),
positif(sahiptir, pencelere),
positif(sahiptir,patlak_gozler).
canli_turu(tirnakli):-
canli_turu(memeli),
positif(sahiptir,toynaklar).
canli_turu(tirnakli):-
canli_turu(memeli),
positif(eylem,gevis_getirme).
positif(X,Y):-
xpositif(X,Y),!.
positif(X,Y):-
not(xnegatif(X,Y)),
soru_sor(X,Y,evet).
negatif(X,Y):-
xnegatif(X,Y),!.
negatif(X,Y):-
not(xpositif(X,Y)),
soru_sor(X,Y,hayir).
soru_sor(X,Y,evet):-!,
write(X,” aranan canli:”,Y,’\n’),
readln(Cevap),nl,
frontchar(Cevap,’e’,_),
sakla(X,Y,evet).
soru_sor(X,Y,hayir):-!,
write(X,” aranan canli: “,Y,’\n’),
readln(Cevap),nl,
frontchar(Cevap,’h’,_),
sakla(X,Y,hayir).
sakla(X,Y,evet):-
assertz(xpositif(X,Y)).
sakla(X,Y,hayir):-
assertz(xnegatif(X,Y)).
olgulari_sil:-
write(“\n\nBitirmek icin space tusuna basiniz.\n”),
retractall(_,dbasedom),readchar(_).
basla:-
aranan_canli(X),!,
write(“\n Aradiginiz canli bir “,X, “olabilir.”),
nl,nl,olgulari_sil.
basla :-
write(“\nAradiginiz canliyi bulmak mumkun degildir.\n\n”),
olgulari_sil.
GOAL basla.
Veritabanındaki her hayvan, sahip olduğu veya olmadığı bazı özelliklerle tanımlanmıştır. Kullanıcının cevaplayacağı sorular olumlu(A, B) veya olumsuz(A, B) şeklindedir. Sorulan bir soruya evet veya hayır şeklinde cevap aldıktan sonra, verilen cevabın veritabanına eklenmesi gerekir. Böylece program karar verirken eski bilgileri kullanır. Burada olumlu ve olumsuz olmak üzere sadece iki yüklem kullanılmıştır.
database
xpozitif(symbol, symbol)
xnegatif(symbol, symbol)
Böylece aradığımız hayvanın tüylü değilse, xnegative(vardir, tuy) şeklindeki olguyu kullandık. Pozitif ve negatif kurallar, kullanıcının verdiği cevabın önceden bilinip bilinmediğini kontrol eder.
pozitif(X,Y):-
xpozitif(X,Y),!.
pozitif(X,Y):-
not(xnegatif(X,Y)),
soru_sor(X,Y,evet).
negatif(X,Y):-
xnegatif(X,Y),!.
negatif(X,Y):-
not(xpozitif(X,Y)),
soru_sor(X,Y,hayir).
Pozitif ve negatif kurallarının ikinci kuralı, kullanıcıya yeni bir soru sormadan önce arada bir zıtlık olup olmadığını kontrol eder. Tanımladığımız soru_sor yüklemi kullanıcıya soru sorar ve alınan cevapları düzenler. Cevap e ile başlarsa olumlu, h ile başlarsa olumsuz olarak kabul edilir.
ask(X,Y,yes):-!, write(X,” it “,Y,’\n’), readln(Reply),nl,
frontchar(Reply,’y’,_), remember(X,Y,yes).
ask(X,Y,no):-!, write(X,” it “,Y,’\n’), readln(Reply),nl,
frontchar(Reply,’n’,_), remember(X,Y,no).
remember(X,Y,yes):-assertz(xpositive(X,Y)).
remember(X,Y,no):-assertz(xnegative(X,Y)).
clear_facts:-write(“\n\nÇıkmak için boşluk tuşuna basınız\n”), retractall(_,dbasedom),readchar(_).
9.2. Basit bir yön problemi
Türkiye’deki birkaç şehir arasındaki en uygun yolu bulmak için bir program yapalım. Program bize iki şehir arasında yol olup olmadığını sorsun. Vereceğimiz cevaba göre en uygun yolu bize göstersin. Burada geriye iz sürme ve rekursiyon metotlarını kullanacağız.
DOMAINS
sehir = symbol
mesafe = integer
PREDICATES
nondeterm yol(sehir,sehir,mesafe)
nondeterm guzergah(sehir,sehir,mesafe)
CLAUSES
yol(mardin,sanliurfa,190).
yol(gaziantep,mardin,320).
yol(sanliurfa,gaziantep,146).
yol(sanliurfa,elazig,348).
yol(gaziantep,elazig,540).
guzergah(_1_Sehir,_2_Sehir,Mesafe):-
yol(_1_Sehir,_2_Sehi, Mesafe).
guzergah(_1_Sehir,_2_Sehir,Mesafe):-
yol(_1_Sehir,X,Mesafe1),
guzergah(X,_2_Sehir,Mesafe2),
Mesafe=Mesafe1+Mesafe2, !.
GOAL guzergah(sanliurfa,elazig, Toplam_Mesafe).
Yol yüklemi için kullanılan her bir cümlede, bir şehirden diğerine olan yolun km cinsinden değeri verilmiştir. guzergah yüklemi ise iki şehir arasında yol olduğunu göstermektedir. Güzergahı takip eden bir sürücünün mesafe parametresiyle gösterilen miktarda gideceği belirtilmiştir. Burada güzergah yüklemi rekursiv olarak tanımlanmıştır. Güzergah, birinci cümlede olduğu gibi sadece tek yol olabilir. Bu durumda toplam mesafe sadece iki şehir arasındaki yoldur. Bir şehirden diğerine gittikten sonra ana hedefe gidiliyorsa, bu durumda toplam yol Mesafe1+Mesafe2 olur.
9.3. Hazine Avcısı
Labirent biçimindeki dehliz ve mağaralardan oluşan bir yerde gizli bir hazine bulunsun. Aynı yerden tekrar geçmeden, tehlikeye yakalanmadan giriş noktasından başlayıp emniyetli bir şekilde çıkıştan geçmek ve hazineyi almak için hangi yolu takip etmeliyiz?
Önce hazinenin yerini gösteren haritaya bakalım.
DOMAINS
oda = symbol
odalarin_listesi = oda*
PREDICATES
nondeterm galeri(oda,oda)
nondeterm komsu_oda(oda,oda)
buralara_girmek_tehlikeli(odalarin_listesi)
nondeterm git(oda,oda)
nondeterm guzergah(oda,oda,odalarin_listesi)
nondeterm eleman(oda,odalarin_listesi)
CLAUSES
galeri(giris,canavar).
galeri(giris,cesme).
galeri(cesme,bataklik).
galeri(cesme,gida).
galeri(cikis,hazine).
galeri(cesme,denizkizi).
galeri(haydut,hazine).
galeri(cesme,haydut).
galeri(gida,hazine).
galeri(denizkizi,cikis).
galeri(canavar,hazine).
galeri(hazine,cikis).
komsu_oda(X,Y):-galeri(X,Y).
komsu_oda(X,Y):-galeri(Y,X).
buralara_girmek_tehlikeli([canavar,haydut]).
git(Baslama_noktasi,Bitis_noktasi):-
guzergah(Baslama_noktasi,Bitis_noktasi,[Baslama_noktasi]).
git(_,_).
guzergah(Oda,Oda,Gidilen_odalar):-
eleman(hazine,Gidilen_odalar),
write(Gidilen_odalar),nl.
guzergah(Oda,Cikis_yolu,Gidilen_odalar):-
komsu_oda(Oda,Sonraki_oda),
buralara_girmek_tehlikeli(Tehlikeli_odalar),
not(eleman(Sonraki_Oda,Tehlikeli_odalar)),
not(eleman(Sonraki_Oda,Gidilen_odalar)),
guzergah(Sonraki_Oda,Cikis_yolu,[Sonraki_Oda|Gidilen_odalar]).
eleman(X,[X|_]).
eleman(X,[_|H]):-eleman (X,H).
GOAL git(cikis, giris).
Buradaki her dehliz bir olguyla temsil edilmiştir. git ve guzergah yüklemleri kuralları belirler. Sorgudan elde edeceğimiz cevapta hazineyi alıp emniyetli bir şekilde dışarı çıkmak için gerekli güzergah belirtilecektir. Programdaki önemli bir özellik, rekursiv olarak tanımlı olan guzergah sayesinde gidilen odaların kataloga alınmasıdır. Çıkış odasına geldiğiniz anda üçüncü parametre o ana kadar ziyaret ettiğiniz odaları listeler. Bu odalar arasında hazine var ise, hedefe varıldı demektir. Eğer listede hazine odası yoksa, Sonraki_oda seçeneği tehlikeli odalara gidilmeyecek şekilde genişletilir.
TARTIŞMA VE SONUÇ
Yapay Zeka kavramı ortaya çıktıktan sonra, yurtdışında bu konuda çok sayıda teorik çalışma, değişik disiplinlerde kullanılan birçok uygulama program gerçekleştirilmiştir. Bu programlar sayesinde, bir insan tarafından yapılması çok zaman alabilen ve hata oranı oldukça yüksek olabilen durumlar ortadan kalkmıştır. Örneğin Digital firması yetkilileri, müşterilerinin siparişlerine göre yeni bir sistem dizaynı yaparken, bu sistemde kullanılabilecek donanım seçeneklerinin çokluğu, her bir parçanın birden fazla parça ile uyumlu çalışabilmesi veya bazı parçaların birbirleriyle hiç uyuşmaması gibi durumlardan dolayı, yapılan montajlarda büyük oranda hataların ortaya çıktığını; ancak yazılan bir uzman sistem ve parçalar hakkındaki gerçeklerden oluşan bir bilgi veritabanı ile bu durumun ortadan kalktığını, ayrıca yılda milyonlarca dolar tasarruf sağladıklarını belirtmişlerdir. Bunun haricinde farklı alanlarda kullanılmak üzere hazırlanan ve başarılı bir şekilde çalışan çok sayıda uzman sistem örneği vermek mümkündür.
Tıpta teşhis ve tedavi planlaması amacıyla bilgi tabancı sistemler, birçok alanda (Örneğin Laboratuar, Onkoloji veya Kardiyoloji gibi) kullanım sahası bulmaktadır. Diş tedavisi alanında bilgisayar kullanımı daha ziyade sadece kayıt amaçlı kullanılmaktadır. Karlsruhe’deki Diş Hekimliği Akademisi ile Bremen Üniversitesi Yapay Zeka Laboratuarı arasında işbirliği yapılarak, bir yazılım geliştirilmiştir. Bu çalışma, diş doktorları için bilgi tabanlı bir dökümantasyon ve karar verme sisteminin oluşturulmasını sağlamaktadır. Sistemin kullanılması halinde, diş hekimliği ile ilgili teşhis ve tedavi önerileri yapılabilmektedir.
Türkiye’de ise, bu alanda çok ciddi çalışmaların var olduğunu söylemek çok zordur. Gebze Yüksek Teknoloji Enstitüsü’ndeki Yapay Zeka Bölümü’nde, askeri amaçlı projeler üzerinde çalışmalar yürütülmektedir. Ayrıca Bilkent ve Orta Doğu Teknik Üniversitelerinde yürütülmekte olan TurkLang gibi büyük projeler mevcuttur. Fakat bu çalışmaların çoğu Unix ortamında yapılmakta olup, bireysel ve endüstriyel kullanıma yönelik değildir.
Yapay Zeka ve bunun alt bölümleri olarak kabul edilen Uzman Sistemler, Yapay Sinir Ağları, Tabii Dil İşlenmesi gibi alanlarda program yapmak için mantık dillerinin yanı sıra, Pascal, C++ gibi konvansiyonel programlama dilleri de kullanılmaktadır. Prolog (Programming in Logic) mantık dillerinden en popüler olanıdır. Önceleri daha çok Unix, VAX gibi işletim sistemleriyle çalışan anaçatı bilgisayarlarda kullanılan Prolog, zamanla DOS, daha sonraları ise Windows ve diğer işletim sistemlerine de aktarılmıştır. Visual Prolog, işte bu aşamalardan sonra çıkarılan, çoklu ortamlarda rahatlıkla kullanılabilen bir mantık dilidir. Visual Prolog’un diğer konvansiyonel dillerden ayrıldığı bazı özellikler şunlardır:
a) Visual Prolog prosedürel değil tamamen dekleratif bir yapıya sahiptir. Yani çözümü aranan bir problemin nasıl çözüleceğini tanımlamak yerine, problemin kendisini tanımlamak yeterlidir. Kullanıcı tarafından verilen gerçeklere dayanarak, Prolog’da var olan Inference Engine(Karar verme motoru) istenilen problem için mümkün olan bütün çözümleri bulur.
b) Visual Prolog’daki Geriye İz Sürme mekanizması, konvansiyonel dillerin hiçbirinde bulunmayan güçlü bir özelliktir. Bu özellik sayesinde konvansiyonel dillerdeki FOR..NEXT, DO..WHILE gibi döngüler ortadan kalkar. Prolog, bu özelliği sayesinde, çözüm aranan bir sorguya uygun bütün çözümleri kendiliğinden bulur. Bu durum, döngü komutlarının neden olabileceği hataları baştan itibaren ortadan kaldırır.
c) Visual Prolog’un diğer bir özelliği ise, nesneler arasındaki ilişkilerin çok kolay bir şekilde tanımlanabilmesi, bu ilişkilere dayalı işlemler yapabilmesidir. Örneğin, kullanilir(“Visual Prolog”, uzman_sistemler) şeklinde ifade edilebilen ‘Visual Prolog dili uzman sistemlerde kullanılır’ cümlesini, konvansiyonel dillerle ifade etmek mümkün değildir.
Visual Prolog ile uygulama Programları yazarken, nesneler arasındaki ilişkileri ifade etmek son derece kolay olmasına rağmen, uygulamanın sonuçları hakkında Türkçe bilgi vermek ve Türkçe’de düzgün cümleler kurmak açısından bazı zorluklar mevcuttur. Bunun temel nedeni, İngilizce ve Türkçe cümle yapılarında öğelerin dizilişlerinde farklılıklar bulunmasıdır. Bu yüzden, Türkçe ifade edilen bir cümlede, özne ve yüklem arasında akıcı bir ilişki kurabilmek için, öğeler arasındaki özelliklerin önceden veritabanına yüklenmesi faydalı olacaktır. Bu çalışma sayesinde, Prolog’un Türkçe’ye uyarlanmasında karşılaşılan problemler detaylı olarak ele alınmış, problemlerin en doğru şekilde nasıl çözümlenebileceği konusunda bazı yazılımlar geliştirilmiştir.
Visual Prolog kullanarak yapay zeka konusunda çalışma yapacaklar için önemli birçok ipuçları örneklerle verildiği bu tez çalışması sayesinde, yapay zeka alanında Türkçe uygulamalarda önemli gelişmeler sağlanabilecektir.
(1) STERLING; Leon; SHAPHIRO, Ehud; The Art of Prolog, Advanced Programming Techniques, The MIT Press, London, England, 1992.
(2) CLOCKSIN, W.F, MELLISH, C.S, Programming in Prolog, Springer-Verlag Berlin Heidelberg, Germany, 1987.
(3) O’KEEFE, Richard A.; The Craft of Prolog, The MIT Press, London, England, 1990
(4) CASTILLO, E., ALVAREZ, E., Expert Systems: Uncertainity and Learning, Computational Mechanics Publications, Southampton, Boston, America, 1991
(5) GIARRATANO, JOSEPH. C, Expert Systems: Principles and Programming, PWS-KENT Publishing Company, Boston, America,1989.
(6) IGNIZIO, JAMES. P., Introduction to Expert Systems, McGraw-Hill, Inc., America, 1991.
(7) PDC, Visual Prolog Language Tutorial, Copenhagen, Denmark, 1996.
(8) KUŞ, M.; VAROL, A.; OĞUROL, Y.; VAROL, Y.: Verarbeitung von unsiherem Wissen mit Fuzzy-Prolog, Second Turkish-German Joint Computer Application Days, 15-16 October, Konya, 1998
(9) OĞUROL, Y.; VAROL, A.; KUŞ, M.: Anforderungen und Lösungsansätze einer transferierbaren Entwicklungsumgebung für die medizinische Wissenverarbeitung, Second Turkish-German Joint Computer Application Days, 15-16 October, Konya, 1998.
(10) VAROL, A.; VAROL, N.: ESTA İle Bilgisayar Destekli Eğitim, Beta Basım Yayım Dağıtım A.Ş., 299, 1996.
(11) VAROL, A.; VAROL, N.: ESTA Bilgisayar Yazılımı İle Uzman Sistemlerin Hazırlanması Teknikleri, Süleyman Demirel Üniversitesi, Makine Mühendisliği Dergisi, Cilt 1, Sayı 9, 67-72, 1996.
(12) KUŞ, M.; VAROL, A.; OĞUROL, Y.: Uzman Sistemin Dişçilik Alanında Kullanımına Ait Bir Uygulama, Endüstri&Otomasyon, Aylık Elektrik, Elektronik, Makine, Bilgisayar ve Kontrol Sistemleri Dergisi, Sayı: 18, 1998
(13) KUŞ, M.; VAROL, A.; OĞUROL, Y.: Uzman Sistemin Dişçilik Alanında Kullanımına Ait Bir Uygulama, Endüstri&Otomasyon, Aylık Elektrik, Elektronik, Makine, Bilgisayar ve Kontrol Sistemleri Dergisi, Sayı: 18, 1998
(14) VAROL, A.; VAROL, N.: Uzman Sistemlerde ESTA Yazılımının Önemi, Bilişim’96, 18-22 Eylül 1996 İstanbul, Bildiriler Kitabı, 289-294, 1996.
(15) VAROL, A.; VAROL, N.: Uzman Sistem Hazırlanırken Hangi Kriterler Göz Önünde Bulundurulmalı, GAP 2. Mühendislik Kongresi, 21-23 Mayıs 1998, Şanlıurfa, Bildiri Kitabı, S: 559-566, 1998.
Şekil 1. Aile Fertlerinin Şecere Olarak Gösterilmesi……………………………………….. 88
Şekil 2. Şekil 6.1’deki ağaç yapısında Aşağıya-Doğru-Arama metodunun uygulanması. 90
Şekil 3. Binary tarama yapısı………………………………………………………………………. 94
Tablo 3.1: Visual Prolog’da Tipler ve Alabilecekleri değerler.…………………………. 25
Tablo 7.2. Listelerin baş ve kuyruk halinde gösterilmeleri……………………………… 100
Tablo 7.3: Liste eşleştirme örnekleri…………………………………………………………. 101
Tablo 8.4. Criticalerror yükleminin argümanlarının aldığı değerler…………………. 134