1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
---
language: mercury
contributors:
- ["Julian Fondren", "https://mercury-in.space/"]
---
Mercury is a strict, pure functional/logic programming language, with
influences from Prolog, ML, and Haskell.
```mercury
% 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'.
```mercury
:- 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)
|