[GiM logo] gim.org.pl is down || odświeżony jogger (v.0.4) GiMa

D
Dawno nic się tu nie działo, głównie dlatego, że nie bardzo mam czas cokolwiek pisać.
Deely pisał jakiś czas temu o wyrażeniach lambda w C#. Dzisiaj trochę o różnych bajerach jakie można robić w D.
Na początek zrobimy sobie małe opakowanie na foreach. Jak zwykle najpierw kawałek kodu, potem jak zwykle pseudo wyjaśnienia.

  1.  module delegatesexample;
  2.  import tango.io.Stdout;
  3.  
  4.  void blink(T, U)(T t, bool delegate(ref U) cond, void delegate(ref U) act)
  5.  {
  6.       foreach (ref a; t)
  7.         if (cond(a))
  8.           act(a);
  9.  }
  10.  
  11.  void main()
  12.  {
  13.     int[] x = [1,2,3,4,5,6,7,8,9];
  14.  
  15.     blink(x,
  16.       (ref int b){ return b%2 == 0; },
  17.       (ref int b){ Stdout(b).newline; b+=8; } );
  18.  
  19.     Stdout ("----------").newline;
  20.  
  21.     foreach (a; x)
  22.     {
  23.       Stdout (a).newline;
  24.     }
  25.  }
  26.  

No więc o co tu biega? W liniach 4-9 tworzymy sobie szablon funkcji, która przyjmuje: obiekt generycznego typu T i dwie delegatki różniące się zwracanym typem i przyjmujące. jako jedyny argument zmienną generycznego typu U. Następnie w main po deklaracji i inicjalizacji tablicy intów x mamy (instancjację szablonu i) wywołanie blink. Tak na prawdę stworzenie instancji naszego szablonu i wywołanie funkcji powinno wyglądać tak:

  1.     blink!(int[], int)(x,
  2.       bool(ref int b){ return b%2 == 0; },
  3.       void(ref int b){ Stdout(b).newline; b+=8; } );

Jednak ponieważ (co już mówiłem) D jest językiem statycznie typowanym, kompilator na podstawie przekazanych argumentów jest w stanie rozpoznać, że T i U powinny być odpowiednio typów int[] (ponieważ x jest typu int[]) oraz int (na podstawie sygnatur delegatek) i stworzyć odpowiednią instancję szablonu.
A dlaczego możemy pominąć bool i void? To również proste, jeżeli delegatka jest typu void, to nie stanowi to problemu i możemy spokojnie pominąć to słowo, w drugim przypadku natomiast kompilator jest w stanie rozpoznać jaki jest zwracany przez delegatkę typ na podstawie tego co jest w return

A jaki jest efekt działania? Wystarczy spojrzeć na definicję blink, na elementach t (zakładamy, że t jest kontenerem, czy innym obiektem, na którym możemy wywołać foreach) zostanie najpierw wywołana pierwsza delegatka, jeśli zwraca ona true, zostanie wywołana druga.
W praktyce, wybrane zostaną elementy parzyste tablicy x, wypisane, i każdy parzysty element zostanie zwiększony o 8.
Wynik działania na codepad.org - delegatesexample.

Ok, to było na rozgrzewkę. Załóżmy że mamy klasę która jest kontenerem dla pewnego typu obiektów, chcielibyśmy mieć możliwość wykonywania pętli na obiekcie tej klasy. Żeby to zrobić musimy w klasie stworzyć metodę opCall...

  1.  module iteroverobject1;
  2.  import tango.io.Stdout;
  3.  import tango.math.Random;
  4.  
  5.  class Kontener
  6.  {
  7.     private int[] data;
  8.     this (int n) {
  9.       data = new int[n];
  10.       foreach (ref x; data)
  11.         x = Random.shared.next(100);
  12.     }
  13.     int opApply(int delegate(ref int) dg)
  14.     {
  15.       int res;
  16.       foreach (ref a; data)
  17.       {
  18.         if ( (res = dg(a)) != 0 )
  19.           break;
  20.       }
  21.       return res;
  22.     }
  23.  }
  24.  
  25.  void main()
  26.  {
  27.     Kontener kon = new Kontener(10);
  28.     foreach (a; kon)
  29.       Stdout (a) (" ");
  30.     Stdout.newline;
  31.  }

W linii 27 tworzymy obiekt typu Kontener i do konstruktora przekazujemy 10. To spowoduje (linie 8-12) stworzenie 10 elementowej tablicy intów i zainicjalizowanie tej tablicy losowymi wartościami. Następnie w main w liniach 28,29 iterujemy po właśnie stworzonym obiekcie. Tak na prawdę powoduje to wywołanie metody opCall tego obiektu, gdzie delegatką jest tak na prawdę to co jest ciałem pętli foreach (czyli w tym przykładzie pojedyńcza linia 29).
Założenie jest takie, że metoda opCall musi iterować po elementach, dla których aktualny obiekt jest kontenerem i na każdym elemencie wywoływać delegatkę która przyszła jako argument. Proste prawda? :) To że delegatka jest typu int i że opApply zwraca w efekcie to co zwraca delegatka, to nie mój pomysł, tak po prostu musi być :].
WAŻNE: wewnątrz opCall NIE MOŻNA dodawać, usuwać, ani zmieniać samego kontenera, oczywiście same elementy możemy zmieniać :].
W którymś z wcześniejszych artykułów wspominałem zdaje się, że jest cośtakiego jak foreach_reverse, jeżeli chcemy by działało, należy stworzyć metodę opCallReverse (intuicyjnie, nie? :>)
Wynik działania na codepad.org - iteroverobject1.

Ok, skoro już to mamy to możemy bez problemu połączyć pierwszy przykład z drugim.

  1.  module iteroverobject2;
  2.  import tango.io.Stdout;
  3.  import tango.math.Random;
  4.  
  5.  void blink(T, U)(T t, bool delegate(ref U) cond, void delegate(ref U) act)
  6.  {
  7.       foreach (ref a; t)
  8.         if (cond(a))
  9.           act(a);
  10.  }
  11.  
  12.  class Kontener
  13.  {
  14.     private int[] data;
  15.     this (int n) {
  16.       data = new int[n];
  17.       foreach (ref x; data)
  18.         x = Random.shared.next(100);
  19.     }
  20.     int opApply(int delegate(ref int) dg)
  21.     {
  22.       int res;
  23.       foreach (ref a; data)
  24.       {
  25.         if ( (res = dg(a)) != 0 )
  26.           break;
  27.       }
  28.       return res;
  29.     }
  30.  }
  31.  
  32.  void main()
  33.  {
  34.     Kontener kon = new Kontener(10);
  35.     foreach (a; kon)
  36.       Stdout (a) (" ");
  37.     Stdout.newline;
  38.  
  39.     blink(kon,
  40.       (ref int b){ return b%2 == 0; },
  41.       (ref int b){ Stdout(b).newline; b+=8; } );
  42.  
  43.     Stdout ("----------").newline;
  44.  
  45.     foreach (a; kon)
  46.     {
  47.       Stdout (a).newline;
  48.     }
  49.  }
  50.  

Jedyne co wymaga tu skomentowania, to fakt, że kompilator tym razem rozpozna, że w instancji szablonu blink T i U będą odpowiednia typów Kontener oraz int. Reszta powinna być jasna.
Wynik działania na codepad.org - iteroverobject2

Na koniec najciekawszy przykład, stworzymy szablon, który dziedziczy po szablonie ArraySeq (port jednego z kontenerów stworzonych przez Doug'a Lea). Następnie stworzymy zagnieżdżoną klasę, która będzie umożliwiać tworzenie filtrów. Nie czytaj na razie kodu samej klasy, tylko przejdź niżej do funkcji main...

  1.  module filteringcontainers;
  2.  import tango.io.Stdout;
  3.  import tango.util.collection.ArraySeq;
  4.  
  5.  class GArraySeq(T) : ArraySeq!(T)
  6.  {
  7.     FilteredList createFilter (int delegate(ref T) filterExpr)
  8.     {
  9.       return new FilteredList(this, filterExpr);
  10.     }
  11.     private class FilteredList
  12.     {
  13.       private GArraySeq!(T) parentlist;
  14.       private int delegate(ref T) filter;
  15.  
  16.       this (GArraySeq!(T) t, int delegate(ref T) filterExpr)
  17.       {
  18.         this.parentlist = t;
  19.         this.filter = filterExpr;
  20.       }
  21.  
  22.       int opApply(int delegate(ref T) iter)
  23.       {
  24.         int ret;
  25.         foreach (ref elem; this.parentlist)
  26.         {
  27.           if (this.filter(elem))
  28.           {
  29.           if ((ret = iter(elem)) != 0)
  30.               break;
  31.           }
  32.         }
  33.         return ret;
  34.       }
  35.     }
  36.  }
  37.  alias GArraySeq!(int) List;
  38.  
  39.  void main()
  40.  {
  41.     auto list = new List;
  42.  
  43.     foreach (i; [9, 8, 7, 6, 5, 4, 3, 2, 1])
  44.       list.append(i);
  45.  
  46.     foreach (i; list)
  47.         Stdout (i) (", ");
  48.     Stdout.newline;
  49.  
  50.     auto filtered = list.createFilter (
  51.       (ref int x){
  52.         if (x % 2) return 0;
  53.         return 1;
  54.       });
  55.  
  56.     foreach (ref i; filtered)
  57.         i += 10;
  58.  
  59.     foreach (i; list)
  60.         Stdout (i) (", ");
  61.     Stdout.newline;
  62.  
  63.  }
  64.  

W linii 37 tworzymy sobie alias, żeby nie musieć cały czas klepać GArraySeq!(int). Tworzymy nową listę, dodajemy jej kilka elementów. wypisujemy elementy.
W liniach 50-54 tworzymy sobie filtr, widać, że jako argument, metoda createFilter przyjmuje delegatkę przyjmującą int i zwracającą int (na podstawie returnów).
Dalej mamy iterację po elemenetach stworzonego filtra i zwiekszanie elementów.
Na koniec wypisanie wszystkich elementów, by zobaczyć wynik działania.

Teraz możemy spojrzeć na samą klasę, dodana metoda createFilter zwraca obiekt typu FilteredList (7-10), widać, że podklasa, zapamiętuje sobie przekazany obiekt i delegatkę która będzie filtrem (16-20).
Widać, że klasa FilteredList zapewnia opApply więc po obiekcie tej klasy będziemy mogli iterować (main 56-57) według tego co mówiłem wcześniej linie te zostaną zamienione na coś takiego:

  1.   filtered.opApply(int(ref int i){ i += 10; });

opApply w klasie FilteredList, iteruje po elementach obiektu parentlist, który jest typu GArraySeq!(T), oczywiście widać, że sam szablon klasy GArraySeq nie zawiera metody opApply, ale zawiera ją szablon klasy ArraySeq, po której dziedziczymy, także nie mamy się czym przejmować :).
Na elementach wywoływany jest filtr, jeśli zwraca coś != 0, dopiero wtedy wywoływana jest przekazana do opApply delegata, czyli to co było w bloku foreach w main:

  1.   i += 10;

Niestety z niewiadomych mi przyczyn, ten przykład nie działa na codepad :)
Nie muszę chyba tłumaczyć, że na obiekcie filtered, można wywołać blah z poprzednich przykładów...

To wszystko na dzisiaj, następny wpis, kiedyśtam, raczej nieprędko.

catz: [kom.puterowe] [programowanie w D] [Techblog]
tagz: [D] [D programming language] [język D] [programowanie D]
dnia piątek, 25 kwiecień 2008, 140108 by Michał 'GiM' Spadliński

Komentarze:

Proszę wpisy pisane po angielsku komentować również w tym języku.

<offtopic>
O, to jednak ktoś pisze o D na joggerze ;-)
Zaraz sobie bloga zasubskrybuję.

PS. fajnie by było, jakbyś robił jakieś ładne wcięcia w kodach - obecna forma to tragedia ;-) Aha i nie wiem czy wiesz, ale na joggerze mamy Geshi do kolorowania składni

{geshi lang=d}
// kod ...
{/geshi}

</offtopic>

dnia piątek, 25 kwiecień 2008, 141148 by coldpeer

//edit: o, widzę poprawiłeś wcięcia w czasie, kiedy pisałem komentarz ;) Co nie zmienia faktu, że i tak geshi to ładniejsze i szybsze rozwiazanie (taka rada dla Ciebie, ułatwi Ci życie)

dnia piątek, 25 kwiecień 2008, 141253 by coldpeer

dzieki za tip, aktualnie jestem jakby nie na bieżąco z joggerem.

dnia piątek, 25 kwiecień 2008, 142745 by GiM

Czyli jednak pogloski ze GiM przeszedl (-dzil) na D wcale nie byly mocno przesadzone (via komentarz coldpeera)

dnia piątek, 25 kwiecień 2008, 215721 by darkjames

@dj: (hi!) no co ty, jezyka się nie zmienia, po prostu z rzeczy którymi się zajmuje, myślę, że D jest jedną z niewielu która może zainteresować szersze grono.

dnia piątek, 25 kwiecień 2008, 220011 by GiM

..tożsamość..:
..meritum..:
..lokum..:
Wpisz kod:code