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.
- module delegatesexample;
- import tango.io.Stdout;
- void blink(T, U)(T t, bool delegate(ref U) cond, void delegate(ref U) act)
- {
- foreach (ref a; t)
- if (cond(a))
- act(a);
- }
- void main()
- {
- int[] x = [1,2,3,4,5,6,7,8,9];
- blink(x,
- (ref int b){ return b%2 == 0; },
- (ref int b){ Stdout(b).newline; b+=8; } );
- Stdout ("----------").newline;
- foreach (a; x)
- {
- Stdout (a).newline;
- }
- }
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:
- blink!(int[], int)(x,
- bool(ref int b){ return b%2 == 0; },
- 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...
- module iteroverobject1;
- import tango.io.Stdout;
- import tango.math.Random;
- class Kontener
- {
- private int[] data;
- this (int n) {
- data = new int[n];
- foreach (ref x; data)
- x = Random.shared.next(100);
- }
- int opApply(int delegate(ref int) dg)
- {
- int res;
- foreach (ref a; data)
- {
- if ( (res = dg(a)) != 0 )
- break;
- }
- return res;
- }
- }
- void main()
- {
- Kontener kon = new Kontener(10);
- foreach (a; kon)
- Stdout (a) (" ");
- Stdout.newline;
- }
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.
- module iteroverobject2;
- import tango.io.Stdout;
- import tango.math.Random;
- void blink(T, U)(T t, bool delegate(ref U) cond, void delegate(ref U) act)
- {
- foreach (ref a; t)
- if (cond(a))
- act(a);
- }
- class Kontener
- {
- private int[] data;
- this (int n) {
- data = new int[n];
- foreach (ref x; data)
- x = Random.shared.next(100);
- }
- int opApply(int delegate(ref int) dg)
- {
- int res;
- foreach (ref a; data)
- {
- if ( (res = dg(a)) != 0 )
- break;
- }
- return res;
- }
- }
- void main()
- {
- Kontener kon = new Kontener(10);
- foreach (a; kon)
- Stdout (a) (" ");
- Stdout.newline;
- blink(kon,
- (ref int b){ return b%2 == 0; },
- (ref int b){ Stdout(b).newline; b+=8; } );
- Stdout ("----------").newline;
- foreach (a; kon)
- {
- Stdout (a).newline;
- }
- }
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...
- module filteringcontainers;
- import tango.io.Stdout;
- import tango.util.collection.ArraySeq;
- class GArraySeq(T) : ArraySeq!(T)
- {
- FilteredList createFilter (int delegate(ref T) filterExpr)
- {
- return new FilteredList(this, filterExpr);
- }
- private class FilteredList
- {
- private GArraySeq!(T) parentlist;
- private int delegate(ref T) filter;
- this (GArraySeq!(T) t, int delegate(ref T) filterExpr)
- {
- this.parentlist = t;
- this.filter = filterExpr;
- }
- int opApply(int delegate(ref T) iter)
- {
- int ret;
- foreach (ref elem; this.parentlist)
- {
- if (this.filter(elem))
- {
- if ((ret = iter(elem)) != 0)
- break;
- }
- }
- return ret;
- }
- }
- }
- alias GArraySeq!(int) List;
- void main()
- {
- auto list = new List;
- foreach (i; [9, 8, 7, 6, 5, 4, 3, 2, 1])
- list.append(i);
- foreach (i; list)
- Stdout (i) (", ");
- Stdout.newline;
- auto filtered = list.createFilter (
- (ref int x){
- if (x % 2) return 0;
- return 1;
- });
- foreach (ref i; filtered)
- i += 10;
- foreach (i; list)
- Stdout (i) (", ");
- Stdout.newline;
- }
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:
- 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:
- 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.
gim.org.pl is down






