Mercury

Личный сайт Go-разработчика из Казани

Mercury is a strict, pure functional/logic programming language, with influences from Prolog, ML, and Haskell.

1% Percent sign starts a one-line comment. 2 3 % foo(Bar, Baz) 4 % 5 % Documentation comments are indented before what they describe. 6:- pred foo(bar::in, baz::out) is det. 7 8% All toplevel syntax elements end with a '.' -- a full stop. 9 10% Mercury terminology comes from predicate logic. Very roughly: 11 12% | Mercury | C | 13% | | | 14% | Goal | statement | 15% | expression | expression | 16% | predicate rule | void function | 17% | function rule | function | 18% | head (of a rule) | function name and parameters | 19% | body (of a rule) | function body | 20% | fact | (rule without a body) | 21% | pred/func declaration | function signature | 22% | A, B (conjunction) | A && B | 23% | A ; B (disjunction) | if (A) {} else if (B) {} | 24 25% some facts: 26man(socrates). % "it is a fact that Socrates is a man" 27man(plato). 28man(aristotle). 29 30% a rule: 31mortal(X) :- man(X). % "It is a rule that X is a mortal if X is a man." 32% ^^^^^^-- the body of the rule 33% ^^-- an arrow <--, pointing to the head from the body 34%^^^^^^^^-- the head of the rule 35% this is also a single clause that defines the rule. 36 37% that X is capitalized is how you know it's a variable. 38% that socrates is uncapitalized is how you know it's a term. 39 40% it's an error for 'socrates' to be undefined. It must have a type: 41 42% declarations begin with ':-' 43:- type people 44 ---> socrates 45 ; plato 46 ; aristotle 47 ; hermes. 48 %<--first tab stop (using 4-space tabs) 49 %<--third tab stop (first after --->) 50 51:- pred man(people). % rules and facts also require types 52 53% a rule's modes tell you how it can be used. 54:- mode man(in) is semidet. % man(plato) succeeds. man(hermes) fails. 55:- mode man(out) is multi. % man(X) binds X to one of socrates ; plato ; aristotle 56 57% a semidet predicate is like a test. It doesn't return a value, but 58% it can succeed or fail, triggering backtracking or the other side of 59% a disjunction or conditional. 60 61% 'is semidet' provides the determinism of a mode. Other determinisms: 62% | Can fail? | 0 solutions | 1 | more than 1 | 63% | | | | | 64% | no | erroneous | det | multi | 65% | yes | failure | semidet | nondet | 66 67:- pred mortal(people::in) is semidet. % type/mode in one declaration 68 69% this rule's body consists of two conjunctions: A, B, C 70% this rule is true if A, B, and C are all true. 71% if age(P) returns 16, it fails. 72% if alive(P) fails, it fails. 73:- type voter(people::in) is semidet. 74voter(P) :- 75 alive(P), 76 registered(P, locale(P)), 77 age(P) >= 18. % age/1 is a function; int.>= is a function used as an operator 78 79% "a P is a voter if it is alive, is registered in P's locale, and if 80% P's age is 18 or older." 81 82% the >= used here is provided by the 'int' module, which isn't 83% imported by default. Mercury has a very small 'Prelude' (the 84% 'builtin' module). You even need to import the 'list' module if 85% you're going to use list literals.

Complete runnable example. File in ’types.m’; compile with ‘mmc –make types’.

1:- module types. 2:- interface. 3:- import_module io. % required for io.io types in... 4% main/2 is usually 'det'. threading and exceptions require 'cc_multi' 5:- pred main(io::di, io::uo) is cc_multi. % program entry point 6:- implementation. 7:- import_module int, float, string, list, bool, map, exception. 8 9% enum. 10:- type days 11 ---> sunday 12 ; monday 13 ; tuesday 14 ; wednesday 15 ; thursday 16 ; friday 17 ; saturday. 18 19% discriminated union, like datatype in ML. 20:- type payment_method 21 ---> cash(int) 22 ; credit_card( 23 name :: string, % named fields 24 cc_number :: string, 25 cvv :: int, 26 expiration :: string 27 ) 28 ; crypto(coin_type, wallet, amount). 29 30:- type coin_type 31 ---> etherium 32 ; monero. % "other coins are available" 33 34% type aliases. 35:- type wallet == string. 36:- type amount == int. 37 38% !IO is the pair of io.io arguments 39% pass it to anything doing I/O, in order to perform I/O. 40% many otherwise-impure functions can 'attach to the I/O state' by taking !IO 41main(!IO) :- 42 Ints = [ 43 3, 44 1 + 1, 45 8 - 1, 46 10 * 2, 47 35 / 5, 48 5 / 2, % truncating division 49 int.div(5, 2), % floored division 50 div(5, 2), % (module is unambiguous due to types) 51 5 `div` 2, % (any binary function can be an operator with ``) 52 7 `mod` 3, % modulo of floored division 53 7 `rem` 3, % remainder of truncating division 54 2 `pow` 4, % 2 to the 4th power 55 (1 + 3) * 2, % parens have their usual meaning 56 57 2 >> 3, % bitwise right shift 58 128 << 3, % bitwise left shift 59 \ 0, % bitwise complement 60 5 /\ 1, % bitwise and 61 5 \/ 1, % bitwise or 62 5 `xor` 3, % bitwise xor 63 64 max_int, 65 min_int, 66 67 5 `min` 3, % ( if 5 > 3 then 3 else 5 ) 68 5 `max` 3 69 ], 70 Bools = [ 71 yes, 72 no 73 % bools are much less important in Mercury because control flow goes by 74 % semidet goals instead of boolean expressions. 75 ], 76 Strings = [ 77 "this is a string", 78 "strings can have "" embedded doublequotes via doubling", 79 "strings support \u4F60\u597D the usual escapes\n", 80 % no implicit concatenation of strings: "concat:" "together" 81 "but you can " ++ " use the string.++ operator", 82 83 % second param is a list(string.poly_type) 84 % s/1 is a function that takes a string and returns a poly_type 85 % i/1 takes an int. f/1 takes a float. c/1 takes a char. 86 string.format("Hello, %d'th %s\n", [i(45), s("World")]) 87 ], 88 89 % start with purely functional types like 'map' and 'list'! 90 % arrays and hash tables are available too, but using them 91 % requires knowing a lot more about Mercury 92 get_map1(Map1), 93 get_map2(Map2), 94 95 % list.foldl has *many* variations 96 % this one calls io.print_line(X, !IO) for each X of the list 97 foldl(io.print_line, Ints, !IO), 98 foldl(io.print_line, Bools, !IO), 99 foldl(io.print_line, Strings, !IO), 100 io.print_line(Map1, !IO), 101 % ( if Cond then ThenGoal else ElseGoal ) 102 % I/O not allowed in Cond: I/O isn't allowed to fail! 103 ( if Map2^elem(42) = Elem then 104 io.print_line(Elem, !IO) 105 else % always required 106 true % do nothing, successfully (vs. 'fail') 107 ), 108 109 % exception handling: 110 ( try [io(!IO)] ( % io/1 param required or no I/O allowed here 111 io.print_line(received(cash(1234)), !IO), 112 io.print_line(received(crypto(monero, "invalid", 123)), !IO) 113 ) then 114 io.write_string("all payments accepted\n", !IO) % never reached 115 catch "monero not yet supported" -> % extremely specific catch! 116 io.write_string("monero payment failed\n", !IO) 117 ). 118 119:- pred get_map1(map(string, int)::out) is det. 120get_map1(!:Map) :- % !:Map in the head is the final (free, unbound) Map 121 !:Map = init, % !:Map in the body is the next Map 122 det_insert("hello", 1, !Map), % pair of Map vars 123 det_insert("world", 2, !Map), 124 125 % debug print of current (bound) Map 126 % other [Params] can make it optional per runtime or compiletime flags 127 trace [io(!IO)] (io.print_line(!.Map, !IO)), 128 129 det_insert_from_corresponding_lists(K, V, !Map), 130 % this code is reordered so that K and V and defined prior to their use 131 K = ["more", "words", "here"], 132 V = [3, 4, 5]. 133 134:- pred get_map2(map(int, bool)::out) is det. 135get_map2(Map) :- 136 det_insert(42, yes, map.init, Map). 137 138:- func received(payment_method) = string. 139received(cash(N)) = string.format("received %d dollars", [i(N)]). 140received(credit_card(_, _, _, _)) = "received credit card". % _ is throwaway 141received(crypto(Type, _Wallet, Amount)) = S :- % _Wallet is named throwaway 142 ( % case/switch structure 143 Type = etherium, 144 S = string.format("receiving %d ETH", [i(Amount)]) 145 ; 146 Type = monero, 147 throw("monero not yet supported") % exception with string as payload 148 ).

That was quick! Want more?

More Tutorials

Documentation

  • Language manual, user’s guide, and library reference are all at mercurylang.org