On 22-Jul-1998, S. Alexander Jacobson <[EMAIL PROTECTED]> wrote:
| On Wed, 22 Jul 1998, Simon L Peyton Jones wrote:
| 
| > Let me advertise Olivier Danvy's very cunning idea to implement
| > printf in Haskell/ML.
| > 
| >     http://www.brics.dk/RS/98/5/index.html
| 
| Printf isn't really what I want, but I am happy to take a look.
...
| I suppose existential types will allow a variant using the (,) operator e.g.
| ["foo",expr,"bar"] where expr is an instance of Stringable (or Show).

We've been working on implementing type classes and existential types
in Mercury recently.  Peter Schachte recently asked a similar
question on the mercury-developers mailing list. 
Here's a [slightly edited] copy of my reply.

(I hope this is not too off-topic for the Haskell mailing list.)

On 01-Jul-1998, Peter Schachte <[EMAIL PROTECTED]> wrote:
> This is a follow-up to a discussion I had with Fergus and DJ yesterday, sent
> to mercury-developers because I think the wider audience may be interested.
> 
> For a long time, I've believed that the "right" way to do formatted output
> is with a predicate that takes a list of format specifiers:
> 
>   :- pred format(list(format_spec)::in, io_state::di, io_state::uo) is det.
> 
> A reasonable approximation of the definition of format_spec might be:
> 
>   :- type justification ---> left ; right ; center.
>   :- type format_spec --->
>       (   string(string)
>       ;   int(int)
>       ;   float(float)                % most concise output
>       ;   float(float,int)            % specifying digits of precision
>       ;   expfloat(float,int)         % same, but in exponential notation
>       ;   char(char)
>       ;   justified(format_spec, justification, int)
>                                       % give field width and justification
>       ).
> 
> This isn't quite what I wanted, because I find the string/1, int/1, float/1,
> and char/1 wrappers ugly and overly verbose.  But I can live with them if I
> have to.
> 
> This could be implemented in Mercury with no problems.  Unfortunately, it's
> pretty inflexible.  You'd really like to allow users to define their own
> format specifiers. 
> 
> The standard Prolog solution to this problem would be to have a hook
> predicate, which would be declared multifile, to translate a format_spec
> into a string.  format/3 would invoke this for each format spec, and then
> print the result.  No problem. 
> 
> But Mercury's type system won't allow this, as you'd need to have a type
> definition split over multiple files.  Mercury's module system won't allow
> it either, as you'd need to have a predicate split over multiple files, too. 
> 
> When I've mentioned this problem in the past to Mercurians, the answer has
> always been that type classes will allow this.  You just make format_spec be
> a type class that requires a format_spec_to_string operation, and Bob's your
> uncle. 
> 
> Unfortunately, now the list argument to format would be a list of things in
> a type class, rather than things in a type, so Bob's not your uncle after
> all.  The list is actually heterogeneous, and therefore, I was given to
> understand, this requires existential types, and then Bob will be your uncle
> again.
> 
> Now that existential types are (just about) there, I'd like to see how this
> problem can be handled.  What will the person writing the formatting package
> have to write to define the built-in formatting code suggested above? 

I've implemented such a package; it is included at the end of this mail.

The code would be a lot nicer if/when we allow

        (1) abstract `:- instance' declarations

        (2) pred and func definitions inside `:- instance' definitions

        (3) existentially typed data types other than univ

but at least you can do it now.

> And
> what will, say, the implementor of a bignum package have to write to provide
> an implementation of format_spec_to_string for bignums?

If they just want to be able to print bignums, then

        :- instance formattable(bignum) where [
                pred(format/2) is format_bignum
        ].
        format_bignum(X) --> ...

is enough.  If the want some kind of special formatting for them, then

        :- type bignum_format ---> whatever(bignum, ...).
        :- instance formattable(bignum_format) where [
                pred(format/2) is format_bignum_format
        ].
        format_bignum_format(X) --> ...

Allowing (2) would help a little here.

> Finally, what are
> the prospects for having a version of format/3 which doesn't require you to
> wrap everything?  I'd much rather write (this is in a DCG clause):
> 
>       format(["The answer is ", N, ".\n"])
> 
> than this:
> 
>       format([string("The answer is "), int(N), string(".\n")])

That's quite doable.  The drawback is that you basically lose type
inference -- for anything whose argument types include lists, you will
need to explicitly declare whether they should have type `list(T)' or
`format_list'.  My solution below provides this in a submodule, so that
the user can choose whether or not they want to pay that price.

I tested the solution below with my version of the compiler
(MERCURY_COMPILER=~fjh/ws/alpha/mercury/compiler) that includes the
existential types changes, and once I fixed the many compile errors,
it worked fine first time <grin>.

%-----------------------------------------------------------------------------%
:- module format_test.
:- interface.
:- import_module io.

:- pred main(io__state::di, io__state::uo) is det.

:- implementation.
:- import_module format, format__format_list.

main -->
        format(["Hello ", 42, " world\n"]).

%-----------------------------------------------------------------------------%
:- module format.
:- interface.
:- import_module io, char, list.

:- typeclass formattable(T) where [
        pred format(T::in, io__state::di, io__state::uo) is det
].

/* Mercury does not yet support abstract instance declarations. */
% :- instance formattable(int).
% :- instance formattable(string).
% :- instance formattable(float).
% :- instance formattable(char).
% :- instance formattable(list(T)) <= formattable(T).

:- type justification ---> left ; right ; center.
:- type std_format_spec --->
        (   string(string)
        ;   int(int)
        ;   float(float)                % most concise output
        ;   float(float,int)            % specifying digits of precision
        ;   expfloat(float,int)         % same, but in exponential notation
        ;   char(char)
        ;   justified(format_spec, justification, int)
                                        % give field width and justification
        ).
% :- instance formattable(std_format_spec).

:- type format_spec.
:- func +(T) = format_spec <= formattable(T).
% :- instance formattable(format_spec).

%-----------------------------------------------------------------------------%

        :- module format_list.
        :- interface.

        :- type format_list.
        :- func [] = format_list.
        :- func [T | format_list] = format_list <= formattable(T).

        % :- instance formattable(format_list).

        /* Mercury does not yet support abstract instance declarations,
           so this stuff needs to go in the interface, even though logically
           it is part of the implementation. */

        :- pred fmt(format_list::in, io__state::di, io__state::uo) is det.

        :- instance formattable(format_list) where [
                pred(format/3) is fmt
        ].

        :- end_module format_list.

/* Mercury does not yet support abstract instance declarations,
   so this stuff needs to go in the interface, even though logically
   it is part of the implementation. */

:- instance formattable(int) where [
        pred(format/3) is io__write_int
].
:- instance formattable(float) where [
        pred(format/3) is io__write_float
].
:- instance formattable(string) where [
        pred(format/3) is io__write_string
].
:- instance formattable(char) where [
        pred(format/3) is io__write_char
].
:- instance formattable(std_format_spec) where [
        pred(format/3) is format_std_format_spec
].
:- instance formattable(format_spec) where [
        pred(format/3) is format_format_spec
].
:- instance formattable(list(T)) <= formattable(T) where [
        pred(format/3) is format_list
].

:- pred format_format_spec(format_spec::in,
                io__state::di, io__state::uo) is det.

:- pred format_std_format_spec(std_format_spec::in,
                io__state::di, io__state::uo) is det.

:- pred format_list(list(T), io__state, io__state) <= formattable(T).
:- mode format_list(in, di, uo) is det.

%-----------------------------------------------------------------------------%
:- implementation.
:- import_module std_util.
:- import_module require.

        :- module format_list.
        :- implementation.
        
        % :- type format_list == list(format_spec).
        :- type format_list ---> nil ; cons(format_spec, format_list).

        [] = nil.
        [X | Xs] = cons(+X, Xs).

        fmt(nil) --> [].
        fmt(cons(X, Xs)) --> format(X), fmt(Xs).

        :- end_module format_list.

%-----------------------------------------------------------------------------%

format_std_format_spec(int(X)) --> format(X).
format_std_format_spec(string(X)) --> format(X).
format_std_format_spec(float(X)) --> format(X).
format_std_format_spec(char(X)) --> format(X).
format_std_format_spec(float(X, _)) --> format(X). % XXX
format_std_format_spec(expfloat(X, _)) --> format(X). % XXX
format_std_format_spec(justified(X, _, _)) --> format(X). % XXX

%-----------------------------------------------------------------------------%

:- type format_spec ---> format_spec(univ, univ).

+X = format_spec(univ(X), univ_format(X, format)).

:- func univ_format(T, pred(T, io__state, io__state)) = univ.
:- mode univ_format(in, pred(in, di, uo) is det) = out is det.
univ_format(_Val, Func) = univ(Func).

format_format_spec(format_spec(UnivVal, UnivPred)) -->
        { Val = univ_value(UnivVal) },
        { det_univ_to_type(UnivPred, Pred0) },
        { cast_to_higher_order_inst(Pred0, Pred) },
        Pred(Val).

format_list([]) --> [].
format_list([X | Xs]) --> format(X), format_list(Xs).

%-----------------------------------------------------------------------------%

:- pred cast_to_higher_order_inst(pred(T, io__state, io__state),
                                  pred(T, io__state, io__state)).
:- mode cast_to_higher_order_inst(in, out(pred(in, di, uo) is det)) is det.
:- pragma c_code(
        cast_to_higher_order_inst(X::in, Y::out(pred(in, di, uo) is det)),
                will_not_call_mercury, "Y = X;").

%-----------------------------------------------------------------------------%

        % univ_value(Univ):
        %       returns the value of the object stored in Univ.
:- some [T] func univ_value(univ) = T.
:- pragma c_code(univ_value(Univ::in) = (Value::out), will_not_call_mercury, "
        TypeInfo_for_T = field(mktag(0), Univ, UNIV_OFFSET_FOR_TYPEINFO);
        Value = field(mktag(0), Univ, UNIV_OFFSET_FOR_DATA);
").

%-----------------------------------------------------------------------------%
:- end_module format.
%-----------------------------------------------------------------------------%

-- 
Fergus Henderson <[EMAIL PROTECTED]>  |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"
PGP: finger [EMAIL PROTECTED]        |     -- the last words of T. S. Garp.


Reply via email to