4 grudnia 2008

Błędy w OCamlu

Programy w OCamlu pisze się bardzo przyjemnie. Schody zaczynają się gdy OCaml chce nas poinformować, że zrobiliśmy błąd. Często robi to tak nieudolnie, że bardzo trudno jest błąd nawet zlokalizować. Dziś przez pół godziny szukałem z Maćkiem błędu w implementacji Grafu. Błąd znaleźliśmy, ale w zupełnie innym module niż ten na który wskazywał OCaml.
Dziś rano z kolei OCaml powitał mnie komunikatem
File "Database.ml", line 54, characters 17-25:
This pattern matches values of type punkt
but is here used to match values of type punkt

Bardzo zdziwiłem się widząc ten komunikat. I chyba nie znalazłbym błędu, gdybym nie widział podobnego błędu wcześniej. Troszkę starań i OCaml wypluje nam taki komunikat:
This expression has type t but is here used with type t

Jak zmusić OCamla do wyplucia takiego błędu? Opiszemy za chwilę. Na razie spróbujmy zrozumieć co tak na prawdę OCaml ma na myśli. Rozważmy następującą sesję ewaluatora:
# type typ1 = Konstruktor | Innykonstruktor;;
type typ1 = Konstruktor | Innykonstruktor

# Konstruktor;;
- : typ1 = Konstruktor

# type typ2 = Konstruktor | Jeszczeinnykonstruktor;;
type typ2 = Konstruktor | Jeszczeinnykonstruktor

# Konstruktor;;
- : typ2 = Konstruktor
W trzecim poleceniu do zdefiniowania typu "typ2" użyliśmy ponownie konstruktora "Konstruktor". Przesłoniliśmy w ten sposób konstruktor o tej samej nazwie z typu pierwszego. Nie będziemy mogli już z niego normalnie korzystać. Gdy użyjemy teraz w dowolnym miejscu konstruktora "Konstruktor" zostanie on potraktowany jako konstruktor z typu drugiego (a nie jako konstruktor typu pierwszego).
# type t = Konstruktor of int;;
type t = Konstruktor of int

# let get_int a = match a with Konstruktor b -> b;;
val get_int : t -> int =

# type t = Konstruktor of int;;
type t = Konstruktor of int

# let osiem = Konstruktor 8;;
val osiem : t = Konstruktor 8

# get_int osiem;;
This expression has type t but is here used with type t
Przeanalizujmy co tutaj się stało. W pierwszej linijce tworzymy nowy typ t. W drugiej linijce definiujemy funkcję, która jako argument bierze typ t - ten z pierwszej linijki (mniejsza o to co ta funkcja robi). W linijce trzeciej tworzymy nowy typ t (ma on nawet tą samą definicję co nasz pierwszy typ t - mimo to będzie traktowany jako nowy typ). Przesłaniamy w ten sposób pierwszy typ t. W linijce czwartej definiujemy wartość "osiem" używając konstruktora "Konstruktor". Jak wiemy z poprzedniego akapitu - będzie to wartość typu t zadeklarowanego w 3 linijce. Na koniec próbujemy wywołać funkcję od wartości "osiem", która jest typu t (zdefiniowanego w linijce 3) podczas gdy funkcja przyjmuje argumenty typu t (zdefiniowane w linijce pierwszej).

Brak komentarzy: