Oyun Dünyamızda Etrafa Bakınmak


Sahip olmak isteyeceğimiz ilk komut bize bulunduğumuz yeri söyleyecek olan komuttur. Peki bir fonksiyonun dünyadaki bir yeri tanımlamak için neye ihtiyacı olabilir? Tanımlamasını istediğimiz yerin neresi olduğunu bilmek ister ve bir haritaya bakabilmek ve o haritada bu yeri bulabilmek ister. İşte tam olarak bunu yapan fonksiyonumuz:

(defun yer-tarif-et (yer harita)
  (second (assoc yer harita)))

Tahmin edebileceğiniz gibi defun kelimesi bir fonksiyon tanımladığımız anlamına geliyor. Fonksiyonun adı yer-tarif-et ve iki parametre alır: bir yer ve bir harita. Bu değişkenlerin iki yıldız arasında olmamasından onların yerel olduklarını anlıyoruz yani global *yer* ve *harita* değişkenlerinden farklıdırlar. Lisp'teki fonksiyonların diğer programlama dillerindekilerden çok matematiksel fonksiyonlara benzediğine dikkatinizi çekmek isterim: Tıpkı matematikteki gibi bu fonksiyon da kullancıya okuması için bir şeyler basmaz ya da bir mesaj kutusu açmaz: Tek yaptığı tanımı içeren fonksiyonun sonucuna göre bir değer döndürmektir. Bulunduğumuz yerin oturma-odasi olduğunu düşünelim (ki gerçekten de öyle..)


Burası için bir tanım bulmak için öncelikle haritadan oturma-odasi'nı gösteren noktayı bulması gerekiyor. İşte bunu assoc komutu yapıyor ve bize oturma-odasi'nı tarif eden veriyi dönüyor. Sonra second komutu listedeki ikinci nesneyi yani oturma-odasi'nın tanımını siler (Eğer yarattığımız *harita* değişkenine bakarsanız, oturma-odasi'ni tanımlayan yazı parçacığı, oturma-odasi ile ilgili bütün veriyi toplayan listenin ikinci nesnesi olduğunu görürsünüz.)

Şimdi Lisp prompt'umuzu kullanarak fonksiyonumuzu deneyelim-€“Kılavuzumuz boyunca olduğu gibi gene

bu font ve renkte

olan yazıları Lisp komut satırına yapıştırın:

(yer-tarif-et 'oturma-odasi *harita*)

==> (|BÜYÜCÜNÜN| EVINDEKI BIR OTURMA ODASINDASINIZ.
KANEPEDE HORUL HORUL UYUYAN BIR |BÜYÜCÜ| VAR.

Mükemmel! Tam istediğimiz gibi.. oturma-odasi'nin önüne koyduğumuz kesmeye dikkatinizi çekmek isterim, ne de olsa bu sembol yeri adlandıran veri (yani bunu Veri Kipi'nde okumak istiyoruz), ancak *harita* sembolünün önüne bir kesme koymadik, çünkü bu durumda isteğimiz derleyicinin *harita* değişkeninin sakladığı verilere ulaşması (yani derleyicinin Kod Kipi'nde olmasını ve *harita* kelimesine sadece ham veri olarak bakmamasını istiyoruz.)

Pratik Programlama Stili

Belki yer-tarif-et fonksiyonumuzun pek çok değişik yönden bayağı kaba olduğunu fark ettiniz. Öncelikle nden yer ve harita değişkenlerini parametre olarak alıyoruz da, direkt olarak global değişkenleri okumuyoruz? Bunun sebebi Lispçilerin genellikle Pratik Programlama Stili'nde yazmayı sevmeleridir (daha açık olmak gerekirse bu-€"prosedürel programlama" ya da-€"yapısal programlama" ile tamamen alakasızdır..). Bu stilde amaç şu kuralları daima izleyen fonksiyonlar yazmaktır:

  1. Sadece fonsiyona verilen ya da fonksiyon tarafından yaratılmış değişkenleri okur. (Yani hiç bir global değişken okunmaz.)

  2. Asla atanmış bir değişkenin değeri değiştirilmez. (Yani değişkenleri arttırmak yada benzer saçmalıklar yok.)

  3. Asla dış dünya ile etkileşime girmez, sonuç değerini döndürmek hariç. (Yani dosyalara yazma, kullanıcıya mesaj yazma yok.)

Belki de bu katı kısıtlamalarla gerçekten işe yarayan bir şeyler yapan bir kod yazıp yazamayacağınızı merak ediyorsunuzdur... Kim bu kuralları izleme zahmetine katlanır ki? Çok önemli bir sebep: Bu stilde kod yazmak programınıza ilişkisel şeffaflık verir: Bu demek oluyor ki, verilen bir kod parçası aynı parametrelerle çağırıldığında, her zaman aynı değeri döner ve her ne zaman çağrılırsa çağrılsın hep aynı şeyi yapar- Bu programlama hatalarını azaltır ve ayrıca pek çok durumda programcının üretkenliğini arttırdığına inanılır.

Elbette her zaman için fonksiyonel stilde olmayan fonksiyonlar ya da kullanıcı veya dış dünya ile iletişim kuramayacağınız durumlar olacaktır. Bu klavuzda bundan sonra karşılaşacağınız çoğu fonksiyon bu kuralları izlemeyecek.

yer-tarif-et fonksiyonumuzla ilgili bir diğer poblem ise bize o yerden başka yerlere doğru olan yolları söylememesi. Bu yolları bize tarif eden bir fonksiyon yazalım:

(defun yol-tarif-et (yol)
  `(,(first yol) ile ,(second yol) arasinda bir yol var.))

Pekala, bu fonksiyon bayağı garip görünüyor: Hatta bir fonksiyondan çok bir veri parçasını andırıyor. Önce deneyelim, yaptığını nasıl yaptığını sonra anlarız:

(yol-tarif-et '(bati kapi bahce))

==> (BATI ILE KAPI ARASINDA BIR YOL VAR.)

Artık her şey açık: Bu fonksiyon yolu tarif eden bir liste alır (tıpkı *harita* değişkenimizin içinde olan gibi) ve ondan güzel bir cümle çıkarır. Şimdi fonksiyona tekrar bakarsak fonksiyonun yarattığı veriye çok benzediğini göreceksiniz: Kısaca yoldan ilk ve ikinci cisimleri kesip belirlenen cümleye çevirir. Bunu nasıl yapar? Ters-kesme kullanarak!

Daha önce de derleyicimizi Kod Kipi'nden Veri Kipi'ne döndürmek için kesme kullandığımızı unutmayın- Ters-kesme kullanarak sadece dönmekle kalmaz ayrıca Kod Kipi'ne virgül ile geri de dönebiliriz:


Bu-€œters-kesme" tekniği Lisp'in muhteşem bir özelliği-€“bize yarattığı veri gibi gözüken kod yazmamıza izin veriyor. Bu çoğunlukla fonksiyonel stilde yazılan kodla olan bir şey: fonksiyonlarımızı ürettikleri veriye benzer şekilde yarattığımızda kodumuzu daha kolay anlaşılır yaparız ve aynı zamanda uzun ömürlü olmasını sağlar. Veri değişmediği sürece, fonksiyonlar veriyi sıkı bir şekilde takip ettiğinden, onların refaktörize edilmesi ya da değiştirilmesi muhtemelen gerekmeyecektir. Böyle bir fonkiyonu VB ya da C'de nasıl yazardınız bir düşünün: Büyük olasılıkla yolu parçalara bölerdiniz sonra da yazı parçacıklarını tekrar birbirine eklerdiniz-€“Yaratılan veriye hiç benzemeyen ve düşük ihtimalle uzun ömürlü olacak, daha rastgele bir süreç.

Artık bir yol tanımlayabiliriz, ancak oynumuzdaki bir yer birden çok yola sahip olabilir. Haydi yollari-tarif-et isimli bir fonksiyon yaratalım:

(defun yollari-tarif-et (yer harita)
  (apply #'append (mapcar #'yol-tarif-et (cddr (assoc yer harita)))))

Bu fonksiyon, fonksiyonel programlama tekniğini kullanmakta: Yüksek Düzey Fonksiyonlar'ı kullanmak- Yani apply ve mapcar fonksiyonları diğer fonksiyonları parametre olarak alıyor böylece onları kendileri çağırıyorlar- Bir fonksiyonu aktarmak için, fonksiyonun adından önce #' koymanız gerekir. cddr komutu listenin ilk iki elemanını kesip atar (böylece geriye sadece yol verisi kalır). mapcar kısaca başka bir fonksiyonu listedeki her nesneye uygular, temel olarak yaptığı bütün yolları yol-tarif-et fonksiyonu ile güzel açıklamalara dönüştürür. "apply #'append" ise sadece parantezleri temizler ve çok önemli değildir. Haydi bu yeni fonksiyonu deneyelim:

(yollari-tarif-et 'oturma-odasi *harita*)

==> (BATI ILE KAPI ARASINDA BIR YOL VAR.
USTKAT ILE MERDIVEN ARASINDA BIR YOL VAR.)

Çok güzel!

Hala tanımlamamız gereken bir şey var: bulunduğumuz yerin zemininde herhangi bir nesnenin olup olmadığı, varsa ne oldukları. Önce verilen yerde herhangi bir nesne var mı bunu söyleyen bir yardımcı fonksiyon yazalım:

(defun is-at (nesne yer nesne-yeri)
  (eq (second (assoc nesne nesne-yeri)) yer))

...eq fonksiyonu bize nesne-yeri listesindeki sembolün şu anki yer olup olmadığını söyler.





Haydi bunu deneyelim:

(is-at 'viski-sisesi 'oturma-odasi *nesne-yerleri*)

==> T

t sembolü (ya da nil hariç bütün değerler) viski-sisesi'nin oturma-odasi'nda var olduğu anlamına gelir.

Şimdi bu fonksiyonu yeri tanımlamak için kullanalım:


(defun zemin-tarif-et (yer nesneler nesne-yeri) (apply #'append (mapcar (lambda (x) `(zeminde bir ,x görüyorsunuz.)) (remove-if-not (lambda (x) (is-at x yer nesne-yeri)) nesneler))))

Bu fonksiyonda yeni bir kaç şey var: Öncelikle, isimsiz fonksiyonlara sahip (lambda bunun için sadece süslü bir isim). Bu ilk lambda formu, yardımcı bir fonksiyon tanımlamakla aynı (defun fasafiso (x) `(zeminde bir ,x görüyorsunuz.)) ve sonra #'fasafiso'yu mapcar fonksiyonuna gönderir. remove-if-not fonksiyonu listeyi mapcar'a güzel cümleler kurmak üzere göndermeden önce içinden o an ki yerde olmayan bütün nesneleri çıkarır. Şimdi bu yeni fonksiyonu deneyelim:

(zemin-tarif-et 'oturma-odasi *nesneler* *nesne-yerleri*)

==> (ZEMINDE BIR VISKI-SISESI |GÖRÜYORSUNUZ.|
ZEMINDE BIR KOVA |GÖRÜYORSUNUZ.|)

Şimdi bütün bu tanımlayıcı fonksiyonları tek ve kolay bir BAK fonksiyonu ile bağlayabiliriz, bu fonksiyon global değişkenleri kullanarak (yani Fonksiyonel Stil'de olmadan) tanımlayıcı fonksiyonları besler ve her şeyi tanımlar:

(defun bak ()
  (append (yer-tarif-et *yer* *harita*)
          (yollari-tarif-et *yer* *harita*)
          (zemin-tarif-et *yer* *nesneler* *nesne-yerleri*)))



Haydi deneyelim:

(bak)

==> (|BÜYÜCÜNÜN| EVINDEKI BIR OTURMA ODASINDASINIZ.
KANEPEDE HORUL HORUL UYUYAN BIR |BÜYÜCÜ| VAR.
BATI ILE KAPI ARASINDA BIR YOL VAR.
USTKAT ILE MERDIVEN ARASINDA BIR YOL VAR.
ZEMINDE BIR VISKI-SISESI |GÖRÜYORSUNUZ.|
ZEMINDE BIR KOVA |GÖRÜYORSUNUZ.|)

Çok şık!


<< ÖNCEKİ BÖLÜM                   SONRAKİ BÖLÜM >>