When I compile the Mercury code below, I get this error from the compiler:
In clause for `main(di, uo)':
in argument 1 of call to predicate
`test_with_anonymous_functions.assert_equals'/5:
mode error: variable `V_15' has
instantiatedness `/* unique */((func) =
(free >> ground) is semidet)',
expected instantiatedness was `((func) =
(free >> ground) is det)'.
I think what the compiler is saying is "When you declared the type test_case
, you didn't specify a determinism, so I assumed you meant det
. But then you passed in a semidet
lambda."
My questions:
/* unique */
part of TestCase
's instantiatedness means? Will that cause a mismatch, between the given & expected instantiatedness?main
? I have as much declaration about the lambda as I do code inside the lambda.The code:
% (Boilerplate statements at the top are omitted.)
% Return the nth item of a list
:- func nth(list(T), int) = T.
:- mode nth(in, in) = out is semidet.
nth([Hd | Tl], N) = (if N = 0 then Hd else nth(Tl, N - 1)).
% Unit testing: Execute TestCase to get the
% actual value. Print a message if (a) the lambda fails
% or (b) the actual value isn't the expected value.
:- type test_case(T) == ((func) = T).
:- pred assert_equals(test_case(T), T, string, io.state, io.state).
:- mode assert_equals(in, in, in, di, uo) is det.
assert_equals(TestCase, Expected, Message, !IO) :-
if Actual = apply(TestCase), Actual = Expected
then true % test passed. do nothing.
else io.format("Fail:\t%s\n", [s(Message)], !IO).
main(!IO) :-
List = [1, 2, 3, 4],
assert_equals( ((func) = (nth(List, 0)::out) is semidet),
1, "Nth", !IO).
This took me a while to get the hang of as well.
The problem is that the mode of a higher-order term is not part of its type. So there is no syntax for declaring the determinism of a type. The determinism of a higher-order term is carried in the mode.
In your example, the first argument of assert_equals
has type test_case(T)
, but has mode in
. This means that the fact that the function is semidet
gets lost. I'm not sure if it would actually compile or run correctly if the function you're passing was det
; even in that case the mode really shouldn't be in
.
Here's an example:
:- pred apply_transformer(func(T) = T, T, T).
:- mode apply_transformer(func(in) = out is det, in, out).
apply_transformer(F, X0, X) :-
X = F(X0).
main(!IO) :-
apply_transformer((func(S0) = S is det :- S = "Hello " ++ S0),
"World", Msg),
print(Msg, !IO),
nl(!IO).
As you can see, the type of the first argument to apply_transformer
only says that it is a higher-order function takes one argument and returns a result of the same type. It's the mode declaration that actually says the function parameter has mode in
and the function result has mode out
, and that its determinism is det
.
I believe the /*unique */
bit of the error message says that the compiler thinks it is a unique value. I'm not sure whether that's a problem or not, since you're not using unique modes anywhere other than the usual io
state.
As for the lambda syntax, I don't think you can do any better unfortunately. I find the syntax of lambdas in Mercury to be fairly unsatisfactory; they're so verbose that I usually end up just making named functions/predicates instead for all but the most trivial lambdas.