mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 17:41:41 +00:00
264 lines
9.2 KiB
Markdown
264 lines
9.2 KiB
Markdown
---
|
|
name: Mercury
|
|
contributors:
|
|
- ["Julian Fondren", "https://mercury-in.space/"]
|
|
---
|
|
|
|
Mercury is a strict, pure functional/logic programming language, with
|
|
influences from Prolog, ML, and Haskell.
|
|
|
|
```prolog
|
|
% Percent sign starts a one-line comment.
|
|
|
|
% foo(Bar, Baz)
|
|
%
|
|
% Documentation comments are indented before what they describe.
|
|
:- pred foo(bar::in, baz::out) is det.
|
|
|
|
% All toplevel syntax elements end with a '.' -- a full stop.
|
|
|
|
% Mercury terminology comes from predicate logic. Very roughly:
|
|
|
|
% | Mercury | C |
|
|
% | | |
|
|
% | Goal | statement |
|
|
% | expression | expression |
|
|
% | predicate rule | void function |
|
|
% | function rule | function |
|
|
% | head (of a rule) | function name and parameters |
|
|
% | body (of a rule) | function body |
|
|
% | fact | (rule without a body) |
|
|
% | pred/func declaration | function signature |
|
|
% | A, B (conjunction) | A && B |
|
|
% | A ; B (disjunction) | if (A) {} else if (B) {} |
|
|
|
|
% some facts:
|
|
man(socrates). % "it is a fact that Socrates is a man"
|
|
man(plato).
|
|
man(aristotle).
|
|
|
|
% a rule:
|
|
mortal(X) :- man(X). % "It is a rule that X is a mortal if X is a man."
|
|
% ^^^^^^-- the body of the rule
|
|
% ^^-- an arrow <--, pointing to the head from the body
|
|
%^^^^^^^^-- the head of the rule
|
|
% this is also a single clause that defines the rule.
|
|
|
|
% that X is capitalized is how you know it's a variable.
|
|
% that socrates is uncapitalized is how you know it's a term.
|
|
|
|
% it's an error for 'socrates' to be undefined. It must have a type:
|
|
|
|
% declarations begin with ':-'
|
|
:- type people
|
|
---> socrates
|
|
; plato
|
|
; aristotle
|
|
; hermes.
|
|
%<--first tab stop (using 4-space tabs)
|
|
%<--third tab stop (first after --->)
|
|
|
|
:- pred man(people). % rules and facts also require types
|
|
|
|
% a rule's modes tell you how it can be used.
|
|
:- mode man(in) is semidet. % man(plato) succeeds. man(hermes) fails.
|
|
:- mode man(out) is multi. % man(X) binds X to one of socrates ; plato ; aristotle
|
|
|
|
% a semidet predicate is like a test. It doesn't return a value, but
|
|
% it can succeed or fail, triggering backtracking or the other side of
|
|
% a disjunction or conditional.
|
|
|
|
% 'is semidet' provides the determinism of a mode. Other determinisms:
|
|
% | Can fail? | 0 solutions | 1 | more than 1 |
|
|
% | | | | |
|
|
% | no | erroneous | det | multi |
|
|
% | yes | failure | semidet | nondet |
|
|
|
|
:- pred mortal(people::in) is semidet. % type/mode in one declaration
|
|
|
|
% this rule's body consists of two conjunctions: A, B, C
|
|
% this rule is true if A, B, and C are all true.
|
|
% if age(P) returns 16, it fails.
|
|
% if alive(P) fails, it fails.
|
|
:- type voter(people::in) is semidet.
|
|
voter(P) :-
|
|
alive(P),
|
|
registered(P, locale(P)),
|
|
age(P) >= 18. % age/1 is a function; int.>= is a function used as an operator
|
|
|
|
% "a P is a voter if it is alive, is registered in P's locale, and if
|
|
% P's age is 18 or older."
|
|
|
|
% the >= used here is provided by the 'int' module, which isn't
|
|
% imported by default. Mercury has a very small 'Prelude' (the
|
|
% 'builtin' module). You even need to import the 'list' module if
|
|
% you're going to use list literals.
|
|
```
|
|
|
|
Complete runnable example. File in 'types.m'; compile with 'mmc --make types'.
|
|
|
|
```prolog
|
|
:- module types.
|
|
:- interface.
|
|
:- import_module io. % required for io.io types in...
|
|
% main/2 is usually 'det'. threading and exceptions require 'cc_multi'
|
|
:- pred main(io::di, io::uo) is cc_multi. % program entry point
|
|
:- implementation.
|
|
:- import_module int, float, string, list, bool, map, exception.
|
|
|
|
% enum.
|
|
:- type days
|
|
---> sunday
|
|
; monday
|
|
; tuesday
|
|
; wednesday
|
|
; thursday
|
|
; friday
|
|
; saturday.
|
|
|
|
% discriminated union, like datatype in ML.
|
|
:- type payment_method
|
|
---> cash(int)
|
|
; credit_card(
|
|
name :: string, % named fields
|
|
cc_number :: string,
|
|
cvv :: int,
|
|
expiration :: string
|
|
)
|
|
; crypto(coin_type, wallet, amount).
|
|
|
|
:- type coin_type
|
|
---> etherium
|
|
; monero. % "other coins are available"
|
|
|
|
% type aliases.
|
|
:- type wallet == string.
|
|
:- type amount == int.
|
|
|
|
% !IO is the pair of io.io arguments
|
|
% pass it to anything doing I/O, in order to perform I/O.
|
|
% many otherwise-impure functions can 'attach to the I/O state' by taking !IO
|
|
main(!IO) :-
|
|
Ints = [
|
|
3,
|
|
1 + 1,
|
|
8 - 1,
|
|
10 * 2,
|
|
35 / 5,
|
|
5 / 2, % truncating division
|
|
int.div(5, 2), % floored division
|
|
div(5, 2), % (module is unambiguous due to types)
|
|
5 `div` 2, % (any binary function can be an operator with ``)
|
|
7 `mod` 3, % modulo of floored division
|
|
7 `rem` 3, % remainder of truncating division
|
|
2 `pow` 4, % 2 to the 4th power
|
|
(1 + 3) * 2, % parens have their usual meaning
|
|
|
|
2 >> 3, % bitwise right shift
|
|
128 << 3, % bitwise left shift
|
|
\ 0, % bitwise complement
|
|
5 /\ 1, % bitwise and
|
|
5 \/ 1, % bitwise or
|
|
5 `xor` 3, % bitwise xor
|
|
|
|
max_int,
|
|
min_int,
|
|
|
|
5 `min` 3, % ( if 5 > 3 then 3 else 5 )
|
|
5 `max` 3
|
|
],
|
|
Bools = [
|
|
yes,
|
|
no
|
|
% bools are much less important in Mercury because control flow goes by
|
|
% semidet goals instead of boolean expressions.
|
|
],
|
|
Strings = [
|
|
"this is a string",
|
|
"strings can have "" embedded doublequotes via doubling",
|
|
"strings support \u4F60\u597D the usual escapes\n",
|
|
% no implicit concatenation of strings: "concat:" "together"
|
|
"but you can " ++ " use the string.++ operator",
|
|
|
|
% second param is a list(string.poly_type)
|
|
% s/1 is a function that takes a string and returns a poly_type
|
|
% i/1 takes an int. f/1 takes a float. c/1 takes a char.
|
|
string.format("Hello, %d'th %s\n", [i(45), s("World")])
|
|
],
|
|
|
|
% start with purely functional types like 'map' and 'list'!
|
|
% arrays and hash tables are available too, but using them
|
|
% requires knowing a lot more about Mercury
|
|
get_map1(Map1),
|
|
get_map2(Map2),
|
|
|
|
% list.foldl has *many* variations
|
|
% this one calls io.print_line(X, !IO) for each X of the list
|
|
foldl(io.print_line, Ints, !IO),
|
|
foldl(io.print_line, Bools, !IO),
|
|
foldl(io.print_line, Strings, !IO),
|
|
io.print_line(Map1, !IO),
|
|
% ( if Cond then ThenGoal else ElseGoal )
|
|
% I/O not allowed in Cond: I/O isn't allowed to fail!
|
|
( if Map2^elem(42) = Elem then
|
|
io.print_line(Elem, !IO)
|
|
else % always required
|
|
true % do nothing, successfully (vs. 'fail')
|
|
),
|
|
|
|
% exception handling:
|
|
( try [io(!IO)] ( % io/1 param required or no I/O allowed here
|
|
io.print_line(received(cash(1234)), !IO),
|
|
io.print_line(received(crypto(monero, "invalid", 123)), !IO)
|
|
) then
|
|
io.write_string("all payments accepted\n", !IO) % never reached
|
|
catch "monero not yet supported" -> % extremely specific catch!
|
|
io.write_string("monero payment failed\n", !IO)
|
|
).
|
|
|
|
:- pred get_map1(map(string, int)::out) is det.
|
|
get_map1(!:Map) :- % !:Map in the head is the final (free, unbound) Map
|
|
!:Map = init, % !:Map in the body is the next Map
|
|
det_insert("hello", 1, !Map), % pair of Map vars
|
|
det_insert("world", 2, !Map),
|
|
|
|
% debug print of current (bound) Map
|
|
% other [Params] can make it optional per runtime or compiletime flags
|
|
trace [io(!IO)] (io.print_line(!.Map, !IO)),
|
|
|
|
det_insert_from_corresponding_lists(K, V, !Map),
|
|
% this code is reordered so that K and V and defined prior to their use
|
|
K = ["more", "words", "here"],
|
|
V = [3, 4, 5].
|
|
|
|
:- pred get_map2(map(int, bool)::out) is det.
|
|
get_map2(Map) :-
|
|
det_insert(42, yes, map.init, Map).
|
|
|
|
:- func received(payment_method) = string.
|
|
received(cash(N)) = string.format("received %d dollars", [i(N)]).
|
|
received(credit_card(_, _, _, _)) = "received credit card". % _ is throwaway
|
|
received(crypto(Type, _Wallet, Amount)) = S :- % _Wallet is named throwaway
|
|
( % case/switch structure
|
|
Type = etherium,
|
|
S = string.format("receiving %d ETH", [i(Amount)])
|
|
;
|
|
Type = monero,
|
|
throw("monero not yet supported") % exception with string as payload
|
|
).
|
|
```
|
|
|
|
## That was quick! Want more?
|
|
|
|
### More Tutorials
|
|
|
|
* [Mercury Tutorial](https://mercurylang.org/documentation/papers/book.pdf) (pdf link) - a more traditional tutorial with a more relaxed pace
|
|
* [Mercury Crash Course](https://mercury-in.space/crash.html) - a dense example-driven tutorial with Q&A format
|
|
* [GitHub Wiki Tutorial](https://github.com/Mercury-Language/mercury/wiki/Tutorial)
|
|
* [Getting Started with Mercury](https://bluishcoder.co.nz/2019/06/23/getting-started-with-mercury.html) - installation and your first steps
|
|
|
|
### Documentation
|
|
|
|
* Language manual, user's guide, and library reference are all at
|
|
[mercurylang.org](https://mercurylang.org/documentation/documentation.html)
|