To jest stara wersja strony!


Opis

Michał Kołodziej, kolodziej.michal@gmail.com

Read on the Mercury language. Describe concepts, examples, compare to Prolog and Haskell.

haskell

Spotkania

20090416

20090326

Projekt

Introduction

What is Mercury?

From: „Mercury Language Reference”, introduction:

Mercury is a new general-purpose programming language, designed and implemented by a small group of researchers at the University of Melbourne, Australia. Mercury is based on the paradigm of purely declarative programming, and was designed to be useful for the development of large and robust “real-world” applications. It improves on existing logic programming languages by providing increased productivity, reliability and efficiency, and by avoiding the need for non-logical program constructs. Mercury provides the traditional logic programming syntax, but also allows the syntactic convenience of user-defined functions, smoothly integrating logic and functional programming into a single paradigm.

Mercury requires programmers to supply type, mode and determinism declarations for the predicates and functions they write. The compiler checks these declarations, and rejects the program if it cannot prove that every predicate or function satisfies its declarations. This improves reliability, since many kinds of errors simply cannot happen in successfully compiled Mercury programs. It also improves productivity, since the compiler pinpoints many errors that would otherwise require manual debugging to locate. The fact that declarations are checked by the compiler makes them much more useful than comments to anyone who has to maintain the program. The compiler also exploits the guaranteed correctness of the declarations for significantly improving the efficiency of the code it generates.

To facilitate programming-in-the-large, to allow separate compilation, and to support encapsulation, Mercury has a simple module system. Mercury's standard library has a variety of pre-defined modules for common programming tasks — see the Mercury Library Reference Manual.

Why Mercury?

Mercury is developed to solve to main problems that appears in another logic programming languages (e.g. Prolog):

  • poor error detection at compile time, and
  • low performance

Mercury language features

  • purely declarative: predicates and functions in Mercury do not have non-logical side effects
  • strongly typed
  • strongly moded
  • strong determinism system
  • module system
  • supports higher-order programming, with closures, currying, and lambda expressions

Mercury OS platforms

Mercury should work on following platforms:

  • x86 machines running Debian Linux
  • x86 machines running Microsoft Windows XP
  • x86 machines running Solaris 9 (SunOS 5.9)
  • x86_64 machines running Debian Linux
  • Apple PowerPC machines running Mac OS 10.3 and above

Mercury interoperability

The Mercury implementation compiles to a wide variety of target languages on a wide variety of platforms. Target language back-ends include:

Mature:

  • Low level C
  • High level C

Alpha- or beta-release quality:

  • Microsoft's .NET
  • Native code
  • Java

Sprawozdanie

To sprawozdanie zostało napisane w maju 2009 r.

Jak zacząć?

Informacje dotyczące instalacji kompilatora, kompilowania i uruchamiania programów napisanych w Mercurym pod Windows XP

Do pobrania

Aby zacząć pracę z Mercurym i Haskellem należy pobrać następujące pliki:

Instalacja Cygwin

Należy uruchomić instalator Cygwina (setup.exe). Postępować zgodnie ze wskazówkami i zainstalować pakiety opisane w poprzedniej sekcji. W dalszej części zakładał będę, że Cygwin został zainstalowany w katalogu „C:/cygwin”, natomiast katalog domowy użytkownika jest „C:\cygwin\home\USER_NAME”.

Instalacja Haskell

Uruchomić instalator Haskell Platform, postępować zgodnie z instrukcjami. Zakładam że Haskell został zainstalowany do folderu „C:\Program Files\Haskell Platform”

Instalacja Mercury

Szczegółowe informacje o instalacji z kodu źródłowego można znaleźć w archiwum kompilatora, w pliku README. Poniżej przedstawione zostały kroki potrzebne do typowej instalacji kompilatora:

  • rozpakować archiwum tar.gz np. do katalogu „~/USER_NAME/tymczasowy”,
  • uruchomić powłokę cygwina,
  • wywołać komendę
    cd tymczasowy
  • wywołać komendę
    cd mercury-compiler-0.13.1
  • wywołać komendę
    sh configure && make && make install

    ja użylem

    sh configure --disable-most-grades && make && make install

    w celu przyśpieszenia instalacji. (Krok ten może potrwać na prawdę długo!),

  • jeśli instalacja odbędzie się pomyślnie, kompilator Mercury zostanie zainstalowany do katalogu „C:/cygwin/usr/local/mercury-0.13.1”

Żeby móc używać kompilatora mercury:

  • z poziomu linii komend systemu Windows XP należy dodać do ścieżki systemu Windows XP (Panel sterowania → System → zakładka Zaawansowane → przycisk Zmienne środowiskowe → grupa zmienne systemowe, zaznaczyć Path, przycisk Edycja) katalogi „C:/cygwin/usr/local/mercury-0.13.1/bin” oraz „C:/cygwin/bin” (kompilator Mercurego potrzebuje dostępu do kompilatora gcc)
  • z powłoki cygwina, należy dodać do pliku .bashrc, znajdującego się w katalogu home lub użytkownika („C:/cygwin/home” lub „C:/cygwin/home/USER_NAME”, jeżeli nie ma takiego pliku, należy go utworzyć) linię kodu:
    PATH=/usr/local/mercury-0.13.1/bin:$PATH

Kompilowanie i uruchamianie programów w Mercurym

Aby skompilować program w pliku nazwa_pliku_źródłowego.m należy:

  • albo w linii komend Windows XP kompilator wywołuje się komendą
    mercury --make nazwa_pliku_źródłowego
  • albo w powłoce Cygwin'a kompilator wywołuje się komendą
    mmc --make nazwa_pliku_źródłowego

Zostanie utworzony plik wykonywalny nazwa_pliku_źródłowego.exe. Do jego uruchomienia potrzebna jest biblioteka cygwin1.dll, którą należy skopiować z katalogu bin Cygwina („C:/cygwin/bin/cygwin1.dll”), do katalogu w którym znajduje się plik wykonywalny.

Opis głównych cech języków Marcury i Haskell

W tej części przedstawię oraz porównam podstawowe elementy języków Mercury i Haskell. W poprzednich częściach przedstawiłem narzędzia potrzebne do pracy z językami, teraz zaczniemy od stworzenia możliwie najprostszego programu, który może zostać uruchomiony pod Windows'em

Hello world!

Poniżej jest pokazany program w Mercurym, który umożliwia interakcyjną pracę ze światem zewnętrznym. Tak się składa, że ten prosty program wykorzystuje pewne bardziej zaawansowane koncepcje języka mercury, których później zostaną omówione.

% helloWorld.m
%-----------------------%
% KOMPILACJA:
% > mercury helloWorld
%

:- module helloWorld.
/* wieloliniowy komentaz 
   blokowy
*/
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module char, list, string.

	main(!IO) :-
        io.write_string("Hello, World!\n", !IO),
		io.read_char(_, !IO).

:- end_module helloWorld.

Aby skompilować ten program należy w powłoce cygwina wykonać:

$ mmc --make helloWorld

lub w powłoce Windows

mercury helloWorld

Program drukujący powitanie, w Haskell, wygląda następująco:

-- file: hello.hs
 
module Main where 
 
main :: IO ()
main = do
       putStrLn "Hello World!"
       getLine;
	   return ()

Aby skompilować i stworzyć plik wykonawczy należy użyć polecenia:

ghc -o helloNazwaPlikuWykonywalnego hello.hs

Aby stworzyć plik wykonawczy plik z kodem musi zawierać moduł „Main” oraz funkcję main.

Typy i wartości

W Mercurym wszystkie zmienne muszą zaczynać się z dużej litery (w przeciwieństwie do Haskell'a, gdzie wszystkie typy muszą zaczynać się z dużej litery). Nazwy modułów, typów, predykatów, funkcji itp. zaczynają się z małej litery.

Funkcje, predykaty, tuple, typy są wyrażeniami tak jak typy podstawowe. Mogą one być przekazywane do innych funkcji, predykatów, podobnie jak w Haskellu. Typy, funkcje i predykaty mogą być polimorficzne, również podobnie jak w Haskellu.

  • Podstawowe typy:
    • 5:integer
    • 'a':char
    • „abc”:string
    • 3.1415:float
  • Predykaty:
    • pred, pred(T), pred(T1, T2)
  • Funkcje:
    • func(T1, T2) = T
    • func int = int
  • Listy:
    • list(int)
  • Tuple:
    • {}, {T}, {T1, T2}, ….
  • Typy dynamiczne:
    • univ
  • Typy stanu świata (niedeterministyczne):
    • io.state

Typy użytkownika

Nowy typ deklaruje się za pomocą słów kluczowych „:- type”. Możliwe jest definiowanie typów enumerycznych jak i rekordów. Definicja typu następuje po słowie kluczowym „—>”.

:- type tree
             --->    empty
             ;       leaf(int)
             ;       branch(tree, tree).
:- type poliTree(T)
             --->    empty
             ;       leaf(T)
             ;       branch(poliTree(T), poliTree(T)).
:- type employee
             --->    employee(
                            name        :: string,
                            age         :: int,
                            department  :: string
                     ).
:- type list(T)
             --->    []
             ;       [T | list(T)].

W Hasekllu typy użytkownika deklaruje się za pomocą słowa kluczowego „data” i listy konstruktorów oddzielonych „|”. Podobnie jak w Mercurym.

data Tree        = Empty | Leaf Int | Branch Tree Tree
data PoliTree a  = Empty | Leaf a | Branch (PoliTree a) (PoliTree a)
data Employee    = Employee {name :: String, age :: Int, department :: String}
data List a      = Nil | Cons a (List a)

W Mercurym istnieją dodatki syntaktyczne umożliwiające manipulacje polami rekordów (Haskell również wspiera tą funkcjonalność, jednak nie można tworzyć własnych funkcji :-( ):

% Field selection for maps.
% Map ^ elem(Key) = map.search(Map, Key).
%
:- func map.elem(K, map(K, V)) = V is semidet.

% Field update for maps.
% (Map ^ elem(Key) := Value) = map.set(Map, Key, Value).
%
:- func 'elem :='(K, map(K, V), V) = map(K, V).

%uzycie:
% inicjalizowanie mapy
X = map.insert(map.insert(map.init, "Mercury", 1), "Haskell", 2),
% zmiana pola
Y = X^elem("Mercury") := 2,
% odczytanie pola
WhoWins = Y ^ elem("Haskell")

Typy równoważne

Mercury podobnie jak Haskell wspiera definiowanie typów równoważnych. Typy równoważne są definiowane po słowie kluczowym „==”:

:- type newInt == int.

Składnia Haskella:

type NewInt = Int

Listy

W Mercurym konstruktorem listy jest „|”, nie jak w Haskellu „:”. Dodatkowo listy nie należą do podstawowej biblioteki Mercurego (trzeba je importować „:- import_module list”)

Czy są w Mercurym List Comprehensions and Arithmetic Sequences? e.g.

Haskell oferuje pewne dodatki syntaktyczne dla list:

quicksort  []           =  []
quicksort (x:xs)        =  quicksort [y | y <- xs, y<x ]
                        ++ [x]
                        ++ quicksort [y | y <- xs, y>=x]
 
qsort2 :: Ord a => [a] -> [a]
qsort2 []     = []
qsort2 (x:xs) = qsort2 lesser ++ equal ++ qsort2 greater
    where
        (lesser,equal,greater) = part x xs ([],[x],[])
 
part :: Ord a => a -> [a] -> ([a],[a],[a]) -> ([a],[a],[a])
part _ [] (l,e,g) = (l,e,g)
part p (x:xs) (l,e,g)
    | x > p     = part p xs (l,e,x:g)
    | x < p     = part p xs (x:l,e,g)
    | otherwise = part p xs (l,x:e,g)

niestety brakuje ich w Mercurym (kod ze strony http://en.literateprograms.org/Quicksort_(Mercury)):

:- module quicksort.
:- interface.
:- import_module list, int.
:- func qsort(list(int)) = list(int).
:- mode qsort(in) = out is det.
:- implementation.
%
% quicksort 
%
qsort( []) = [].
qsort( [Hd | Tl]) = Result :- (
        /* length 1 */
	Tl = [] -> Result = [Hd] 
      ;
        /* length 2 */
	Tl = [Hd2] -> ( 
	                  Hd =< Hd2 -> Result = [Hd | [Hd2]] 
                         ;
			  Result = [Hd2 | [Hd]]
	                ) 
      ;
	/* else */
        InputList = [Hd | Tl],
	Pivot = list.index0_det( InputList, list.length( InputList) / 2),
	pivot_classify( InputList, Pivot, Lows, Mids, Highs),
	Result = list.append( list.append( qsort(Lows), Mids), qsort(Highs))
	).
%
% classify list elements
%
:- pred pivot_classify( list(int), int, list(int), list(int), list(int)).
:- mode pivot_classify( in, in, out, out, out) is det.
pivot_classify( [], _Pivot, [], [], []).
pivot_classify( [Hd | Tl], Pivot, Lows, Mids, Highs) :- 
        ( Hd < Pivot -> some [Lows0] ( 
                          pivot_classify( Tl, Pivot, Lows0, Mids, Highs),
                          Lows = [Hd | Lows0]
                         ) 
        ;
          Hd > Pivot -> some [Highs0] ( 
                          pivot_classify( Tl, Pivot, Lows, Mids, Highs0),
                          Highs = [Hd | Highs0]
                         ) 
        ;
          /* else */
          some [Mids0] (
            pivot_classify( Tl, Pivot, Lows, Mids0, Highs),
            Mids = [Hd | Mids0]
          )
        ).
:- end_module quicksort.

Predykaty i funkcje

Składnia:

:- pred is_all_uppercase(string).   
:- func strlen(string) = int.

Predykaty i funkcje w Mercurym mogą być również polimorficzne:

:- pred member(T, list(T)).
:- func length(list(T)) = int.

Składnia Haskella jest podobna:

is_all_uppercase :: String -> Bool
is_all_uppercase [] = False
is_all_uppercase (a:[]) = isUpper a
is_all_uppercase (pierwszyZnak:resztaZnakow) = isUpper pierwszyZnak && is_all_uppercase resztaZnakow
 
length :: [t] -> Int
length [] = 0
length (x:xs) = 1 + lenght xs
 
map                     :: (a->b) -> [a] -> [b]
map f  []               =  []
map f (x:xs)            =  f x : map f xs

W Mercurym funkcje i predykaty to również wyrażenia. Predykaty i funkcje jako argumenty mogą przyjmować inne predykaty i funkcje. Poniżej przedstawione są predykaty i funkcje wyższych rzędów.

:- type foldl_pred(T, U) == pred(T, U, U).
:- type foldl_func(T, U) == (func(T, U) = U).
     
:- pred p(int) `with_type` foldl_pred(T, U).
:- func f(int) `with_type` foldl_func(T, U).

% co jest równoważne
% :- pred p(int, T, U, U).
% :- pred f(int, T, U) = U.
% lub
% :- pred p(int, foldl_pred(T, U)).
% :- pred f(int, foldl_func(T, U)) = U.

System instancji (ang. instantiatedness)

System instancji pozwala zdefiniować stan instancji danego typu.

Każdy typ jest opisany za pomocą drzewa. Drzewo to składa się z konstruktorów typu (or node) i argumentów tych konstruktorów (and node). System instancji pozwala na opis argumentów konstruktorów (and node) danego typu.

Typ i instancja

Podstawowymi blokami budującymi system instancji w Merkurym są stany:

niezwiązany (ang. free), czyli zmiennej nie jest przypisana żadna wartość ani wyrażenie (w C++ deklaracja zmiennej jakiejś klasy bez inicjalizacji np.: String lancuch; wtedy zmienna lancuch byłaby niezwiązana), a dodatkowo zmienna ta nie jest związana z żadną inną zmienną. Wszyscy potomkowie niezwiązanego argumentu (and node) są niezwiązani,

związany (ang. bound), czyli zmiennej jest przypisana jakaś wartość lub wyrażenie (np. w C++ String zwiazanyLancuch = „miw”;)

Z ich pomocą można budować bardziej wyszukane stany instancji. Poniżej znajduje się przykład stanu instancji „dbOperationInst” dla typu „dbOperation”:

:- type dbOperation ---> lookup(key, data) ; set(key, data).

:- inst dbOperationInst ---> lookup(ground, free) ; set(ground, ground).

System trybów (ang. mode system)

System trybów (ang. mode system), ogólnie rzecz biorąc, pozwala narzucić ograniczenie na zmianę stanu instancji zmiennej. Tryb funkcji lub predykatu jest mapowaniem pomiędzy początkowym stanem instancji argumentów i rezultatu (predykatu lub funkcji), a ich końcowym stanem instancji.

Np. jeżeli chcemy nałożyć ograniczenia na argumenty wejściowe funkcji i jej rezultat, to ograniczenia te możemy zdefiniować w następujący sposób:

:- mode in(Inst) == Inst >> Inst.            /* definicja trybu parametrycznego */
:- mode out(Inst) == free >> Inst.           /* definicja trybu parametrycznego */
:- inst listskel ---> []; [free | listskel].

:- func length(list(T)) = int.               /* deklatacja funkcji */
:- mode length(in(Inst =< listskel)) = out.  /* definicja parametrycznie ograniczonego trybu funkcji */
:- mode length(out(Inst =< listskel)) = in.  /* definicja parametrycznie ograniczonego trybu funkcji */

Definicja trybu może zawierać argument parametryczny, w powyższym przykładzie parametrem tym jest „Inst”.

:- pred append(list(T), list(T), list(T)).  /* deklatacja predykatu */
:- mode append(in, in, out) is det.         /* definicja trybu predykatu */
:- mode append(in, out, in) is semidet.     /* definicja trybu predykatu */
:- mode append(out, in, in) is semidet.     /* definicja trybu predykatu */
:- mode append(out, out, in) is multi.      /* deklatacja predykatu */

% definicja predykatu
append(Xs, Ys, Zs) :- 
  ( 
    Xs = [],
    Zs = Ys
  ;
    Xs = [Hd | Tl],
    append(Tl, Ys, Zs0),
    Zs = [Hd | Zs0]
  ).

Unique insts and modes

Deklaracje trybów mogą również opisywać tak zwane „tryby unikatowe”. Tryby unikatowe w Mercury'm są podobne do „typów liniowych” występujących w niektórych funkcjonalnych językach programowania jak na przykład Clean. Można za ich pomocą oznaczyć zmienną jako taką do której istnieje tylko jedno odniesienie. Tryby unikatowe są używane do optymalizacji kompilowanego kodu (np. destrukcyjne przypisanie jednego elementu tablicy, zamiast kopiowanie całej tablicy żeby zmienić element) oraz jako mechanizm, który jest używany przez Mercury'ego w celu stworzenia deklaratywnych operacji I/O.

Wbudowane stany unikatowych instancji:

  • unique - takie same jak ground z tym, że istnieje dodatkowe ograniczenie mówiące że istnieje tylko jedno odwołanie do odpowiadającej wartości wyrażenia
  • unique(…) - pozwala na budowanie drzewa
  • dead - nie ma żadnego odniesienia do odpowiadającej wartości wyrażenia
     % unique output
     :- mode uo == free >> unique.
     
     % unique input
     :- mode ui == unique >> unique.
     
     % destructive input
     :- mode di == unique >> dead.

Determinizm

Dla każdego trybu (mode) funkcji i predykatu w Mercury'm, definiujemy ile udanych rozwiązań może posiadać ten tryb, oraz czy znalezienie pierwszego rozwiązania może nie powieść się, czy też nie.

W zależności od możliwości niepowodzenia znalezienia rozwiązania oraz ilości możliwych rozwiązań, w tabeli poniżej zostały przedstawione wszystkie możliwe kategorie determinizmu.

0 solution 1 solution more than 1 solution
can't fail erroneous det multi
can fail failure semidet nondet

Ze względu na wiedzę kompilatora na temat ilości rozwiązań danej procedury poniżej przedstawione zostało drzewo zależności pomiędzy poszczególnymi kategoriami determinizmu:

                erroneous
                 /     \
             failure   det
                \     /   \
                semidet  multi
                    \     /
                     nondet

Składnia determinizmu w Mercurym jest przedstawiona na przykładzie poniżej:

     :- pred append(list(T), list(T), list(T)).
     :- mode append(in, in, out) is det.
     :- mode append(out, out, in) is multi.
     :- mode append(in, in, in) is semidet.
     
     :- func length(list(T)) = int.
     :- mode length(in) = out is det.
     :- mode length(in(list_skel)) = out is det.
     :- mode length(in) = in is semidet.

Funkcje i predykaty wyższych rzędów

Mercury pozwala na programowanie funkcji i predykatów wyższych rzędów, w szczególności dostępne są:

  • zwijanie (ang. currying) - f(x,y) = x/y, f(2/3) = g(3) (g(y) = 2/y) = 2/3
  • klauzule (ang. closures) - czyli funkcje, która używają wolnych zmiennych(zmienna, która nie jest związana: Haskell /x -> x y → y jest wolną zmienną), „otaczające” jakąś przestrzeń leksykalną, np. Haskell f x = (\y -> x + y) w tym przypadku f zwraca klauzulę, ponieważ zmienna x, która jest przypisana na zewnątrz lambda abstrakcji, jest używana wewnątrz niej.
  • funkcje anonimowe (ang. lambda abstractions) np. Haskell f x = (\y -> x + y)

Weźmy następujący predykat w Mercurym:

     :- pred sum(list(int), int).
     :- mode sum(in, out) is det.

Możemy go użyć w następujący sposób:

     :- func scalar_product(int, list(int)) = list(int).
     :- mode scalar_product(in, in) = out is det.

     X = (func(Num::in, List::in) = (NewList::out) is det
             :- NewList = scalar_product(Num, List)),
     sum(X, 2).

Zwijanie (currying)

     Sum123 = sum([1,2,3])

% binds `Sum123' to a higher-order predicate term of type `pred(int)'.

*Ograniczenia* nie można używać nazw operacji wbudowanych w język, np. zamiast

list.filter(\=(2), [1, 2, 3], List)

należy użyć:

list.filter((pred(X::in) is semidet :- X \= 2), [1, 2, 3], List)

%lub
     list.filter(not_equal(2), [1, 2, 3], List)

%gdzie not_equal jest zdefiniowane nastepujaco
     :- pred not_equal(T::in, T::in) is semidet.
          not_equal(X, Y) :- X \= Y.

Klauzule (closures)

     addNumFunction = (func addNumbers(X::in, Y::in) = Sum::out is Det :- Sum = X + Y).

     :- func sum2Nums(int::in, int::in) = int::out.

     :- func addNumClosure(X::in) = PartialSum::out :-
         %PartialSum = call(addNumFunction, X). 
          PartialSum = sum2Nums(X).

Moduły i ukrywanie implementacji

Merkury wspiera modularny sposób budowania programów, pozwala również na ukrywanie szczegółów implementacyjnych. Poniżej przedstawiono przykładowy moduł:

     :- module ModuleName.

     :- interface.

     % moduly importowane
     :- include_module ModuleToInclude.

     %
     % przedmioty eksportowane prze modul
     %

     :- implementation.

     % prywatne moduly potrzebne do implementacji
     :- include_module PrivateModuleToInclude.

     %
     % szczegoly implementacji
     %

     :- end_module ModuleName.

Każdy moduł zaczyna się od deklaracji

:- module ModuleName.

gdzie „ModuleName” to nazwa modułu.

Deklaracja interfejsu modułu zaczyna się od:

:- interface.

Sekcja ta specyfikuje, które przedmioty są eksportowane przez ten moduł.

Deklaracja części implementacyjnej modułu jest następująca:

:- implementation.

Sekcja ta zawiera implementację wszystkich przedmiotów. Sekcja ta jest prywatna dla modułu.

Koniec modułu jest oznaczany deklaracją:

:- end_module ModuleName.

gdzie „ModuleName” to nazwa modułu.

Aby importować moduły umieszczone w oddzielnych plikach należy posłużyć się deklaracją:

:- include_module Module.Module1, Module2, ModuleN

Nazwy tych plików muszą być związane z nazwą modułu, dla przykładu jeżeli importujemy moduł o nazwie „Module.Module1” to plik zawierający ten moduł musi mieć nazwę „Module.Module1.m”. Nazwy modułów muszą być pełne (ang. fully qualified).

Każda deklaracja w module rodzicu jest widoczna w module dziecku, ale nie na odwrót.

Type classes

Merkury wspiera ograniczony polimorfizm za pomocą klas typów (ang. type classes). Klasy typów pozwalają pisać predykaty i funkcje, które operują na zmiennych dowolnego typu, dla których pewien zbiór operacji jest zdefiniowany. Klasy typów są podobne do interfejsów w Javie, a w Haskell'u do Type Classes.

Przykład klasy typu w Mercury'm:

     :- typeclass point(T) where [
             % coords(Point, X, Y):
             %       X and Y are the cartesian coordinates of Point
             pred coords(T, float, float),
             mode coords(in, out, out) is det,
     
             % translate(Point, X_Offset, Y_Offset) = NewPoint:
             %       NewPoint is Point translated X_Offset units in the X direction
             %       and Y_Offset units in the Y direction
             func translate(T, float, float) = T
     ].

Liczba parametrów klasy typów jest nie ograniczona:

:- typeclass a(T1, T2) where [...].
     :- type coordinate
             ---> coordinate(
                     float,           % X coordinate
                     float            % Y coordinate
             ).
     
     :- instance point(coordinate) where [
             pred(coords/3) is coordinate_coords,
             func(translate/3) is coordinate_translate
     ].
     
     
     :- pred coordinate_coords(coordinate, float, float).
     :- mode coordinate_coords(in, out, out) is det.
     
     coordinate_coords(coordinate(X, Y), X, Y).
     
     :- func coordinate_translate(coordinate, float, float) = coordinate.
     
     coordinate_translate(coordinate(X, Y), Dx, Dy) = coordinate(X + Dx, Y + Dy).
     :- type rgb
             ---> rgb(
                     int,
                     int,
                     int
             ).
     
     :- type coloured_coordinate
             ---> coloured_coordinate(
                     float,
                     float,
                     rgb
             ).
     
     :- instance point(coloured_coordinate) where [
             pred(coords/3) is coloured_coordinate_coords,
             func(translate/3) is coloured_coordinate_translate
     ].
     
     
     :- pred coloured_coordinate_coords(coloured_coordinate, float, float).
     :- mode coloured_coordinate_coords(in, out, out) is det.
     
     coloured_coordinate_coords(coloured_coordinate(X, Y, _), X, Y).
     
     :- func coloured_coordinate_translate(coloured_coordinate, float, float)
             = coloured_coordinate.
     
     coloured_coordinate_translate(coloured_coordinate(X, Y, Colour), Dx, Dy)
             = coloured_coordinate(X + Dx, Y + Dy, Colour).

Ograniczenia klas typów dla funkcji i predykatów

     :- pred distance(P1, P2, float) <= (point(P1), point(P2)).
     :- mode distance(in, in, out) is det.
     
     distance(A, B, Distance) :-
             coords(A, Xa, Ya),
             coords(B, Xb, Yb),
             XDist = Xa - Xb,
             YDist = Ya - Yb,
             Distance = sqrt(XDist*XDist + YDist*YDist).

Ograniczenia typu klasy przez typy klasy

     :- typeclass ring(T) where [
             func zero = (T::out) is det,               % '+' identity
             func one = (T::out) is det,                % '*' identity
             func plus(T::in, T::in) = (T::out) is det, % '+'/2 (forward mode)
             func mult(T::in, T::in) = (T::out) is det, % '*'/2 (forward mode)
             func negative(T::in) = (T::out) is det     % '-'/1 (forward mode)
     ].

     :- typeclass euclidean(T) <= ring(T) where [
             func div(T::in, T::in) = (T::out) is det,
             func mod(T::in, T::in) = (T::out) is det
     ].

     :- typeclass portrayable(T) where [
             pred portray(T::in, io.state::di, io.state::uo) is det
     ].

     :- instance portrayable(list(T)) <= portrayable(T) where [
             pred(portray/3) is portray_list
     ].
     
     :- pred portray_list(list(T), io.state, io.state) <= portrayable(T).
     :- mode portray_list(in, di, uo) is det.
     
     portray_list([], !IO).
     portray_list([X | Xs], !IO) :-
     	portray(X, !IO),
     	io.write_char(' ', !IO),
     	portray_list(Xs, !IO).

Zależności funkcyjne

Zależności funkcyjne są dostępne w Haskell'u za pomocą rozszerzeń języka.

Zależności funkcyjne są używanie w celu ograniczenia parametrów klas typów. Pozwalają one na stwierdzenie, że dla wieloparametrowej klasy typu, jeden z typów parametrów może być zdeterminowany na podstawie innych. Dla przykładu determinowany parametr, może być zwracanym typem przez metodę, ale nie typem argumentów tej metody.

data Vector = Vector Int Int deriving (Eq, Show)
data Matrix = Matrix Vector Vector deriving (Eq, Show)
 
class Mult a b c | a b -> c where
  (*) :: a -> b -> c
 
instance Mult Matrix Matrix Matrix where
  {- ... -}
 
instance Mult Matrix Vector Vector where
  {- ... -}

W Mercury'm można to zapisać w następujący sposób:

:- type vector
    ---> vector(
      int,
      int
    ).

:- type matrix
    ---> matrix(
      vector,
      vector
    ).
        
:- typeclass mult(A, B, C) <= (A, B -> C) where [
    func multiply(A, B) = C  
]

:- instance mult(matrix, matrix, matrix) where [
    multiply/2 is matrixMultiply
]

Existential types

Egzystencjalnie kwalifikowane zmienne typów (w skrócie egzystencjalne typy) są przydatnymi narzędziami dla abstrakcji typów. W połączeniu z klasami typów, pozwalają one pisać kod w obiektowym stylu, podobnym do tego używanego w Javie lub Cpp.

Mercury pozwala na egzystencjalne kwalifikatory dla funkcji, predykatów i typów. Można używać ograniczeń klas typów dla egzystencjalnie kwalifikowalnych zmiennych typów.

Existentially typed predicates and functions

     % Here the type variables `T1' and `T2' are existentially quantified.
     :- some [T1, T2] func bar(int, list(T1), set(T2)) = pair(T1, T2).

     % Here the type variable 'T1, T2' are existentially quantified,
     % but the type variables 'T3' and 'T4' are universally quantified.
     :- some [T2] pred foo(T1, T2, T2, T3).


     /* przyklady uzycia */

     :- some [T] pred e_bar(T, T, func(T) = int).
     :-          mode e_bar(out, out, out(func(in) = out is det)).
     e_bar(2, 3, (func(X) = X * X)).
     	% ok (T = int)
     
     :- func call_e_bar = int.
     call_e_bar = F(X) + F(Y) :- e_bar(X, Y, F).
     	% ok
     	% returns 13 (= 2*2 + 3*3)

Existentially typed types

The standard library module „univ” provides an abstract type named „univ” which can hold values of any type. You can form heterogeneous containers (containers that can hold values of different types at the same time) by using data structures that contain univs, e.g. „list(univ)”.

The interface to „univ” includes the following:

     % "univ" is a type which can hold any value.
     :- type univ.
     
     % The function univ/1 takes a value of any type and constructs
     % a "univ" containing that value (the type will be stored along
     % with the value)
     :- func univ(T) = univ.
     
     % The function univ_value/1 takes a `univ' argument and extracts
     % the value contained in the `univ' (together with its type).
     % This is the inverse of the function univ/1.
     :- some [T] func univ_value(univ) = T.

The `univ' type in the standard library is in fact a simple example of an existentially typed data type. It could be implemented as follows:

     :- implementation.
     :- type univ ---> some [T] mkuniv(T).
     univ(X) = 'new mkuniv'(X).
     univ_value(mkuniv(X)) = X.

Existentially typed type classes constraints

bla bla

Cechy języków Haskell i Mercury

Sztandarowe cechy języków, standardowe biblioteki, narzędzia programistyczne

Purity

Laiziness - Strictness

Type system

Module system

Standard librarnies

Tools, extensions

Extensibility

Referencje

Prezentacja

Materiały

pl/miw/2009/miw09_mercury.1252771170.txt.gz · ostatnio zmienione: 2019/06/27 15:57 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0