Maybe monad in Prova

The Maybe monad is another classic functional programming construct. It has the purpose of wrapping the results of computations so that the error conditions are propagated as special Nothing objects. We model the Maybe monad by two terms maybe(nothing()) and maybe(just(X)). The latter actually represents a valid value.

The following code from the new test func_005.prova shows how we can build a monad using this representation.

% Demonstrate
%    the Maybe monad,
%    the use of monadic computations instead of monadic instances as inputs to bind,
%    the use of fully applied functions in such monadic computations,
%    show how to use Java types in functions.

% The two examples below pass an explicit monad instance as input to Maybe bind
% maybe(just(3)) =>> try_f
% this returns: X=[[maybe,[just,1]]]
:- solve(
	derive(
		[[join,map(try_f)],[maybe(just(3))],X]
	)
).

% maybe(nothing()) =>> try_f
% this returns: X=[[maybe,[nothing]]]
:- solve(
	derive(
		[[join,map(try_f)],[maybe(nothing())],X]
	)
).

% In the last three tests, we pass a computation as input.
% This computation is a generator, i.e., it does not have parameters.
% gen_7() =>> try_f
% this returns: X=[[maybe,[just,3]]]
:- solve(
	derive(
		[[join,map(try_f)],[gen_7()],X]
	)
).

% use a partially applied function as the input computation: gen_f(7) =>> try_f
% this returns: X=[[maybe,[just,3]]]
:- solve(
	derive(
		[[join,map(try_f)],[[gen_f(7)]],X]
	)
).

% demonstrate a chain that converges to Nothing: gen_f(7) =>> try_f =>> try_f =>> try_f =>> try_f
% this returns: X=[[maybe,[nothing]]]
:- solve(
	derive(
		[[join,map(try_f),join,map(try_f),join,map(try_f),join,map(try_f)],[[gen_f(7)]],X]
	)
).

% map for the Maybe monad is an example of an fmap: a->b->Ma->Mb
map([Fun,maybe(nothing())],maybe(nothing())).
map([Fun,maybe(just(A))],maybe(just(B))) :-
	derive(Fun(A,B)).
% an extra rule for using a monadic computation (a generator) as input
map([Fun,Gen],B) :-
	derive(Gen([],G)),
	map([Fun,G],B).

% join (like concat for the list monad) must always be wrapped in an outer list.
% this is our implementation detail 
join(maybe(nothing()),[maybe(nothing())]).
join(maybe(just(X)),[X]).

gen_f([X],maybe(just(X))).

% generators are parameter-less functions 
gen_7([],maybe(just(7))).

try_f(0,maybe(nothing())) :- !.
try_f(Number.A,maybe(just(Number.B))) :-
	Number.B=Number.A/2.

There are a few points worth mentioning here. To define a monad, we choose the particular strategy to define bind in terms of map and join--as we did for the List monad in the previous post. For the Maybe monad, these are different and are defined here. Our map implementation has now provision for passing computations rather than monad instances as the first input to the pipeline, which requires generator (parameter-less) functions (e.g., see definition of gen_7). Fully applied functions like gen_f(7) can be obviously used in place of generators that take no parameters. The join implementation for the Maybe monad opens the Maybe container that contains another Maybe container once. This is the job join needs to do to make the results of a computation on a monad to be passed to the next stage in the pipeline, returning back to the once-wrapped container Maybe level. Finally, the function try_f shows how Java types can be used to constrain the function definitions.