Thread overview
Hector neden ısırmıyor :)
May 02, 2022
Salih Dincer
May 02, 2022
Salih Dincer
May 02, 2022
Ali Çehreli
May 02, 2022
Salih Dincer
May 02, 2022
Ali Çehreli
May 02, 2022

Merhaba,

Şu makale'den esinlendiğim ve Belçikalı yazarı sağlam bir C++'cı olan (2017'de D'ye gönül veriyor ve eski kütüphanelerini yeniden kodladığında, yapabildiklerini görünce hayranlıktan adeta sarhoş oluyor!) Jean-Louis Leroy'un örneği hoşuma gitti ve kendimce, içine Türkçe ifadeler katarak güzel bir OOPtutor yapmak istedim...

Sonucu şöyle:

>

Bu insan yürüyor...
Snoopy: havlarım
Hector: Bu insan koşuyor...

Ama nedense, gizlediğim satırlar tamam da (evet, Pitbull sınıfına iki şekilde de erişiliyor) foreach() içindeyken Hector, yürüyen veterinerimizi bir türlü ısırmıyor 😀

Herhalde bir şeyler yolunda gitmiyor çünkü override bir işlev, işte eğlenceli kodumuz:

/* Şöyle üst-karşıt önermeleri düşünün:

 * "bütün köpekler ısırır"        (YANLIŞ)
 * "ısıran her köpek hayvandır"    (DOĞRU)
 * "hiçbir köpek hayvan değildir" (YANLIŞ)
 * "bütün köpekler hayvandır"      (DOĞRU)
*/
interface Hayvan { string tekmele(); }
/* Arayüze de bir eylem ekleyelim...
 *
 * "tekmelenen her köpek ısırır"  (YANLIŞ)
 */
class Köpek /* bir */ : Hayvan /* dır */
{
  string neYapar = "havlarım ";
  string tekmele() {
    return neYapar;  /* tekmele()'diğinde! */
  }
}
// Öyleyse başka köpek ısırabilir :)

class Pitbull /* bir */ : Köpek, Hayvan /* tir */
{
  string neYapar = " ısırırım!";
  override string tekmele() {
    return super.neYapar     ~ "ve kaçamazsan"
    /* tekmele()'diğinde! */ ~ this.neYapar;
  }
}

class İnsan {
  enum neYapıyor
  {
    uyuyor, yürüyor, oturuyor, koşuyor,
    kodluyor // mesleğini yapıyor
  }

  neYapıyor eylem;

  this(int eylemi) {
    eylem = cast(neYapıyor)eylemi;
  }

  void hayvanlaKarşılaşıyor(Hayvan hayvan) {

    with(neYapıyor) final switch(eylem) {
      case uyuyor :
        "zzz, rüyasında köpek görüyor".writeln;
        break;
      case yürüyor :
        // yolda köpekler saldırıyor!
        hayvan.tekmele.writeln;
        goto case koşuyor;
      case oturuyor :
        eylem = oturuyor; // kaç...
        break;
      case koşuyor :
        eylem = koşuyor; // kaç...
        break;
      case kodluyor :
      eylem = kodluyor; // kaç...
    }
  }

  override string toString() {
    import std.format;
    return format("Bu insan %s...", eylem);
  }
}

import std.stdio;
void main()
{
  auto veteriner = new İnsan(1);
       veteriner.writeln; // yürüyor...

  Hayvan Snoopy = new Köpek,
         Hector = new Pitbull;
/*
  Snoopy.tekmele.writeln;
  Hector.tekmele.writeln;
*/
  Hayvan[string] hayvanlar = [
    "Snoopy" : new Köpek,
    "Hector" : new Pitbull
  ];
/*
  hayvanlar["Snoopy"].tekmele.writeln;
  hayvanlar["Hector"].tekmele.writeln;
*/
  foreach(hayvan; hayvanlar.keys) {
    hayvan.write(": ");
    veteriner.hayvanlaKarşılaşıyor(
      hayvanlar[hayvan]
    );
  }
  veteriner.writeln; // koşuyor...
  /*** nedeni belli: Pitbull ! ***/
}

SDB@79

May 02, 2022

On Monday, 2 May 2022 at 03:17:42 UTC, Salih Dincer wrote:

>

"tekmelenen her köpek ısırır" (YANLIŞ)
"her köpek tekmelenmez! (DOĞRU)

Karşıtını unutmuşum :)

Aristoteles'den cebirin babası Muhammad ibn Musa al-Khwarizmi'ye kadar (biz onu algoritmaya isim veren Hârizmî olarak biliriz), oradan OOP ve D'ye 2 binli yıllara...

Aman tanrım 😀

Neyse, küçük bir düzeltme: Orijinal makalede Pitbull, Köpek sınıfından türemeydi ve ben denemek için ekstra Hayvan arayüzüne de ekledim; bakalım n'olcak diye...

Özetle ne denediysem sorunu çözemedim, belki DI ile kandırarak dışardan nesnelere erişmesini sağlayabilirim. Bilemedim şimdi!

İyi bayramlar...

May 02, 2022
Bunu çok tekrarladığım için üzülüyorum ama ben seni bazen çok zor anlıyorum. Örneğin, bu konuda bir soru mu sorduğundan yoksa eğlenceli bir başlık mı kullandığından bile emin olamadım.

Neyse ki daha sonradan "ne denediysem sorunu çözemedim" dediğine bakarak bir sorun olduğunu anlıyorum. Ama sorunu açıkça belirtmiyorsun. Örneğin, "Hector neden ısırmıyor" derken onun ısırması gerektiğini düşündüğünü ben ancak hissedebiliyorum.

Konunun senin kafanda apaçık olduğunu biliyorum ama araya "gizlediğim satırlar", "iki şekilde de erişiliyor", vs. girince soru bana hiç açık gelmiyor.

Veya, aşağıdaki ifadenin akıllıca bir yöntem olduğunu hissediyorum ama sanki asıl konuyla ilgisi olmayan bir yan unsur mu o?

    return super.neYapar     ~ "ve kaçamazsan"
    /* tekmele()'diğinde! */ ~ this.neYapar;

Yoksa bu konunun temelindeki asıl konu o mu? Şimdi okumaya geçiyorum...

On 5/1/22 20:17, Salih Dincer wrote:

> Jean-Louis Leroy'un örneği hoşuma
> gitti ve kendimce, içine Türkçe ifadeler katarak güzel bir OOPtutor
> yapmak istedim...

Orada temel bir sorun var: Jean-Louis Leroy, "open methods" kavramını tanıtırken öncelikle şunu vurgular: Aralarından D'nin de bulunduğu bütün yaygın programlama dillerinin OOP yaklaşımları yanlıştır. Örneğin, bu diller sanal davranışı üye işlevler yoluyla sunarlar. Bu yanlıştır. Onun yerine, sanal işlevler "open" olarak ve sınıfların dışından tanımlanabilmelidir. Buyrun, "open methods"... İşin güzeli, open methods "multiple dispatch" denen soruna da çözüm getirir. (Kendisine katılıyorum ama bunlar Jean-Louis'nin sözleridir.)

Jean-Louis'nin verdiği köpekli örnek, bu işin D'de sınıflarla nasıl zor halledilebildiğini ama open methods ile nasıl kolay olduğunu gösterir.

Sen ise aynısını D'nin var olan OOP'si ile çözmeye çalışıyorsun. Daha zor olacaktır ama tamam...

> Sonucu şöyle:
>> Bu insan yürüyor...
>> Snoopy: havlarım
>> Hector: Bu insan koşuyor...

Sanırım o sonucu yanlış buluyorsun. Onun yerine ne olmasını istediğini yazmıyorsun.

> Ama nedense, gizlediğim satırlar tamam da (evet, Pitbull sınıfına iki
> şekilde de erişiliyor) ```foreach()``` içindeyken Hector, yürüyen
> veterinerimizi bir türlü ısırmıyor 😀

Galiba çıktıda "ısırma" görmek istiyoruz. (Ben gizlenen satırları gözardı edeceğim.)

> Herhalde bir şeyler yolunda gitmiyor çünkü override bir işlev, işte
> eğlenceli kodumuz:
>
> ```d
> /* Şöyle üst-karşıt önermeleri düşünün:
>
>   * "bütün köpekler ısırır"        (YANLIŞ)
>   * "ısıran her köpek hayvandır"    (DOĞRU)
>   * "hiçbir köpek hayvan değildir" (YANLIŞ)
>   * "bütün köpekler hayvandır"      (DOĞRU)
> */
> interface Hayvan { string tekmele(); }
> /* Arayüze de bir eylem ekleyelim...
>   *
>   * "tekmelenen her köpek ısırır"  (YANLIŞ)
>   */
> class Köpek /* bir */ : Hayvan /* dır */
> {
>    string neYapar = "havlarım ";
>    string tekmele() {
>      return neYapar;  /* tekmele()'diğinde! */
>    }
> }
> // Öyleyse başka köpek ısırabilir :)
>
> class Pitbull /* bir */ : Köpek, Hayvan /* tir */
> {
>    string neYapar = " ısırırım!";

Bu sorun bazen karşımıza çıkar: OOP yalnızca davranışı halleder ama bazen burada görüldüğü gibi veriyi de etkilemesini isteriz. Sanırım burada "keşke virtual veri" olabilseydi demek istiyoruz. Ne yazık ki yoktur. Aklıma hemen gelen iki çözüm:

- neYapar() diye sanal bir işlev sunulur ve üst sınıf onu çağırır.

- Türler ne yaptıklarını üst sınıflarını super() ile kurarken bildirirler.

>    override string tekmele() {
>      return super.neYapar     ~ "ve kaçamazsan"
>      /* tekmele()'diğinde! */ ~ this.neYapar;
>    }

Şimdi anladım: Pitbull'a vurulunca "ısırırım" görmek istiyorsun.

> }
>
> class İnsan {
>    enum neYapıyor
>    {
>      uyuyor, yürüyor, oturuyor, koşuyor,
>      kodluyor // mesleğini yapıyor
>    }
>
>    neYapıyor eylem;
>
>    this(int eylemi) {
>      eylem = cast(neYapıyor)eylemi;
>    }
>
>    void hayvanlaKarşılaşıyor(Hayvan hayvan) {

Hah! İşte orası "multiple dispatch": İnsanın hayvanla karşılaşmasının sonucu hem insana hem hayvana bağlı olmalı. Sen, OOP'nin sunduğu sanal işlev çözümünün dışına çıkmak zorunda kalarak 'switch' kullanıyorsun.

Böyle kısa bir örnekte halledilebilir ama sınıf çeşitleri arttıkça içinden çıkılamaz hale gelir.

>      with(neYapıyor) final switch(eylem) {
>        case uyuyor :
>          "zzz, rüyasında köpek görüyor".writeln;
>          break;
>        case yürüyor :
>          // yolda köpekler saldırıyor!
>          hayvan.tekmele.writeln;
>          goto case koşuyor;

O biraz garip gibi. Yalnızca 'yürüyor' iken tekmele() çağrılıyor, öyle mi? Peki, olabilir...

>        case oturuyor :
>          eylem = oturuyor; // kaç...

O atamaların bir etkisi yok tabii: 'oturuyor' diye başladık ve 'oturuyor' atadık.

>          break;
>        case koşuyor :
>          eylem = koşuyor; // kaç...
>          break;
>        case kodluyor :
>        eylem = kodluyor; // kaç...
>      }
>    }
>
>    override string toString() {
>      import std.format;
>      return format("Bu insan %s...", eylem);
>    }
> }
>
> import std.stdio;
> void main()
> {
>    auto veteriner = new İnsan(1);

Zaten okunaklı bir 'enum' varken neden 1 kullanıyoruz? Herhalde 'yürüyor'a karşılık geliyor. Tamam...

>         veteriner.writeln; // yürüyor...
>
>    Hayvan Snoopy = new Köpek,
>           Hector = new Pitbull;

Akıllıca kısa yazılmış bir kod. Geleneksel olmadığından Ali'nin anlaması için çaba sarfetmesi gerekti. Ama tamam: Snoopy ve Hector diye iki Hayvan var...

> /*
>    Snoopy.tekmele.writeln;
>    Hector.tekmele.writeln;
> */
>    Hayvan[string] hayvanlar = [
>      "Snoopy" : new Köpek,
>      "Hector" : new Pitbull
>    ];

Onlar nereden çıktı? Şimdi sanki bir de "Snoopy" ve "Hector" dizgileriyle erişilen bir Köpek ve bir de Pitbull var. Umarım Snoopy ile "Snoopy"yi ve Hector ile "Hector"u karıştırmam. :/

> /*
>    hayvanlar["Snoopy"].tekmele.writeln;
>    hayvanlar["Hector"].tekmele.writeln;
> */
>    foreach(hayvan; hayvanlar.keys) {
>      hayvan.write(": ");
>      veteriner.hayvanlaKarşılaşıyor(
>        hayvanlar[hayvan]
>      );
>    }
>    veteriner.writeln; // koşuyor...
>    /*** nedeni belli: Pitbull ! ***/
> }
> ```
> SDB@79

Biraz uzun sürdü ama sorunu çözdüm: Veteriner önce Snoopy ile karşılaşıyor ve eylemini 'koşuyor' olarak değiştiriyor. Hata, yukarıda hissettiğim yerdeymiş: switch deyiminde 'koşuyor' durumunda tekmele() işlevi çağrılmıyor.

(Bunu çözmek için kodun çeşitli yerlerine writeln() ile bir şeyler yazdırdım ve Pitbull.tekmele()'nin neden çağrılmadığını anlamaya çalıştım.)

Ali

May 02, 2022
On Monday, 2 May 2022 at 17:24:54 UTC, Ali Çehreli wrote:
> Biraz uzun sürdü ama sorunu çözdüm: Veteriner önce Snoopy ile karşılaşıyor ve eylemini 'koşuyor' olarak değiştiriyor. Hata, yukarıda hissettiğim yerdeymiş: switch deyiminde 'koşuyor' durumunda tekmele() işlevi çağrılmıyor.

Bu sorunu (Hector'un neden ısramadığını) gözden kaçırdığıma inanamıyorum. Belli ki kodun eksiklik ve mantık hatası içermesi kaçınılmaz olarak beni hataya düşürmüş:

tekmele(), (orijinal örnekte kick) neden Hayvan isimli interface'deki? Öyle ya, bu bir kanguru veya eşek değil. Olsa, olsa kovala() olması gerekirdi.

Ayrıca İnsan sınıfının durum değişikliği kalıcı yapma hatası bana önemli bir şeyi öğretti (evet, hatamdan ders çıkardım) hatta birçok ders çıkardım diyebilirim.

Şimdi uzaklaş() ve tekmele() yöntemlerini İnsan'a geçircem ve dediğim gibi Hayvan'da da kovala() olacak (belki havla/ısır, hatta kokla da olmalı) ne dersiniz?

Böylece, örneğin uzaklaş(100) işlevi ile veteriner'i güvenli alana aldıktan sonra sonra tekrar yürütmeleyim. Böylece başka arazide Pitbull ile karşılaşabilir ve belki uzaklaştıramayıp ısırtabilirim de 😀

Bu durumda köpeklerin yarıçapı metre cinsinden olan mıntıka alanını da önceden belirlemeliyim ki insan koştuğunda durumu olumlu/olumsuz değişebilirdi.

Ya hu program yazmak niye bu kadar zor!
May 02, 2022
On 5/2/22 12:59, Salih Dincer wrote:

> tekmele(), (orijinal örnekte kick) neden Hayvan isimli interface'deki?
> Öyle ya, bu bir kanguru veya eşek değil. Olsa, olsa kovala() olması
> gerekirdi.

Programa göre değişir. Bütün dünyayı bütün programlarda mükemmel olarak tanımlamak zorunda değiliz. İşe yaradığı sürece her şey kabul...

Kaldı ki, OOP çok nadir durumlarda işe yarayan bir programcılık umududur. Örneğin, "herşey bir nesnedir" buyruğu yanlıştır. Bunun en güzel kanıtlarından birisi, herkesin bildiği Borsa (Monopoly) oyununu kodluyor olalım. Bir oyuncunun bir emlağı satın alabileceğini denetleyen işlev hangi sınıfın üye işlevi olmalıdır? Oyuncu? Oyun? Emlak? Zar? :p Buyurun: Her kavram bir sınıf ise ve her şey bir nesne ise çözünüz, görelim. :) (Bunları "rich design model" ve "anemic design model" karşılaştırmaları sırasında da konuşmuştuk.)

Eğer kolay bir çözüm bulamazsanız da kendinizi suçlamayın. Borsa gibi bir oyundaki basit etkileşimleri OOP ile kolayca çözemiyorsak suç bizde mi, yoksa OOP'de ve onu sunan programlama dilinde mi? Yok, yok... OOP, herşeyi çözdüğünü iddia eden eski bir umuttan başka bir şey değildir.

Ali