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

Dzisiaj o typach składowania parametrów funkcji, nazywanych również atrybutami. Sporo zastanawiałem się, czy opisy umieścić tutaj, jednak stwierdziłem, że chyba łatwiej i czytelniej będzie w postaci komentarzy w kodzie. Mam nadzieję, że taka forma będzie wam odpowiadać.
Omówimy sobie krótko na przykładach trzy typy składowania in, out, oraz ref (lub jak ktoś woli inout (h3r3tic mnie dziś uświadomił, że inout nie jest aktualnie zalecany i że prawdopodobnie zostanie usunięty, lub zmieni się jego semantyka, także lepiej nie używać). Zastanawiałem się też od którego z nich zacząć, zdecydowałem się na in. Jeżeli ktoś nie miał do czynienia z programowaniem wcześniej to jego zachowanie, może wydawać się dość dziwne, w rzeczywistości jest jednak jasne i proste :).

  1.  import tango.io.Stdout;
  2.  
  3.  // atrybut 'in' mówi, że przekazanie nastąpi przez wartość.
  4.  // zmiany na obiekcie, nie będą widoczne w metodzie wywołującej
  5.  // jednakże...
  6.  void test_int(in int x) {
  7.       x = 70;
  8.       Stdout ("int in test_int ") (x).newline;
  9.  }
  10.  
  11.  // ... z powodu 'płytkiego kopiowania', zmiany na elementach
  12.  // przekazywanego obiektu (czyli wiersz z zamianą znaku) będą
  13.  // widoczne w metodzie wywołującej
  14.  void test_func(in char[] a) {
  15.       a ~= " zonk"; // ponieważ w D ciągi znaków są obiektami
  16.           // ta zmiana powoduje zmianę rozmiaru,
  17.           // i doklejenie ciągu 'zonk' (lokalnie!)
  18.           // jednakże nie niszczy obiektu, który
  19.           // otrzymaliśmy przy wywołaniu funkcji
  20.       a[0] = 'X';
  21.       a[1] = 'X';
  22.       a = "blah!".dup; // niszczymy (lokalnie) orginalny obiekt,
  23.       a[2] = 'X'; // więc te podmiany liter, nie będą
  24.       a[3] = 'X'; // widoczne w metodzie wywołującej
  25.       Stdout ("string in func ") (a).newline;
  26.  }
  27.  
  28.  void main() {
  29.       char[] t = "zażółć gęślą jaźń".dup;
  30.       int val=15;
  31.  
  32.       test_func(t);
  33.       Stdout("string in main ") (t).newline;
  34.  
  35.       test_int(val);
  36.       Stdout ("int in main ") (val).newline;
  37.  }
  38.  

Podając do rebuilda opcję -exec spowodujem odpalenie programu zaraz po kompilacj, tak więc:

  • rebuild -clean -exec test_in.d

Krótkie wyjaśnienie do powyższego kodu. W funkcji main, zaraz za ciągiem znaków widać słówko .dup. Powoduje ono utworzenie kopii stringa. Jest tak dlatego, że w linuxie literały stringów, są w pamięci read-only (jedno zdanie można znaleźć tutaj), więc próba modyfikacji elementów takiego stringa, zaowocuje kochanym SIGSEGV. Stąd potrzebujemy wcześniej skopiować ten string :).
Czas na ref, czyli przekazywanie przez referencję.

  1.  import tango.io.Stdout;
  2.  
  3.  // atrybut 'ref' mówi,
  4.  // że przekazanie nastąpi przez referencje, tak więc
  5.  // wszystkie zmiany których dokonamy na przekazywanym
  6.  // obiekcie będą widoczne w metodzie wywołującej.
  7.  void test_int(ref int x) {
  8.       x = 70;
  9.       Stdout ("int in test_int ") (x).newline;
  10.  }
  11.  
  12.  void test_func(ref char[] a) {
  13.       a ~= " zonk";
  14.       a[0] = 'X';
  15.       Stdout ("string in func ") (a).newline;
  16.  }
  17.  
  18.  void main() {
  19.       char[] t = "zażółć gęślą jaźń".dup;
  20.       int val=15;
  21.  
  22.       test_func(t);
  23.       Stdout("string in main ") (t).newline;
  24.  
  25.       test_int(val);
  26.       Stdout ("int in main ") (val).newline;
  27.  }

Chyba nie trzeba nic tłumaczyć :)
Przejdźmy do out. Out jak się można domyślić oznacza, że parametr jest tylko parametrem wyjściowym, to znaczy że dana funkcja czy metoda ma w nosie wartość jaką tam podamy. Jest przy tym jedna ważna cecha, ale to w komentarzach.

  1.  import tango.io.Stdout;
  2.  
  3.  // najciekawszy atrybut czyli 'out', przekazywanie następuje
  4.  // przez referencje, jednakże, zanim obiekt trafi do metody
  5.  // następuje jego inicjalizacja do wartości domyślnej.
  6.  void test_int(out int x) {
  7.       // zmianna typu int jest domyślnie inicjalizowana 0
  8.       Stdout ("int in test_int ") (x).newline;
  9.  }
  10.  
  11.  void test_func(out char[] a) {
  12.       a ~= " zonk"; // ponieważ na początku zmienna a
  13.           // jest inicjalizowana na pusty ciag,
  14.           // to doklejenie spowoduje tak sklejenie
  15.       // "" razem z " zonk"
  16.       Stdout ("string in func ") (a).newline;
  17.  }
  18.  
  19.  void main() {
  20.       char[] t = "zażółć gęślą jaźń".dup;
  21.       int val=15;
  22.  
  23.       test_func(t);
  24.       Stdout("string in main ") (t).newline;
  25.  
  26.       test_int(val);
  27.       Stdout ("int in main ") (val).newline;
  28.  }
  29.  

Najpierw kawałek kodu, a poniżej opis, jakie zostawiam zadanie ;)

  1.  import tango.io.Stdout;
  2.  
  3.  void test_a() { Stdout ("To jest test_AAA").newline; }
  4.  void test_X() { Stdout ("To jest test_XXX").newline; }
  5.  
  6.  void test_change(in void function() x) {
  7.       Stdout("wywolanie pierwsze w change").newline;
  8.       x();
  9.       x = &test_X;
  10.       Stdout("wywolanie drugie w change").newline;
  11.       x();
  12.  }
  13.  
  14.  void main() {
  15.       void function() a = &test_a;
  16.  
  17.       test_change(a);
  18.       Stdout("wywolanie w main ").newline;
  19.       a();
  20.  }

Małe wyjaśnienie. W main widać deklarację zmiennej a typu void function(), czyli wskaźnik na funkcję typu void, nie przyjmującą żadnych parametrów. Stąd przed test_a pojawia się &, czyli pobranie adresu funkcji test_a.
Podobnie funkcja test_change przyjmuje parametr typu wskaźnik na funkcję (typu void, która nie przyjmuje parametrów). Zadanie jest bardzo skomplikowane, mianowicie potestować, jak ten program będzie się zachowywał, gdy podmienimy atrybut pierwszego parametru funkcji test_change na inoutref oraz out.
Po zrobieniu zadania można zerknąć na ostatni przykład.

  1.  module baker;
  2.  
  3.  import tango.io.Stdout;
  4.  
  5.  void baker(inout int b)
  6.  {
  7.       assert(b < 0x10000, " RANY! b jest większe od 65535! co my teraz zrobimy?! ");
  8.       b = 666;
  9.  }
  10.  
  11.  int main()
  12.  {
  13.       int x = 0x5ac4;
  14.  
  15.       Stdout.format ("{:x}", x).newline;
  16.  
  17.       baker(x);
  18.       Stdout.format ("{:x}", x).newline;
  19.  
  20.       x = 0x12345;
  21.       baker(x);
  22.       Stdout.format ("{:x}", x).newline;
  23.  
  24.       return 0;
  25.  }

Jedynym nowym elementem, który się tu pojawia jest assert. assert podobnie jak w C sprawdza, czy podany warunek jest prawdziwy. Jeśli nie jest, 'rzuca' on wyjątkiem (który można oczywiście przechwycić).
assert(0); można używać, do oznaczania fragmentów kodu, do których program nie powinien dotrzeć. Pierwszą myślą jest, "jak to możliwe, jak może być miejsce, gdzie program ma nie dotrzeć?", jednak chociażby w przypadku budowania bardziej skomplikowanych parserów, jest to dość przydatne. Więcej na temat Contract Programming w którymś z kolejnych odcinków

Do powyższego przykładu, proszę spróbować zmienić inout w funkcji baker, na out i sprawdzić (albo jeszcze lepiej przewidzieć) jaki będzie wynik działania.

Omówiłem tylko trzy podstawowe typy składowania, jest ich jednak więcej i mają różne ciekawe zastosowania. Zaciekawionych odsyłam do dokumentacji:
invariant, const oraz final (const w D jest typem składowania, a nie atrybutem dla typu, więcej na ten temat oraz porównanie do C++ tutaj
lazy (warto wspomnieć, że typ składowania lazy został zasugerowany przez Tomasza 'h3r3tic'a Stachowiaka z Team0xf).
Podane w linkach typy składowania, pewnie pojawią się kiedyś w przykładach ;)

catz: [kom.puterowe] [programowanie w D] [Techblog]
tagz: [D] [D programming language] [in] [inout] [język D] [out] [ref] [storage class]
dnia sobota, 10 listopad 2007, 153155 by Michał 'GiM' Spadliński

Komentarze:

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

Kolejny bardzo ciekawy wpis, gratuluje. Muszę przyznać na boku, że po przeczytaniu artykułu poczytałem trochę o lazy i delegates i stwierdzam, że D rządzi :-)

dnia niedziela, 11 listopad 2007, 113445 by deely

The D part 5 - wykonywanie w czasie kompilacji, tablice asocjacyjne, statyczne konstruktory

Ten wpis miał być, dokończeniem na temat kontraktów (design by contract), ale ponieważ akurat o tym było na
drugich warsztatach, więc odsyłam tam :).
Zacznijmy od czegoś prostego, jak w D zadeklarować i zainicjalizować tablicę? Stw

dnia środa, 14 listopad 2007, 195215 by gim

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