[m-users.] What's special about Mercury?
jfondren at minimaltype.com
jfondren at minimaltype.com
Fri Jun 21 11:26:57 AEST 2019
Howdy,
I'm writing a Mercury tutorial and thought I'd have a sales pitch early
in it, since hello-world type code makes Mercury look bad. I'd
appreciate any feedback you have on the following. The tutorial itself
won't be done for a while.
Thanks,
Julian
---
** What's special about Mercury?
You can write Java once and then run it everywhere. You get memory
safety without garbage collection if you write Rust. Every single
thing you look at in your Smalltalk environment is an object that you
can send messages to. You can put your frontend developers on backend
projects with Node. You can write field validation code once and then
have both your C++ server apply it and your JS interface apply it, if
you write the validation code in Nim (or in Mercury if a JS grade is
ever added).
What can you do with Mercury?
Consider this C code:
#+BEGIN
int bounded_increment (int n) {
assert(n < 500);
return n + 1;
}
#+END
In C (and we're not picking on C) this is a little machine that you
can push a number into, and either get that number+1 back out of it,
or you get an explosion. That's it. Similar code in virtually all
other languages will do the same thing. The machine might explode in
different ways in other languages, or different ranges of numbers
might work with it, but the machine will always operate in the same
direction.
To which you might reply, "well, duh?" Code does what it's written to
do--what other outcome could there be? What's going on with that "same
direction" talk? Isn't there only one direction in code?
Well, here's the Mercury version of that function:
#+BEGIN
:- pred bounded_increment(int, int).
:- mode bounded_increment(in, out) is semidet.
bounded_increment(A, B) :-
A < 500,
B = A + 1.
#+END
In Mercury, this is a little machine that you can push a number into,
and either get that number+1 back out of it, or you get an explosion.
... so Mercury's exactly the same then?
It is until we add:
#+BEGIN
:- mode bounded_increment(out, in) is semidet.
#+END
With that single additional line, you can now run the machine
backwards. You can push a number into it, and then either get that
number-1 back out of it, or you get an explosion.
When you write `bounded_increment(5, N)`, then `N=6` (running the code
forwards). When you write `bounded_increment(N, 5)`, then `N=4` (running
the code backwards). Or you can get an explosion with
`bounded_increment(500, N)` or `bounded_increment(N, 501)`.
Now let's add:
#+BEGIN
:- mode bounded_increment(in, in) is semidet.
#+END
Now your machine, which previously took an input and yielded an
output (whichever direction you ran it), is turned into a detector!
You give it two numbers and it either explodes or doesn't.
This explodes: `bounded_increment(500, 501)`, as does this:
`bounded_increment(1, 3)`. But this doesn't explode:
`bounded_increment(41, 42)`.
With Mercury, you now have three completely different ways to run the
brief bit of code you wrote above. To get those different behaviors
out of C, you have to write more code for each behavior:
#+BEGIN
int bounded_increment2(int n) {
assert(n-1 < 500);
return n-1;
}
void bounded_increment3(int a, int b) {
assert(a < 500);
assert(a + 1 == b);
}
#+END
(And you'd probably want exceptions at this point, rather than assert.)
In nearly any language but Mercury, not only do you have to write the
code three different times for three different behaviors, but you
encode a relationship three different times in three different ways,
thereby disguising that relationship. In C it's at least *less*
obvious that bounded_increment() and bounded_increment2() are working
with two numbers that have the same relationship between them in both
functions: the first number is less than 500, and the second number is
the first number plus one. In Mercury it's extremely obvious that the
relationship is the same because there's only one piece of code for
both behaviors:
#+BEGIN
bounded_increment(A, B) :-
A < 500,
B = A + 1.
#+END
If circumstances overtook the program and the relationship needed to
change, in Mercury you'd make the change once. In other languages
you'd have to make the change multiple times, and also repeat the
different encodings of the relationship.
That's what cool about Mercury. This is why I think you should
consider using Mercury instead of some other language. In Mercury you
make machines that can run forwards and backwards and sideways. In
other languages you hold relationships in your head and then encode
behaviors that apply those relationships. If you wrote a bunch of
Feet/Inches converting functions, you might put the relationship
between Feet and Inches in a comment. In Mercury you just write the
relationships down--the comment is your code, and you can use it
repeatedly to get all the separate behaviors you wanted.
Now, not all of your code is going to benefit from this. I've put this
section at the very beginning of the tutorial because 'hello world'
certainly will not demonstrate it. And you may not at the outset of any
program know for sure that you'll benefit from this. But I think you
should write Mercury for those instances where you do benefit, and
in anticipation of finding those instances.
---
More information about the users
mailing list