[m-users.] Concise syntax for initialising and updating discriminated union types

Zoltan Somogyi zoltan.somogyi at runbox.com
Tue Jul 19 03:37:37 AEST 2022

2022-07-19 02:50 GMT+10:00 "Volker Wysk" <post at volker-wysk.de>:

> 2. How to concisely update multiple fields? You can do this in multiple
> steps, like this:
> konstr1(LP3) :-
>     LP1 = 'lp_link_target :='(null_link_parts, "Target"),
>     LP2 = 'lp_link_subsection :='(LP1, yes("Subs.")),
>     LP3 = 'lp_link_text :='(LP2, "bla").
> But this too, is rather verbose and maybe error prone.

You can also do this with

LP = (((null_link_parts ^ lp_link_target := ...)
  ^ lp_link_subsection := ...)
  ^ lp_link_text := ...)

though matching all the parentheses can be a bit of a pain.

> The code smells very much like state variables. It could be done like this:
> konstr3(Out) :-
>     some [!LP] (
>         !:LP = null_link_parts,
>         lp_link_target("Target", !LP),
>         lp_link_subsection(yes("Subs."), !LP),
>         lp_link_text("bla", !LP),
>         Out = !.LP
>     ).

And lots of Mercury code does just such sequences of field updates,
though none of the Mercury code I have seen started with a deliberately
junk structure. Amongst other things, this code will do the wrong thing
if someone ever adds a fourth field to the structure returned by null_link_parts.

> 1. A concise syntax for initialising values of a discriminated union type.
> Something like this (link_parts is a data constructor):
> link_parts(lp_link_target = "Target",
>            lp_link_subsection = yes("Subs."),    
>            lp_link_text = "bla").
> The compiler would complain when some fields aren't initialised.

This would be quite a lot of work to implement in the compiler,
and there is already a simple way to get the effect that I presume your are
after: a guarantee that two fields of the same type cannot be mixed up
without that mixup being detected. That way is to use semantic wrappers
to make fields that are semantically different be actually of different types.

In this example, this would mean defining

:- type link_target
  --->   link_target(string).

:- type link_text
  ---->    link_text(string).

With these, link_params(link_target("Target"), yes("Subs"), link_text("bla"))
would work, but any permutation of the arguments would get an error.
> 2. Some concise way to update multiple fields, like above. Maybe the
> compiler could additionally generate field update predicates for the fields
> (like above), similar to field update functions.

This would be easier to implement, but one would want to design
any such new feature carefully. For example,

- how do you specify which subset of the fields you want setter predicates for
  (in many situations, some fields should be read-only).

- it is probably the usual case that you want getter predicates for all the fields, but

  -  sometimes you don't, and
  - even if you do, you may want to export only a subset of them, leaving the others

This last consideration also applies to setters, of course.

You would also want some mechanism to control the names of the generated
predicates, though a naming scheme consisting of {get,set}_<fieldname>
would be a good default.

All of this info is specific to a particular type definition, so you wouldn't want
to specify it using a compiler option; the appropriate mechanism would seem to be
a new kind of pragma.


More information about the users mailing list