[Haskell-cafe] Non-recursive let [Was: GHC bug? Let with guards loops]

Richard A. O'Keefe ok at cs.otago.ac.nz
Fri Jul 12 02:22:49 CEST 2013

On 11/07/2013, at 6:16 PM, <oleg at okmij.org> wrote:

> I'd like to emphasize that there is a precedent to non-recursive let
> in the world of (relatively pure) lazy functional programming.

So what?  You can find precedents for almost anything.
I could even point you to a lazy mostly-functional language
with assignment statements in which an identifier occurrence
may refer to two different variables in the course of execution.

Having a precedent doesn't mean that it's a good thing.

> The programming language Clean has such non-recursive let

I am familiar with Clean and used it quite a bit for several years.
My experience with that Clean idiom is *WHY* I hate this usage and
love monads.
> Let me point out the latest Report on the programming language Clean
>        http://clean.cs.ru.nl/download/doc/CleanLangRep.2.2.pdf

which I already have.  If the Clean developers hadn't decided to
concentrate on Windows, leaving the systems I used to wither,
and if they hadn't made fairly massive changes to the language
that broke all my code, it's _possible_ that I might eventually have
come to regard this style as acceptable.

> It seems the designers of Clean have the opposite view on the explicit
> renaming (that is, sequential numbering of unique variables).

That is so.  If that's what you want, you know where to find it.

Like I said, precedent is not proof of goodness.

>    readchars:: *File -> ([Char], *File)
>    readchars file
>    #    (ok,char,file)           = freadc file
>    |    not ok                   = ([],file)
>    #    (chars,file)             = readchars file
>    =    ([char:chars], file)

This is *PRECISELY* the kind of stuff that I find confusing.
If they would just *NUMBER* the states so that I can tell what
is happening when, I would be so much happier.

> The code uses the same name 'file' all throughout, shadowing it
> appropriately. Clean programmers truly do all IO in this style, see
> the examples in
>        http://clean.cs.ru.nl/download/supported/ObjectIO.1.2/doc/tutorial.pdf
> [To be sure I do not advocate using Clean notation '#' for
> non-recursive let in Haskell. Clean is well-known for its somewhat
> Spartan notation.]

I wouldn't call Clean Spartan.  Clean syntax is elaborate.
It achieves brevity not by avoiding keywords but by using
punctuation marks for them, as in [t] vs [!t] vs [|t]
-- does it leap to the eye that [t] is lazy, [!t] is head
strict, and [|t] is strictness-polymorphic? --
and the very important distinction between
a *function* f x = e and a *macro* f x :== e.
(There's a reason why the higher-order list processing
'functions' are actually 'macros'.  See page 109 of the report.
There's precedent for a LOT of things that I don't want in Haskell.)

> State monad is frequently mentioned as an alternative. But monads are
> a poor alternative to uniqueness typing.

In this particular case, uniqueness typing is an utter red herring.
People are advocating state monads JUST TO HIDE THE WIRING, not to
get the effect of destructive assignment.
I *agree* that uniqueness typing is a fine thing and recommended it
to the Mercury developers, who adopted it.

I don't care whether they are called monads, state combinators,
or weeblefretzers.  What I care about is that that
 - the states are HIDDEN from the human reader and
 - they are AUTOMATICALLY wired up correctly for the author.

Suppose we have

	# (x,s) = foo s
	# (y,z) = bar x s
	# (z,s) = ugh x y s

where my finger slipped on the s key in the second line and
pressed the z key instead.  Precisely BECAUSE the variable name
is the same each time, nobody notices, not the compiler, not you,
not me.  The program just goes wrong.

With numbered variables,

	let (x,s1) = foo s0
	    (y,z2) = bar x s1
	    (z,s3) = ugh x y s2
	in ...

the compiler notices that s2 isn't defined.

With suitable combinators,

	foo >>= \x -> bar x >>= \y -> ugh x y ...

nobody can make the mistake in the first place,
because the state variable isn't _there_ to get wrong.
> Why Clean is relatively unknown? Well, why is Amiga?

Clean is relatively unknown because
 - they started in the Macintosh world, and when
   they provided a compiler for the Unix world,
   they did not port their "modern" graphics and
   I/O library to it.  So you could never write
   a program that would run on Macs and other things.
 - they then abandoned the Macintosh world for
   Windows.  The Mac IDE was killed off; there is
   now an IDE for Windows but not MacOS or Linux.
 - other major features remain Windows-only
 - the change from Clean 1.3 to Clean 2 was huge,
   like I mentioned above, none of my code survived
   the change, and there was at that time no
   conversion program
 - the available books about Clean are way out of
   date, several drafts of other books remain
 - the documentation (like the Report) has always been
   rather amateurish and incomplete.  Certainly
   compared with the Haskell documentation.
 - there is nothing to compare with the Haskell Platform.
 - The language was originally called *Concurrent* Clean,
   running on a network of Macs.  But the concurrency
   support was dropped.  There are rumours of ideas of
   plans to do something about this some day, but there
   is nothing like the concurrency support that Haskell
   has *right now*.

If I had to write something on a Windows box, I would
definitely think about Clean again.  And then I would
use F#, because I can run F# on my Mac as well, and it
has a great deal more in the way of library support.

For what it's worth,

> let x = 1 in
-   let x = x+1 in
-     let x = x+2 in
-       x;;


val it : int = 4

in the F# interactive system, but

> let x = 1 in
- let x = x+1 in
- let x = x+2 in
-   x;;

prints "Duplicate definition of x" at the second line.

More information about the Haskell-Cafe mailing list