[m-users.] if/then/else griping.

Julian Fondren jfondren at minimaltype.com
Mon Jul 22 16:15:57 AEST 2019


Hello,

Please consider the following. I realize that daytype/1 would be
better as just a bunch of disjunctions, but I'm using the example to
show off different ways to format Mercury code:

   :- type days
       --->    sunday
       ;       monday
       ;       tuesday
       ;       wednesday
       ;       thursday
       ;       friday
       ;       saturday.

   :- func daytype(days) = string.
   daytype(D) = R :-
       (
           D = wednesday
       ->
           R = "humpday"
       ;
           (D = sunday; D = saturday)
       ->
           R = "weekend"
       ;
           R = "workday"
       ).

if/then/else seems to be the recommended style, but, I find it
impossible to format in a pleasing way. Here are some attempts, and
my problems with them:

   % this feels like the natural way.
   % but this is a level of indentation too many.
   % it looks OK here but not next to (A;B;C;D).
   daytype(D) = R :-
       (
           if D = wednesday then
               R = "humpday"
           else if (D = sunday; D = saturday) then
               R = "weekend"
           else
               R = "workday"
       ).

vs.

   % Mercury stdlib seems to have settled on this style.
   % the initial 'if' looks unbalanced vs. the rest of the code.
   daytype(D) = R :-
       ( if D = wednesday then
           R = "humpday"
       else if (D = sunday; D = saturday) then
           R = "weekend"
       else
           R = "workday"
       ).

   % trying to fix the previous version.
   % new problem: inconsistent indentation.
   daytype(D) = R :-
       ( if D = wednesday then
           R = "humpday"
         else if (D = sunday; D = saturday) then
           R = "weekend"
         else
           R = "workday"
       ).

   % trying to fix the inconsistency.
   % new old problem: this is a level of indentation too many.
   daytype(D) = R :-
     ( if D = wednesday then
         R = "humpday"
       else if (D = sunday; D = saturday) then
         R = "weekend"
       else
         R = "workday"
     ).

Even if horizontal formatting is generally discouraged as a
maintenance hassle, Mercury styling uses it a lot (and somewhat more
conveniently than normal since it's aligned to tab stops), and it
just looks really, really nice, for code that's not going to change
a lot.

   daytype(D) =
       (
           D = wednesday               ->  "humpday";
           (D = sunday; D = saturday)  ->  "weekend";
                                           "workday"
       ).

With if/then:

   % This is somehow intolerable. With punctuation it looks like math
   % you'd write freehand on paper. Maybe that's why I like that
   % version.
   daytype(D) =
       (
           if D = wednesday                    then "humpday"
           else if (D = sunday; D = saturday)  then "weekend"
                                               else "workday"
       ).

*Syntax* issues with languages, I'd normally disregard as trivial,
but I don't think *formatting* is trivial. Formatting is so
important that

   1. languages like Python, Haskell, Nim, make it meaningful.

   2. languages like Go (gofmt), Ada (gnatpp), Elm, make it
      automatic. You don't format your code; a code prettifier does.

   3. JavaScript speakers like Douglas Crockford pretend that
      automatic semicolon insertion is a scaaaary, spooooky
      nondeterministic process that you can't trust with your code,
      in order to persuade devs to always use semicolons even though
      JS doesn't actually need them, because there are exactly *two*
      minor formatting problems that come up if you avoid semicolons
      as a rule.

   4. ReasonML and Elixir are successful and popular 'languages' that
      are just skins over existing languages that are somewhat
      more annoying (Erlang string literals produce list(char) and to
      avoid that you write uglier <<"blah">> binary literals; OCaml
      has a toplevel/non-toplevel syntax distinction that people so
      routinely fail to understand that you'll see unnecessary ;;
      double semicolons all the time in newbie OCaml code).

Maybe this is untrue, but my assumption for a while was that
if/then/else was added to Mercury in the first place because people
learning Mercury complained about formatting problems with the
Prolog-style conditionals. This is odd as the new syntax retains any
problem you'd have with the old syntax, but, even if people are
aggravated by something, this doesn't mean they *recognize* what
specifically they find aggravating. Maybe the formatting aggravation
was always mis-explained as "words are easier to read than
punctuation":

   1. other languages use words rather than punctuation (true, but
      irrelevant)
   2. other languages don't need to parenthesize their conditionals
      to mark where the conditionals end

Maybe the water's long under the bridge, but an 'end' token
instantly fixes the syntax in my view.

   daytype(D) = R :-
       if D = wednesday then
           R = "humpday"
       else if (D = sunday; D = saturday) then
           R = "weekend"
       else
           R = "workday"
       end.

...

Well, having said all that, I've already realized that the
unbalanced-if is what the stdlib is going with, so I'll try that and
see if it eventually stops bothering me.

I do think that the tutorial should reflect this style if that's
what's recommended. Right now the tutorial has

   fib(N, X) :-
       (   if    N =< 2
           then  X = 1
           else  fib(N - 1, A), fib(N - 2, B), X = A + B
       ).

which is just...

1. an indention too many
2. the N =< 2 column isn't on a (4spc) tab stop. If fixed to lie on
    a tab stop (for writing/editing convenience), it's excessively
    indented and looks ugly.
3. that final 'else' looks bad and every other way to reformat it
    looks much, much worse.

And then later, a four-space indentation, followed by a three-space
indentation for if/then/else keywords, followed by a two-space
indentation for the body of the conditional:

   main(!IO) :_
       io.read_line_as_string(Result, !IO),
       (  if
            Result = ok(String),
            string.to_int(string.strip(String), N)
          then
            io.format("fib(%d) = %d\n", [i(N), i(fib(N))], !IO)
            main(!IO)
          else
            io.format("I didn't expect that...\n", [], !IO)
       ).

I actually like Mercury's syntax, and how the language looks
overall. I was even happy to discover that the omnipresent
parentheses are really just mathematical parentheses for
grouping, and that this is valid (if inadvisable):

   daytype(D) = R :-
       (D = sunday; D = saturday),
       R = "weekend";

       (D = monday; D = tuesday; D = thursday; D = friday),
       R = "workday";

       D = wednesday,
       R = "humpday".

But I don't think anyone could read the tutorial, and try to
recreate the examples as they're shown, and come to any other
conclusion than that Mercury conditionals are very annoying to
format. While reproducing the second example, in vim, I used visual
block mode to delete a single space, rather than actually recreate
that 3-space indent.


More information about the users mailing list