[m-users.] Printing any value with io.print

Zoltan Somogyi zoltan.somogyi at runbox.com
Mon Mar 16 06:03:02 AEDT 2020



On Sun, 15 Mar 2020 18:46:31 +0100, Volker Wysk <post at volker-wysk.de> wrote:
> The io.print predicates are able to print values of any type. I'm
> curious how this is possible. In Haskell, the types must be in the
> "Show" type class and you define a member function, which handles
> values of the type.

Mercury treats every type as Haskell would treat a type that had
"deriving (Eq,  Ord, Show, Read)" after its definition. In other words, in Mercury,
the unification, comparison, print and read operations are defined for values
of all types. (There is a minor niggle in that you cannot unify or compare
higher order values, so the compiler-defined unify and compare operations
on higher order values throw exceptions, and reading and writing them
don't make much sense either.)

> How does io.print know how to handle values of ANY
> type...?

When your code has a call to io.print, and passes e.g X to be printed,
the compiler adds an implicit argument to the call that tells it the type of X.
But io.print is not special; for every predicate or function that has a type
variable in its signature, the compiler passes along an extra argument
that gives the identity of the concrete type that is bound to the type variable
on this call. We call this compiler-generated argument a "type_info", since it
gives the callee information about a type. And if a predicate or function
has N type variables in its signature, the compiler passes N extra type_info
arguments, one for each type variable.

The type_info structures are small, just enough to describe the structure
of the type in terms of type constructors. (For example, "map(string, list(int))"
has four type constructors, map/2, string/0, list/1 and int/0.) For each
type constructor, they contain a pointer to a larger data structure called
a type_ctor_info that specifies whether the type constructor is a builtin
and if so which builtin (e.g. string or int), and if not, what function symbols
the user-defined type constructor defines, and how their arguments are
represented. These data structures were designed specifically to provide
the information that operations such as io.print need to work.

If you want to know more about this, we wrote this up in a paper
a long time ago. The paper is available at

http://mercurylang.org/documentation/papers.html#rtti_ppdp

The details have changed a bit since then, but the principles remain
the same.

> I need to write an algebraic data type, as a term, to a string, just
> like io.print does, when it writes it to an output stream. How should
> this be done?

I am not sure what this is asking, but if it is asking "how can I print
arbitrary values so the output goes not to an output stream, but to
a string", then ...

> I couldn't find a string_output_stream feature...

... you should use stream.m, which specifies an interface for things
that can be written to as a stream, and string.builder, which specifies
how you can write to a string.builder as a stream. Note: writing
to a string as a stream as a bad idea, because the constant appending
to the end of a string yields O(N^2) complexity. With string.builder,
you build up a list of string fragments, which you can convert into
a string when you are done appending. This is O(N).

> Is the
> "format anything" capability of io.print available outside of io.print?

As the above shows, the answer is "yes". And the raw operations
on which io.print is built is available in the deconstruct module
of the Mercury standard library.

Zoltan.



More information about the users mailing list