Better syntax for currying and functions (was Re: Mercury syntax?
Peter Moulder
reiter at netspace.net.au
Mon Sep 8 17:41:49 AEST 1997
This article describes a number of syntactic conveniences. I don't
actually expect the mercury developers to implement any of these soon,
as I'd prefer that more work be put into the back end than the front.
But maybe others think differently, and different developers prefer
working on different things.
1. Currying.
Introduce a symbol `_<N>' for N in {1,2,...}.
Then:
Curried = p(a, _<2>, _<1>, d),
Curried(Arg1, Arg2)
is the same as
p(a, Arg2, Arg1, d).
(The symbol `_<N>' is a placeholder for ArgN, for each natural N.)
This means that order is not important for currying.
The same technique applies to getting DCG's to work as one wants.
2. Functions.
We were introduced to functions with the notion that a function is
just syntactic sugar for predicates.
Thus `Y = f(ARGS)' has the same semantics as `p(Y,ARGS)' and as
`q(ARGS,Y)' (for appropriate definitions of p and q).
Function syntax is indeed very sweet, and I'd like to be able to use
it more often. Therefore I suggest introducing another symbol, say
`_@', such that `p(a, _@, c, d)' is a "function" returning Y such that
p(a,Y,c,d). (I place `function' in inverted commas because it needn't
be deterministic.)
Of all the ideas in this article, I think this the most useful. Using
this, people can forget about the separate syntax for declaring
functions, and just use predicates. This syntax thus makes the
Mercury language both easier to learn and nicer to write.
2b. An equivalence between functions and predicates.
As regards mapping predicates onto functions returning a bool: Mercury
"functions" can be nondeterministic, so can already encode true/false
information. Adding a boolean in the form of a return value makes no
more sense than writing in C `((a == b) == TRUE)', or adding a boolean
to all existing predicates. (And in all of these cases one might just
as well go any number of steps further -- e.g. `(((a == b) == TRUE) ==
TRUE)' or having a ,function over ,a function returning a bool` and a
bool` returning a bool, or adding another boolean to a predicate to
which we've just added a boolean.)
So let's say that each function f(ARGS) can also be written p(ARGS, _@).
3. Data constructors / functions
In brief: if data constructors are considered functions, then we can
convert `X = tree(L,R)', which is written in function form, to its
equivalent predicate form `tree(L,R,X)'.
I don't see this as particularly useful without further changes to
Mercury to make the idea more powerful; but it does at least allow
Marko's original example to be written `list__map(intersection(A,
_<1>, _<2>), List1, List2)' (but for the fact that one must state the
mode explicitly for higher order predicates at present).
The rest of what I'd written under this section expands on the `data
constructors considered functions' idea. I'll leave it in now that
I've written it, but the changes suggested are more fundamental /
substantial than those above.
pjm.
Funny this should come up. I just happened to be thinking today that
data constructors are conceptually functions, so that
:- type tree(T) ---> empty ; tree(tree(T), T, tree(T)).
is the same as declaring functions `tree__empty()' and
`tree__tree(tree(T), T, tree(T))' and that
(all [X,T]
(X is of type tree(T))
<=>
(
X = tree__empty()
;
X = tree__tree(_L, _V, _R),
)
),
(all [L,V,R]
tree__empty() \= tree__tree(L,V,R)
).
(I've prepended `tree__' to the names to represent the fact that they
are in their own name space and can't be confused with, say,
stack__empty().)
This is nice in some ways, but I don't know how useful it is in this
form. It might become useful if it were further extended to the point
of algebraic specification languages like Larch (where the programmer
needn't specify any representation of a type's structure but only how
the access methods interact), but I imagine that it's quite a bit of
work for a compiler to work out a good structure from this.
Mercury/Prolog's data constructors are hardly optimal for translating
to a machine representation, but at least a translation has already
been developed and is, by comparison, quite straightforward so easy to
debug.
If anything were done with type declarations then I'd prefer that it
be allowing something like:
istype(X, tree(T)):-
(
X = tree:empty
;
X = tree:tree(L, V, R),
istype(L, tree(T)),
istype(V, T),
istype(R, tree(T))
).
This allows the programmer to put more constraints on the type than
can be expressed with Mercury's existing type declarations. In
practical terms this means the possibility of less runtime checking,
tighter determinism, and better code. Giving the compiler more
information about one's data structures is usually a good thing.
pjm.
More information about the users
mailing list