Thread overview
Lambda İşlevler Testi
Apr 11, 2022
Salih Dincer
Apr 12, 2022
Ali Çehreli
Apr 12, 2022
Salih Dincer
Apr 12, 2022
Salih Dincer
Apr 12, 2022
Ali Çehreli
Apr 12, 2022
Salih Dincer
Apr 12, 2022
Ali Çehreli
April 11, 2022

Yeni yaptığım faydalı test sonuçları şöyle:

  • D’de function literal olarak anılan delegate ve function türünde olmak üzere 2 lambda işlev vardır. Bunların farklı yazım türlerinden dolayı kısa/uzun, tek satır veya isimsiz/anonim kullanım şekilleri olsa da çoğu birbirinin hemen hemen aynısıdır. Derleyici kontrolünde auto ile kurulması tavsiye edilir.

  • Delegate (temsilci) olarak tanımlanan uygulamanın diğerine göre bir avantajı vardır. O da içinde bulunduğu kapsamın dışında tanımlı nesnelere erişebilmesidir. Ancak function öyle değidir, global alanda tanımlananlar hariç, bir yapı/sınıf içinde bile this’e erişemez. Her ikisi de yerel değişkenlerine doğal olarak erişir.

  • Sıradan bir işlev gibi tek başına çağrıldığında mutlaka sonunda 2. bir parantez aç/kapa olmalıdır. Ama hem UFCS ile çalışır, hem de ilk parametre soluna yazılsa ve () işareti unutulsa bile çağrıldığı yere by-value olarak dönüş sağlar. Eğer parametre almıyorsa isimsiz olanı saymazsak baştaki () işaretini kullanmayabilirsiniz. Örneğin:

auto logYaz = {
  auto logger = new LOGLAYICIM(LogLevel.info);
  logger.log("Kurulum tamam, hata yok.");
}
  • Dilerseniz, derleme zamanı değerlere ihtiyaç duyan uygulamalar için (örn. bir yapı içine işlev enjekte etmekte) alias kullanabilirsiniz Pratikte faydası olmasa da const ile de kurabilirsiniz ama sadece aldığı parametrelerde kullandığınızda etkisini görebilirsiniz. Çünkü kendi içinde (eğer erişebiliyorsa) kapsam dışındakileri değiştirmeye devam edecektir. O yüzden delegate türünü kullanırken dikkatli olmalısınız, affetmez!

  • Dilerseniz de iki aşamalı, sanki virtual function tanımlar gibi (sınıflardaki abstract konusuna bakınız) hiçbir şeye eşitlemeden daha sonra neyi tanımladıysanız ona uygun bir işlev bağlanabilir. (-bknz. örnekteki global delegate/function tanımlaması, farketmez main() içinde de olabilir)

Bu 5 madde dışında başka sonuçlar da çıkarabilir ya da eksik ifadeler olabilir. Ama lütfen, örnek kod sonundaki bonus'lara da bakıptest()'den başlayarak deneyiniz.

Aslında konu,şurada örnekleriyle anlatılıyor. Ayrıca Ali Çehreli'nin kitabındaki ilgili bölümde daha fazla bilgi ve örnek mevcut. Ama hızlıca denemek isterseniz elde ettiğim sonuçları aşağıdaki örneklerde görebilirsiniz:

import std.stdio;

void test()
{
  string str;
//        ^-- bu noktada str = null

  (){
    str = "Merhaba D";
  }();
//  ^-- tam bu noktada str değişir

  auto anonim = {
    str = "Merhaba C";
    return 0;
  };
// Ama bu çağrılmayı bekler...

  str.writeln; // "Merhaba D"

  anonim(); /* int delegate() türünde bir işlev,
             gereksiz yere 0 değerini döndürür */

  str.writeln; // "Merhaba C"

  anonim = () => 1;
  anonim().writeln; /* Şimdi döndürdüğü kullanılır
  ama dileseydik temsilci kullanmazdık:         */

  auto func = () => 2;
  typeid(func).writeln; // ÇIKTISI:
//       int function() pure nothrow @nogc @safe*
  int i = 3;
  // func = () => i;
}   /* Bu değer --^
döndürülemeyeceği için kod derlenmez! */

Çünkü function türünde kurmuştuk. Eğer delegate olsaydı main() içinde temsil edilebilirdi. Ayrıca dikkat: @safe yanındaki asterix (*) işaretine bakılırsa by-value değilmiş! Sizce neden delegate'de işaretçi olduğu belirtilmemiş olabilir? Çünkü zaten temsilci nesne olduğu için mi?

int eight = 8;

int dokuz() { return 9; }
int sekiz() { return eight; }

int delegate() del;
int function() fun;

void main()
{
  test();

  auto dokuz = 9;

//  del = () => 8;/* bu satır yerel değişken döndürür
  del = ()
  {
    return dokuz;
  }; /*
  Bu temsilci (delegate) işlev yerel olmayan değer
  de döndürebilir */




//  fun = () => dokuz;/* bu satır derlenöz!
  fun = ()
  {
    return 8; // eight; <-- bunu da dene, lütfen!
  }; /*
  Bu anonim (function) işlevin derlenmediğinde,
  Error: __lambda2 cannot access
         frame of function D main
  hatası verir çünkü bulunduğu kapsamın yerel değişken döndürür */

  writeln("- Lambda işlevler: () ile kullanıma dikkat!");
  del().writeln(", ", fun());

  "\n".writeln("- Normal işlevler:");
  dokuz.writeln(", ", sekiz);
}

6. numara (bonus):

Eğer denediyseniz fun() eight değişkenine erişebiliyor çünkü global alanda tanımlanmış. Burada delegate/function farklı bellek alanlarında bulunduğu sonucu çıkarılabilir. Bilemiyorum belki de izinler ile ilgili bir durumdur.

Ayrıca farkettiyseniz dokuz() işlevi ile main() içindeki aynı isimde değişken karışmıyor. Çünkü farklı kapsamlarda (biri global'de) ve hata da vermiyor. Derleyiciye yardım etmek istersek sonunda () işaretini koyabiliriz. Bir benzerini şu örnekte de uygulabilirsiniz:

int delegate(int) karesi;

void main()
{
  karesi = (int a) => a * a;
  int karesi = 5.karesi;
  assert(karesi == 25);
}

Başarılar...

April 11, 2022
On 4/11/22 00:53, Salih Dincer wrote:

> [ilgili
> bölümde](http://www.ddili.org/ders/d/kapamalar.html)

Closure'un karşılığı olarak "kapama" kullanmış olmaktan artık hiç memnun değilim. :/

>    auto anonim = {
>      str = "Merhaba C";
>      return 0;
>    };

Konuyla ilgisiz bir gözlem olarak, orada bir çelişki var: "anonim", isimsiz demek ama orada "anonim" kendisi isim olduğundan çelişki oluşturuyor. :)

> ```@safe``` yanındaki asterix
> (*) işaretine bakılırsa by-value değilmiş!

Bence oradaki '*' en azından bu durumda bir hata çünkü aşağıdaki açık yazımda görüldüğü gibi, tür ile ilgisi yoktur:

  int function() pure nothrow @nogc @safe func = () => 2;

> Sizce neden delegate'de
> işaretçi olduğu belirtilmemiş olabilir? Çünkü zaten temsilci nesne
> olduğu için mi?

Orada hata olmadığı için. :)

function'daki '*', function'ın C gibi işlev göstergeleri ile ilgisinden geliyor olmalı. Evet, function işlev göstergesidir ama türü 'function' diye yazınca ayrıca '*' yazılmaz; yazım hatasıdır. O yüzden typeid() bence hatalı davranıyor. Bir de serbest işlevle deneyelim:

import std.stdio;

int foo(double) {
  return 0;
}

void main() {
  int function(double) f;
  f = &foo;
  writeln(typeid(f));
}

Orada da '*' var:

int function(double)*

Bence olmamalı.

> //  del = () => 8;/* bu satır yerel değişken döndürür

Tam doğru söylemek gerekirse orada bir değişken yok çünkü 8 bir hazır değerdir (literal).

> Eğer denediyseniz ```fun()``` eight değişkenine erişebiliyor

Ama eight bir değişken değil, bir işlev. Tek başına 'eight' yazmak, eight() diye çağırmanın aynısıdır.

> delegate/function farklı bellek
> alanlarında bulunduğu sonucu çıkarılabilir.

function, 8 bayttan oluşan bir işlev göstergesidir. Perde arkasında mikroişlemcinin adres değerlerinden bir farkı yoktur. Hangi işlevin adresini taşıyorsa o değeri IP'ye (instruction pointer) yerleştirip işlemek kadar basittir.

delegate'in tek farkı, iki 8 bayttan oluşmasıdır:

1) function gibi bir işlev adresi

2) delegate'in 'context'i. İki farklı context olabilir:

2a) Context, yukarıdaki örneklerde görüldüğü gibi yerel değişkenler olabilir. Bu yerel değişkenler, delegate daha sonradan işletilirken de geçerli olsunlar diye program yığıtında (stack) değil, dinamik bellek bölgesinde yaşarlar.

2b) Context, bir sınıf nesnesinin üyeleri olabilir.

> Bilemiyorum belki de izinler
> ile ilgili bir durumdur.

Hayır, izinle ilgisi yok. function ile delegate yalnızca 'context' değişkenine sahip olup olmamak ile ayrılırlar.

Ben de bir 'delegate'in kapsamının nasıl dinamik bellekte yaşadığını göstermek istiyorum.

import std.stdio;

struct S {
  ~this() {
    writeln("sonlanıyor");
  }
}

auto delegateÜret(int i) {
  writeln("delegateÜret çağrıldığında;\ni: ", &i);

  return (int j) {
    writeln("deletage işletilirken;\ni: ", &i, "\nj: ", &j);
    return i + j;
  };
}

void main() {
  int stackDeğişkeni;
  writeln("Stack şu adres yakınında:\n", &stackDeğişkeni);

  auto d = delegateÜret(1);
  assert(d(2) == 3);

  writeln("delegate'in kapsamı (context'i):\n   ", d.ptr);
}

Çıktısını aşağıda açıklıyorum:

Stack şu adres yakınında:
7FFF003B1CE0                     <--- (1)
delegateÜret çağrıldığında;
i: 7F60DA66D008
deletage işletilirken;
i: 7F60DA66D008                  <--- (2)
j: 7FFF003B1CC8                  <--- (3)
delegate'in kapsamı (context'i):
   7F60DA66D000

Geçen sohbette yaptığımız gibi, (2) ve (3) değişkenlerinin belleğin çok farklı yerlerinde yaşadığını gösteriyorum. Salt bir delegate tarafından kullanıldığından, i stack'te değil, dinamik bellektedir. (Stack'in hangi adres yakınında olduğunu (1) ispatlıyor.)

En sonda da .ptr niteliği yazdırıyorum ve o da i'nin 8 adres ötesinde...

Ali

April 12, 2022

On Tuesday, 12 April 2022 at 00:33:23 UTC, Ali Çehreli wrote:

>

On 4/11/22 00:53, Salih Dincer wrote:

Ben de bir 'delegate'in kapsamının nasıl dinamik bellekte yaşadığını göstermek istiyorum.

Teşekkürler hocam, ben de örneğinizi geliştirerek 2x2 genişletilmiş bir kod uygulamasını paylaşmak istiyorum. Sizden farklı olarak oluşturulan değişkenin adresini, üret()içine parametre olarak verdim vebunu global/stack ayrımını vurgulamak için yaptım. Gereksiz yere aynı adresleri göreceksiniz ama durum bu:

int globalDeğişkeni; /* önce global state'de
bir değişkenimiz ve bir de üreteceğimiz şeyi
gösteren bir takma adımız olsun:             */

alias int delegate(int) @system temsilci;
temsilci G; // main() içinde lazım olcak...

temsilci üret(int* i)
{
  writeln("(üret) işlevinde...");
  writefln("i: %s", i);

  return (int j)
  {
    writeln("(temsilci) işlevinde...");
    writefln!"i: %s\nj: %s"(i, &j);
    return *i + j;
  };
}

import std.stdio;
void main()
{
  int stackDeğişkeni; "bu (main)'deki
   ilk değişken:".writeln(&stackDeğişkeni);

  globalDeğişkeni = 1; "bu (main) dışındaki
   ilk değişken:".writeln(&globalDeğişkeni);

  /*************/ writeln("\n"); /**************/

  temsilci m = üret(&stackDeğişkeni);
  writeln("(main) içindeki temsilcinin kapsamı
  (context'i):");
  writeln("@: ", m.ptr);

  auto i = m(int.max);
  assert(i == int.max);

  /*************/ writeln("\n"); /**************/

  G = üret(&globalDeğişkeni);
  writeln("(main) dışındaki temsilcinin kapsamı
  (context'i):");
  writeln("@: ", G.ptr);

  auto ii = G(int.max - 1);
  assert(ii == int.max);
} /* ÇIKTISI:

April 12, 2022

On Tuesday, 12 April 2022 at 07:40:04 UTC, Salih Dincer wrote:

>

bu (main)'deki
ilk değişken:7FFD06363CA0
bu (main) dışındaki
ilk değişken:7F9B09846088

>

(üret) işlevinde...
i: 7FFD06363CA0
(main) içindeki temsilcinin kapsamı
(context'i):
@: 7F9B09745000
(temsilci) işlevinde...
i: 7FFD06363CA0
j: 7FFD06363C18

>

(üret) işlevinde...
i: 7F9B09846088
(main) dışındaki temsilcinin kapsamı
(context'i):
@: 7F9B09745010
(temsilci) işlevinde...
i: 7F9B09846088
j: 7FFD06363C18

Çıktısını unutmuşum, onu da bütünlük olsun diye hemen yukarı alıntı olarak iliştiriyorum. Ayrıca ekstra bir örnek nakledeyim. Sonra isteyen olursa kritik ederiz:

alias int delegate(int) del;

struct MAT(del)
{
  int sonuç;
  del işlem;

  this(int sayı, del işlem)
  {
    this.işlem = işlem;
    this.sonuç = işlem(sayı);
  }
}

void main()
{
  del karesi = (int a) => a*a;
  auto mat = MAT!del(5, karesi);
  assert(mat.sonuç == 25);
*/
April 12, 2022
On 4/12/22 00:40, Salih Dincer wrote:

> int globalDeğişkeni; /* önce global state'de

Bu küçük hatayı ben de yapıyorum ama bunu okuyan C, C++, vs. programcıları varsa o tür değişkenlere global demenin yanıltıcı olacağını hatırlatalım. O değişken bir modül değişkenidir ve hatta her iş parçacığında (thread) farklı bir tane bulunur.

> alias int delegate(int) @system temsilci;

alias'ın görece yeni kullanımı daha mantıklı görünebilir:

alias temsilci = int delegate(int) @system;

Ali

April 12, 2022
On 4/12/22 00:49, Salih Dincer wrote:
> struct MAT(del)

O, değer çeşidinden bir şablon parametresi olmuş ama bu yapıda kullanılmıyor.

Çoğu durumda lambda'lar 'alias' çeşidinden şablon parametresi olarak kullanılıyor:

struct MAT(alias işlem)
{
  import std.traits : ReturnType;
  ReturnType!(typeof(işlem)) sonuç;

  this(T)(T sayı)
  {
    this.sonuç = işlem(sayı);
  }
}

void main()
{
  auto karesi = (int a) => a*a;
  auto mat = MAT!karesi(5);
  assert(mat.sonuç == 25);
}

'karesi' diye farklı değişkene de gerek kalmıyor:

  auto mat = MAT!((int a) => a*a)(5);

ReturnType'ın kullanılabilmesinin nedeni, lambda'da 'int' kullanılmış olmasıdır. Eğer 'a => a * a' olarak yazılsaydı, lambda'nın kendisi şablon olurdu ve ReturnType duruma göre değişirdi ve o yüzden bilinemezdi..

Ali

April 12, 2022

On Tuesday, 12 April 2022 at 16:49:33 UTC, Ali Çehreli wrote:

>

On 4/12/22 00:49, Salih Dincer wrote:

>

struct MAT(del)

O, değer çeşidinden bir şablon parametresi olmuş ama bu yapıda kullanılmıyor.

Hocam kusura bakmayın...

Öylesine ayaküstü, 1-2 dakikada yazılmış bir denemeydi. Kafam karışmış olcak ki istediğim şeyi tam koda dökemedim. Amacım embed and launch function yöntemini icraa etmek. Böyle bir yöntem muhtemelen yok çünkü ben uydurdum :)

Ama ReturnType iyimiş, teşekkürler...

Kodun güncellenmiş hali şöyle:


struct MAT(alias fp)
{
  import std.traits : ReturnType;
  import std.format;

  alias T = ReturnType!(typeof(fp));
  T sonuç;
  typeof(fp) işlem;

  this(T sayı)
  {
    this.işlem = fp;
    this.sonuç = fp(sayı);
  }

  string toString()
  {
    return format("İşlem Sonucu: %d", sonuç);
  }
}

import std.stdio;
void main()
{
  alias karesi = (int a) => a * a;
  auto mat = MAT!karesi(5);

  with(mat) {
    işlem(sonuç).writeln; //625
    işlem = (int a) => 2 ^^ a;
    işlem(9).writeln; // 512

    assert(sonuç == 25);
    sonuç = işlem(8);
    assert(sonuç == 256);
  }
}