Thread overview
Özelleştirilmiş InputRange İşlevleri
Oct 02, 2022
Salih Dincer
Oct 02, 2022
Ali Çehreli
Oct 03, 2022
Salih Dincer
Oct 03, 2022
Salih Dincer
Oct 03, 2022
Ali Çehreli
Oct 04, 2022
Ali Çehreli
October 02, 2022

Merhaba,

>

Üst Not: Ali hocam işin içinden çıkamadığım modüller arası alan ve kapsam ile ilgili bir sorum (burda bulamadım) var. Sorum aşağıda, yazdıklarımın sonundaki cümleye çözüm var mı?

Az önce Phobos kütüphanesinin aralıklarla ilgili olanaklarına şöyle bir bakıyordum ve şu tanıdık kodlara (ve tabi daha nicesine!) rastladım:

@property bool empty(T)(auto ref scope T a)
if (is(typeof(a.length) : size_t))
{
    return !a.length;
}

/// ...

@property ref inout(T) front(T)(return scope inout(T)[] a) @safe pure nothrow @nogc
if (!isAutodecodableString!(T[]) && !is(T[] == void[]))
{
    assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof);
    return a[0];
}

/// ...


void popFront(T)(scope ref inout(T)[] a) @safe pure nothrow @nogc
if (!isAutodecodableString!(T[]) && !is(T[] == void[]))
{
    assert(a.length, "Attempting to popFront() past the end of an array of " ~ T.stringof);
    a = a[1 .. $];
}

Örneğin popFront() tam olarak şurada:
https://github.com/dlang/phobos/blob/master/std/range/primitives.d#L2288

Aslında bunları kullanıyordum ve hatta belki de farkında olmadan kullanıyoruz bile! Yani en basitten biz diziyi foreach() ile ekrana yazdırdığımızda (zaten ilk cümlede ifade edilmiş) bunlar çağrılıyor. Gerçi kendi türlerimizde bu 3 işlevi (örn. bir yapı içerisinde) tanımlayıp Andrei'nin hep savunduğu aralıklar dünyasına girebilirsiniz...

Bu konular için lütfen okuyunuz: Ali Çehreli çevirisi şu yazı: https://ddili.org/makale/eleman_erisimi_uzerine.html ya da orinali için: https://www.informit.com/articles/printerfriendly/1407357

Neyse, ana konumuza dönelim: Özelleştirme!

Peki şimdi olaya sadeleştirme gözüyle bakarsak üçünü daha basit şekilde şöyle de yazabirdik:

class Foo
{
  bool isFull(T)(auto ref scope T a)
  {
    return a.length > 0;
  }

  @safe pure nothrow @nogc
  {
    ref inout(T) frontItem(T)(return scope inout(T)[] a)
    {
      return a[0];
    }

    void nextItem(T)(scope ref inout(T)[] a)
    {
      a = a[2 .. $]; // aslında, dilimin ilk değeri 1 olmalı (geçici)
    }
  }
}
>

Not: Lütfen class Foo'ya takılmayın, çünkü artık aynı isimde yeni 3 işlevimiz var. Onun orijinalleri class Orijinal_Kodlar içine de alabilirsiniz. Ben sadece aralarında geçiş yapmak için kolaylık olsun diye koydum.

Şimdi bunları kullanacak örneğimize geçelim. Ama dikkat (!) empty()'i hemen oracakta ismini değiştirerek ve değilini kaldırarak özelleştirdim. Böylece while() içinde ikinci kez tersini almamış oldum:

import std.stdio;
void main()
{
  int[] sayılar = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

  //with(new Orijinal_Kodlar) /*
  with(new Foo)//*/
  {
    while(isFull(sayılar))
    {
      frontItem(sayılar).write(" "); // 1 3 5 7 9
      nextItem(sayılar);
    }
  }
  writeln("(tek sayılar)");
}

Yukardaki örneği çalıştırmak gereksiz çünkü orijinal kodlar ile dizideki 10 sayıyı ekrana yazdırıyoruz. Farklı olan, Foo'daki değişitirilmiş kodlarda. Yani geçici olarak popFront() içinde dilimi 2'den başlattım. Dolayısıyla tek sayılar ekrana yazacak hepsi bu.

Bütün bunları kendi kütüphanenizde kullanabilirsiniz. Örneğin ben isFull'ü bir sınıf veya yapı içine aldığımda main() içinde şu şekilde kullanamıyorum:

bool doluMu(T)(auto ref scope T a) {
  return a.length > 0;
}

/// ...
    while(sayılar.doluMu)

Eğer doluMu() modüller arası alanda (örneğin tek dosyalı bir uygulamada main() dışında) olursa elbette şu hatayı almayacaksınız:

>

Error: no property doluMu for type int[]

Ali hocam bu konuyu şöyle dile getirmiş (genel olarak):

>

İsim Alanı
D'de her isim, tanımlandığı noktadan başlayarak hem içinde tanımlandığı kapsamda, hem de o kapsamın içindeki kapsamlarda geçerlidir. Her kapsam bir isim alanı tanımlar.

İçinde tanımlandığı kapsamdan çıkıldığında, isim artık geçersiz hale gelir ve derleyici tarafından tanınmaz [...]

Yani işlevler/yapılar içinde elbette bir kapsam var, bu doğal. Ama UFCS'yi kullanmak istediğimizde kapsamın içinde olsa dahi sorun yaşabiliriz. Bunun olmaması için konu başından beri bahsettiğim InputRange işlevlerini kendi modülüm (sdb.range) içine alırsam hiç bir sorun yaşamam:

import sdb.range;
void main()
{
  int[] tekler, rakamlar = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];

  bool doluMu(T)(auto ref scope T a) {
    return a.length > 0;
  }

  /* işlev main() içinde olduğu için zaten çalışmaz!
  "rakamlar[] dolu mu? ".write;
  if(rakamlar.doluMu() ) {
    "Evet, dolu!".writeln;
  } else {
    "Hayır, boş!".writeln;
  }//*/

  auto test = rakamlar; // rakamlar tükenmesin diye, o bize lazım :)
  while(test.isFull)
  {
    const r = test.frontItem;

    if(r % 2) tekler ~= r;
    test.nextItem;
  }

  import std.algorithm : all;
  assert(tekler.all!"a % 2"); // tamamı tek sayı, kanıtlandı...

  assert(
          !all!"a % 2"(rakamlar)
  ); // iddia ediyorum içinde sadece tek sayı yoktur!
}

Neyse, bu dersi bir şeyler öğrenmek için ve bizzat deneyerek uyguladım. Siz de buradan veya başka bir bölümden başlayarak aralıklar konusunda derinlemesine bilgi sahibi olabilirsiniz. Bir tek "no property" hatasını çözemiyorum!

Ali hocam, işlevi parantezi ile birlikte kullanmak istemesek ve evet, bir nesnenin üyesi/özelliği olmadığını biliyoruz ama UFCS gereği çalışması gerekmez mi? Yani illa ki modüller arası bir alanda olmalı ha!

Teşekkürler...

October 02, 2022
On 10/2/22 08:03, Salih Dincer wrote:

> class Foo
> {
>    bool isFull(T)(auto ref scope T a)

Çok ilgisiz olarak, "full" deyince bir şeyin boş olmadığı değil, tamamen dolu olduğun anlaşılabiliyor. Yani, "hiç yerimiz yok".

>      void nextItem(T)(scope ref inout(T)[] a)

İlgisiz not: Yan etkisi olan işlevlerin isminde yüklem kullanmak daha doğru oluyor. Burada nextItem() "bir sonraki nesne" gibi anlaşılabiliyor. Tabii ki dönüş değeri 'void' olduğu için bir nesne dönmediğini anlıyoruz ama yine de anlam biraz kayıyor. :) (Yeri gelmişken, D'nin 'empty'si bence yanlıştır çünkü öyle işlevler isEmpty diye isimlendirilmelidir... diye biliriz...)

> Bütün bunları kendi kütüphanenizde kullanabilirsiniz. Örneğin ben
> isFull'ü bir sınıf veya yapı içine aldığımda `main()` içinde şu şekilde
> kullanamıyorum:
> ```d
> bool doluMu(T)(auto ref scope T a) {
>    return a.length > 0;
> }
>
> /// ...
>      while(sayılar.doluMu)
> ```

Orada bir yazım hatası olduğunu düşünüyorum çünkü hem "kullanabilirsiniz" hem de "kullanamıyorum" diyorsun.

Ben kullanabileceğini düşünürüm. Değilse, biraz daha açıklamaya ihtiyacım var.

> Eğer doluMu() modüller arası alanda (örneğin tek dosyalı bir uygulamada
> main() dışında)

Onu da düzeltmek gerek: D'de modüller arası alan yoktur. Her D kodu bir modüle aittir ve en dışarıda tanımlanan bütün isimler de kendi modülüne ait. Başına module yazılmasa da örneğin main.d'nin modülü 'main'dir.

> olursa elbette şu hatayı almayacaksınız:
>
>> Error: no property `doluMu` for type `int[]`

Yani, modülde tanımlarsan çalışıyor ama bir fonksiyon içinde tanımlarsan çalışmıyor mu? UFCS öyledir: Yalnızca modül düzeyindeki işlevleri dikkate alır. (Bağlantısı aşağıda.)

> Yani işlevler/yapılar içinde elbette bir kapsam var, bu doğal. Ama
> UFCS'yi kullanmak istediğimizde kapsamın içinde olsa dahi sorun
> yaşabiliriz.

Yaşayabiliriz değil, yaşarız. :)

Bu konu şurada anlatılıyor:

  https://dlang.org/spec/function.html#pseudo-member

"Functions declared in a local scope are not found when searching for a matching UFCS function. Neither are other local symbols, although local imports are searched"

Yani, UFCS kullanılacaksa, iç kapsamlardaki isimler dikkate alınmaz.

Burada bahsi geçmedi ama şu da var:

"Member functions are not found when searching for a matching UFCS function."

İçinde bulunduğumuz sınıf veya yapını üye işlevleri dikkate alınmaz.

Ali


October 03, 2022

On Sunday, 2 October 2022 at 15:55:31 UTC, Ali Çehreli wrote:

>

[...] "full" deyince bir şeyin boş olmadığı değil, tamamen dolu olduğun anlaşılabiliyor. Yani, "hiç yerimiz yok". [...] D'nin 'empty'si bence yanlıştır çünkü öyle işlevler isEmpty diye isimlendirilmelidir.

Tabi anlamlar önemli, istesek de her şeye Türkçe anlam veremeyiz. Yani Türkçe anlaşsak bile yarıdan fazlası İngilizce olan kodları kullanmak zorundayız.

Ben şahsen şu kodu şöyle okumayı seviyorum:

>

while( true_ise ) {
döngüye_devam
}

Hoş dünyadaki tüm programcılar böyle okuyor ya. Yani ekstra karakter (! <- değil) koymak hoşuma gitmiyor:

while(!stack.empty)
{
  // Yığın boş değilse (doluysa)
  // döngüye devam...
}

Sanırım neden isFull'e yöneldiğim ve ne demek istediğim anlaşılmıştır. Gerçi nesne/işlevlerdeki yüklem (predicate) ifadelerini sevmediğiniz biliyordum ve eminim kendinize göre haklı sebepleriniz vardır.

Mesela şablonlara şu şekilde find!"a < b" eklenen ifadelere predicate diyorlar da son derece anlaşılır oluyor. Kastettiğiniz belki de başka bir şey ama kusura bakmayım henüz anlayamadım. 😀

On Sunday, 2 October 2022 at 15:55:31 UTC, Ali Çehreli wrote:

> >

isFull'ü bir sınıf veya yapı içine aldığımda main() içinde
şu şekilde
kullanamıyorum:

bool doluMu(T)(auto ref scope T a) {
   return a.length > 0;
}

/// ...
     while(sayılar.doluMu)

Orada bir yazım hatası olduğunu düşünüyorum çünkü hem "kullanabilirsiniz" hem de "kullanamıyorum" diyorsun.

Ben kullanabileceğini düşünürüm. Değilse, biraz daha açıklamaya ihtiyacım var.

Evet, Türkçem bozulmaya başladı sanırım 😀

Ben de şimdi okudum ve isteğim anlamı tam verememişim. Ama bu bahsettiğimi birkaç kere denedim. Mesela şimdi bir yapı açtım ve içine şu 2 üyeyi koydum:

  • bool isOdd(T)(T i) in (isIntegral!T) => i & 1;

  • bool isEven(T)(T i) in (isIntegral!T) => (i % 2) == 0 && i != 0;

Sonra yine aynı yapı içinde printEvenNumbers() şeklinde bir test kodu hazırladım ve maalesef UFCS'nin nimetlerinden faydalanamadım. Ama isteğim zaman yapı dışına çıkıp modüller arasındaki kolaylık işlevlerime sadece bir nokta koyarak eriştim.

Örneğin: if(.isOdd(n)) { // .. sdb.math.isOdd

Çünkü main() dışında import sdb.math ekli ve dolayısıyla modüller arası alanım genişledi ve derleyici gidip onu oradan buldu.

Özetle n.isOdd() olarak kullanamamak bence bu UFCS'ye yapılmış önemli bir haksızlıktır.

> >

Eğer doluMu() modüller arası alanda (örneğin tek dosyalı bir
uygulamada
main() dışında)

Onu da düzeltmek gerek: D'de modüller arası alan yoktur. Her D kodu bir modüle aittir ve en dışarıda tanımlanan bütün isimler de kendi modülüne ait. Başına module yazılmasa da örneğin main.d'nin modülü 'main'dir.

Var hocam, bana bunu nasıl söylersiniz :) Hatta modüllerimde public import kullanırsam D dünyasına tek satır ile sınırlar kalkar tüm olanaklar her yer kullanılır.

Saygılar...

October 03, 2022

On Monday, 3 October 2022 at 03:30:53 UTC, Salih Dincer wrote:

>

Mesela şimdi bir yapı açtım ve içine şu 2 üyeyi koydum:

Test kodumu paylaşmayı unutmuşum! Siz de benim modüllerim yok ama ilk satırı uyarlayabilirsiniz de...
(sanırım std içinde olması lazım?)

//import sdb.range, sdb.math;

struct Matematiksel {
  int[] num;

  this(int[] num) {
    add(num);
  }

  void add(int[] num) {
    this.num ~= num;
  }
  // DİKKAT: isOdd() => true döndürecek şekilde manipule edildi!
  bool isOdd(T)(T i) in (isIntegral!T)
  {
    /* açıklamaya ihtiyaç bırakmıyor,
     * kalan işleminin tersi bize tek
     * sayı özelliğini veriyor ama if
     * ( !(i % 2) ) anlamına geliyor!
     */
    return true;//i & 1;
  }

  bool isEven(T)(T i) in (isIntegral!T)
  {
    /* index(i), alışıldığı gibi 0'dan
     * başlıyorsa kullanılması tavsiye
     * edilmez ama matematikte 0 sayısı
     * çift değil, tek de değildir. O
     * yüzden ihtiyaç halinde kullan!
     */
    return (i % 2) == 0 && i != 0;
  }

  void printEvenNumbers(bool evenNumbers = true)()
  {
    import std.stdio;

    bool togglePrint;
    foreach(n; num)
    {
      static if(evenNumbers)
      {
        if(.isEven(n)) togglePrint = true;
      }
      else {
        if(.isOdd(n))  togglePrint = true;
      }

      if(togglePrint)
      {
        n.write(" ");
        togglePrint = false;
      }
    }
    writeln;
  }
}

void main()
{
  auto rakamlar = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];/*
  auto rakamlar = inclusiveRange(9).arrayCon;//*/
  auto mat = Matematiksel(rakamlar);

  mat.printEvenNumbers!false;/* "1 3 5 7 9"
  mat.printEvenNumbers; //* "2 4 6 8" */

  const i = 2;
  assert(i.isEven); // hata yok çünkü 2 bir çift sayıdır!

  bool çiftMi() { return (i % 2) == 0 && i != 0; }

  //assert(i.çiftMi); ÇALIŞMAZ ÇÜNKÜ MODÜLLER ARASI ALANDA DEĞİL!
}

Başarılar...

October 02, 2022
On 10/2/22 20:30, Salih Dincer wrote:

> Ben şahsen şu kodu şöyle okumayı seviyorum:
>
>> while( `true_ise` ) {
>> `döngüye_devam`
>> }
>
> Hoş dünyadaki tüm programcılar böyle okuyor ya. Yani ekstra karakter (`!
> <- değil`) koymak hoşuma gitmiyor:

Aynı fikirdeyiz.

>
> ```d
> while(!stack.empty)
> {
>    // Yığın boş değilse (doluysa)
>    // döngüye devam...
> }
> ```
>
> Sanırım neden isFull'e yöneldiğim ve ne demek istediğim anlaşılmıştır.

Tabii ki anladım. Ama "full" doğru sözcük değil. Full, "dopdolu" demek. Sen ise "bomboş değil" anlamını istiyorsun. Başka bir deyişle, "empy"nin tersi "full" değildir.

> Gerçi nesne/işlevlerdeki yüklem (predicate) ifadelerini sevmediğiniz
> biliyordum ve eminim kendinize göre haklı sebepleriniz  vardır.

"Yüklem"i yanlış mı kullandım? "Fiil" daha mı doğruydu? Yanlış anlaştığımız kesin çünkü predicate de severim yüklem de. (?) :)

>> Onu da düzeltmek gerek: D'de modüller arası alan yoktur. Her D kodu
>> bir modüle aittir ve en dışarıda tanımlanan bütün isimler de kendi
>> modülüne ait. Başına module yazılmasa da örneğin main.d'nin modülü
>> 'main'dir.
>
> Var hocam, bana bunu nasıl söylersiniz :)

Benim kafamda şöyle canlanmış: D'de herşey modül içindedir. Modül olmayan yer yoktur.

> Hatta modüllerimde public
> import kullanırsam D dünyasına tek satır ile sınırlar kalkar tüm
> olanaklar her yer kullanılır.

Evet ama yine de herkes kendi modülünde tanımlı. public import edince yaptığımız, bir modüldeki isimleri erişilir hale getirmek. Erişime ek olarak bir de tanımlandıkları yeri değiştirmiyoruz.

Ali


October 04, 2022
On 10/2/22 23:13, Ali Çehreli wrote:
> On 10/2/22 20:30, Salih Dincer wrote:

>  > Gerçi nesne/işlevlerdeki yüklem (predicate) ifadelerini sevmediğiniz
>  > biliyordum ve eminim kendinize göre haklı sebepleriniz  vardır.
>
> "Yüklem"i yanlış mı kullandım? "Fiil" daha mı doğruydu? Yanlış
> anlaştığımız kesin çünkü predicate de severim yüklem de. (?) :)

O konuyu geç anladım: Nitelik (property) işlevlerinde sevmiyorum. Örneğin, .getLength() yerine .length() olmalı.

Eğer işlev bir iş görüyorsa örneğin dosyaOluştur() gibi olmalı.

Ali