

Lisk — Lisp and Haskell - ihodes
http://chrisdone.com/posts/2010-11-25-lisk-lisp-haskell.html

======
partition
While programming in Haskell I also struggled with Haskell's brand of
whitespace indentation, even though I come from Python. The key difference in
usability between Python's whitespace indentation system and Haskell's
indentation system is that you cannot start the lines in a block on the same
line as the block.

Python's is more restrictive: you must skip a line before starting a multi-
line indented block; i.e., this is not legal:

    
    
        if a == b: print "asdf"
            print "a"
        else:
            print "a"
    

This lets you decide on a very simple rule for dealing with whitespace
indentation: Each new block is started by inserting a carriage return and the
proper number of tabs.

Not so in Haskell, because you are given the freedom to put the first line of
a block in the same line as the block starter:

    
    
        let x = 1
            y = 2
    
        do x <- 1
           y <- 2
    

Now you need to decide whether to be 3 or 4 spaces in depending on whether it
is a let or do block. You cannot use the rule, because the appropriate number
of spaces is no longer a multiple of your indentation unit.

That's just the simple case, because you are also allowed to put these block
starters (let, do, case, etc) at arbitrary points in an expression:

    
    
        f = let x = 1 in let y = x + 2 in
         y + 1
    

This is syntactically correct Haskell; but personally I like the idea of
lexical scope being represented by indentation level, which is not reflected
here.

It becomes very easy to get yourself into situations where you cannot use the
Python rule. But you can also impose restrictions on yourself so you _can_ use
that rule. Like always treating let, do, in, etc as "{"'s in C:

    
    
        f = let
                x = 1
            in
                let
                    y = x + 2
                in
                    y + 1
    

This may look a little verbose. But in real cases there would be a lot more
statements there. In the middle of all this I want to maintain the idea
"number of tabs corresponds to lexical scope". We can also push the analogy to
"{"'s in C and adopt the "K&R" style, but on block-starting expressions:

    
    
        f = let
            x = 1 in let
                y = x + 2 in
                    y + 1
    
    

There's also the solution of editors that just figure out where to indent, in
which case we can make it look pretty and still get the indentation right. I
think it's best to develop a consistent style that will work across editors
though.

~~~
Periodic
My rule has been to just pad "do" appropriately. Almost everything else works
out well with a 4-space tab. Also, don't ever use only one-or-two spaces for
indentation, and if you're finding you have a long expression with so much
indentation and unindentation that it gets confusing I usually find I'm doing
something confusing and should simplify it anyway. For example:

    
    
        let x = y
         in do  something
                something with
                          ( lots
                          . of
                          . args
                          )
                something else
            

Is not that hard to read for me, and actually looks quite nice.

And if you're getting those terribly long-expressions, break them up. They can
always be broken out in a let expression to be simpler, and with let the
precedence is obvious.

    
    
        map (alpha . bravo) . filter charlie <$> xray <|> yankee >> zulu
    

might be better written as

    
    
        let f1     = map (alpha . bravo) . filter charlee <$> xray
            f2     = yankee
            result = f1 <|> f2
         in result >> zulu
    

Haskell has the ability to write great one-liners, just like Perl, but it
takes discipline not to use them.

Of course naming your variables descriptively helps a lot too. Examples with
single-variable names are probably not the best.

~~~
eru
Yes, but like in Lisp, your Haskell code looks usually better without trailing
parens.

------
omegazero
I don't see how it's tackling the 'where' block--or it that would even be
possible under this scheme. I find that my code is much more legible with the
housekeeping and other less important helper-functions defined after the
important bits. The clunky code that the author complains about can be much
improved using where clauses to break it into chunks.

Taking the authors example and moving some of the processing to a where clause
makes the flow much easier to follow:

    
    
        someFunction conn (Foo n) (K {x=zot}) plib = do
          withTransaction conn $ \db ->
             coconut <- sizzleQuery [Foob n]
             potato  <- sizzleQuery [Foob n]
             let sizzle = (zotify coconut) ++ potato ++ gravy
                 record = fasterize $ makeRecord' sizzle
                 date = dateOrError sizzle
              in catch handler $ insertIntoDB sizzle plib
    
          where sizzleQuery = queryTheDB "select * from sausages where sizzle = ?"
                zotify c = zot (plib $ zip [1..] c)
                dateOrError d = error "Unable to parse date"
                                `fromMaybe` parseDate d "date"
                handler e = do something `with` (k $ the exception)
                makeRecord' s = (MakeRecord { recName = sizzle "name"
                                                , recAge  = sizzle "age"
                                                , recDate = date
                                                })
                                    $ Plib </> (fromMaybe "" $ sausages >>= bacon)
    
    

Also one of my favorite features of Haskell is using the $ as an unmatched
left parenthesis, saving you from that blob of closing parenthesis that every
lisp expression accumulates.

One last thing, the author complains about the ambiguity of the indentation,
but doesn't make any comment about the brace & semi-colon syntax. I personally
don't like it, but it should be explained why it isn't an acceptable solution.

~~~
kwantam
Well, brace and semicolon is considered "not idiomatic," though of course lisk
as an alternative is even less so.

Regarding "where," it seems like most of the time "let" is an acceptable
substitute. "where" is most convenient in conjunction with pattern matching
syntax, so perhaps the solution to the problem of where and pattern matches
can be neatly handled together.

------
kenjackson
Here's my obligatory dumb question of the day. What is the problem he is
trying to solve with the macros? It seems super straightforward, so I suspect
I'm missing something (being only conversational in Lisp and Haskell, although
don't know macros even decently):

Here's the snippet of code that describes the problem (which I don't get):

do exists <\- doesFileExist "lalala" if exists then ... else ...

~~~
rchowe
I don't know, what he seems to want to do can be expressed in haskell as

    
    
        doesFileExist "file" >>= \exists -> if exists
            then ...
            else ...
    

When he writes (do (>>= ... in his lisp example I don't think that he
understands that do is just syntactic sugar for >>=s and >>s, and that what's
actually happening is the data is being "taken out" (for lack of a better
term) of a monad.

His problem seems to be that you do have to bother with monadic IO when with a
macro he can just write (mif cond ...) and be done with it.

------
lsb
Not tackling pattern matching is a pretty big deficiency. Otherwise, it seems
interesting, and I wonder if

    
    
      :i-o
    

would really be easier to type than

    
    
      IO
    

for most use cases.

~~~
jpd
I agree, it looks like :i-o is the unfortunate result of the generic
hyphenation-to-capital-letter routine he has. IO should probably be a special
case (:io).

------
kwantam
How strange. I was _just_ thinking about doing this as a toy project earlier
today.

I honestly don't share the OP's hatred of Haskell syntax, nor do I find it
categorically worse than lispish languages. It's entertaining that he's gone
from capital letters to the sigils (":"). Both tend to be controversial and
which is better is more a matter of personal taste than anything.

I agree with another commenter that the lack of pattern matching syntax is
something of a deficiency, and I was also troubled over how I'd do this. A
"plambda" operator is one possibility that I was considering, though it feels
like it's a really hackey embedding of MLish in lispish:

    
    
         (plambda fname
                  ((pattern-1a pattern-1b ...) (stuff))
                  ((pattern-2a pattern-2b ...) (stuff)))
    

Also, rather than a preprocessor I was thinking of implementing the whole
thing in Template Haskell---not because it's easier, but really because I
expect the opposite: I figured it'd be a good way to really bite off a big
chunk of TH and get good at it.

EDIT: It occurs to me that one could just use a variant on cond to implement
pattern matching, e.g., "cond-argv":

    
    
         (define (fname a b) (cond-argv
                             ((a1 b1) (stuff))
                             ((a2 b2) (stuff))))

------
vorg
Everyone has their own syntactic preferences. And what, really, is the
difference between a preference for certain colors in an IDE and a preference
for a certain syntax? Why is one easily customizable and the other rigidly
defined by a language? If programs in all languages were stored on file as
lisp-style AST's, with comments attached to AST nodes, then programmers could
view programs using their own preferred syntax, as well as formatting and
colors.

~~~
blasdel
The pure version of this idea has failed repeatedly for more than 50 years —
noone ever writes their code in M-expressions :)

A debased version of the idea found success in Haskell, where the language
syntax is defined as desugaring transformations towards a core calculus, but
the internal representation is implementation-specific, and since the syntax
was decided up front nobody got invested in writing code in a plain System F.

A novel take on the idea is implemented wholeheartedly in Google's Go, where
there's a utility based on the compiler called gofmt that reserializes the
AST. They use it not only for style issues, but also to update the entire
standard library to reflect syntax changes.

------
anc2020
Bit of a plug, I've written something like this except that it doesn't work
via GHC and instead outputs source code (which means the gensyms aren't very
safe...)

I'm not working on it anymore (unfortunately never really got over the static
typing) but it should run and might be of interest:
<https://github.com/aliclark/hasp> <http://www-
student.cs.york.ac.uk/~anc505/code/hasp/hasp.html>

------
amouat
Shouldn't:

def fib(n):

    
    
        if   n < 2: return 1
    
        else      : return fib(n - 1) + fib(n - 2)
    

Be: ...

    
    
        if   n < 2: return n
    
        ...

?

Or am I being stupid?

EDIT: changed x to n.

~~~
tome
You must mean "return n", but I don't have an opinion on "correct" behvior.
All that's happening is the index is shifted by 1.

~~~
amouat
True. I'll update above regarding n.

However, it's certainly different to the other examples and I would expect
fib(2) to be 1 + 0 not 1 + 1.

It's a very minor point though - I think I'll stop now ;)

------
hakl
I remember something like this called Liskell, but I think it died.

~~~
gruseom
The OP devotes a paragraph to it.

~~~
hakl
Oops, that's embarrassing. Should've read all the way through.

