[mercury-users] Another place for existentials?

Mark Anthony BROWN dougl at cs.mu.OZ.AU
Wed Aug 2 16:38:28 AEST 2000


Michael Day writes:
> 
> > I then ran into problems when I wrote a predicate that required
> > its argument to be a csp/2, but didn't care about the type of the
> > node it was related to.  My first attempt was:
> > 
> > :- func solve(CSP) = CSP <= csp(CSP, _SomeNode).
> 
> Problems like this seem to plague the use of type classes with multiple
> arguments; at least whenever I think I've found a nice abstraction I end
> up requiring either a constraint with an unbound variable, or an instance
> declaration with an unbound variable, or a constraint with a term...
> 
> % match is a type class for regular expressions
> :- typeclass match(C, T) where [ ... ].
> 
> :- instance match(char, string).	% can match characters in strings
> :- instance match(string, string).	% can match strings in strings

OK, fine so far ...

> :- instance match(list(T), U) <= match(T, U).	% error

This will fail to compile because it doesn't satisfy the restriction
on type class instances.  The reference manual details this restriction
and its rationale.  There is a simple workaround for it which should work
in this case:

	:- type list_like(X) ---> list_like(X).
	:- instance match(list(T), list_like(U)) <= match(T, U).

You can probably think of a better name than `list_like'.

> :- instance match(list(char), string).		% error

In this case, the workaround is even simpler:

	:- type char_list ---> char_list(list(char)).
	:- instance match(char_list, string).

> 
> As always, match/2 became match/1, hard coded to use strings all the time.
> Anyone managed to do anything useful with multiple argument type classes
> in Mercury?

Sure; the prototype declarative debugger is based on two multi-parameter
typeclasses (see browser/declarative_*.m in the Mercury source).  Here's a
sample of the code:

		% Members of this typeclass represent an entire annotated
		% trace.  The second parameter is the type of identifiers
		% for trace nodes, and the first parameter is the type of
		% an abstract mapping from identifiers to the nodes they
		% identify.
		%
	:- typeclass annotated_trace(S, R) where [ ... ].

...

	:- typeclass mercury_edt(S, T) where [ ... ].

The intention is that instances of mercury_edt/2 should be defined in terms
of instances of annotated_trace/2.  So the instance declaration looks like:

	:- instance mercury_edt(wrap(S), edt_node(R)) <= annotated_trace(S, R)
		where [ ... ].

		% The wrap/1 around the first argument of the instance is
		% required by the language.
		%
	:- type wrap(S) ---> wrap(S).


Even though the `S' in the second typeclass is meant to be the same as the
`S' in the first typeclass, the `wrap' constructor is needed to get around
the typeclass instance restriction.  The added verbosity at first seemed an
annoyance.

As it happens, this added verbosity has made the code much easier to maintain.
The two typeclasses essentially represent the same data, but at two different
levels of abstraction.  The `wrap' constructor makes explicit in the code the
point at which we change from one level of abstraction to the other.  So, not
only does the constructor help the compiler understand the program, it also
helps the user do the same.

In retrospect, I should have chosen something more meaningful than `wrap'.

Cheers,
Mark.

--------------------------------------------------------------------------
mercury-users mailing list
post:  mercury-users at cs.mu.oz.au
administrative address: owner-mercury-users at cs.mu.oz.au
unsubscribe: Address: mercury-users-request at cs.mu.oz.au Message: unsubscribe
subscribe:   Address: mercury-users-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------



More information about the users mailing list