News for hackers. Stuff that matters.
Re:Lisp and Haskell (2015) by q3k on Thursday October 08, 2020 @06:28AM (#24717776)
Agreed. I have a strong background in 'bad' programming languages like Go, Python, C. Half a year ago I had a short stint at a Haskell shop and was thoroughly disillusioned.

That particular 'writing against the compiler' approach lended itself to write-once, over complex type juggling code. Because why make it readable - if it compiles, it works, and if it works, there won't be any need to read it, right? Well, except you still get bugs, and then debugging anything is like making sense of brainfuck. Maybe smarter people than me can juggle a handful of types and operators simultaneously in their head - I can't.

My favourite example of 'just because it compiles it doesn't mean it works is' Maybe [a]. I've had a colleague scratch their head for hours over something like this:

    userCount :: Foo -> Int
    userCount f = length $ getUsers f
This compiled, but always returned 1. Why? Well, because turns out `getUsers` returned `Maybe [User]`, and not `[User]` as the programmer expected, and somewhat asserted in their mind with types. And it also turns out that in Haskell, `length` takes a `Foldable`, and `Maybe` is a `Foldable`. Thus, a `length` of a Maybe will gladly return 0 or 1, which makes perfect sense from the point of view of the type system, but zero sense when it comes to humans. Whoops.
Re:Lisp and Haskell (2015) by tome on Thursday October 08, 2020 @06:40AM (#24717838)
The Foldable instance for Maybe is one of the biggest warts in Haskell. It's not limited to Haskell, by the way.

    def getUsers(f):
        return (logged_in_users(f), logged_out_users(f))

    def userCount(f):
        return len(getUsers(f))
Re:Lisp and Haskell (2015) by q3k on Thursday October 08, 2020 @06:43AM (#24717848)
Sure, but Python and Python developers don’t hype up its type system to be the solution to all your programming woes :).
Re:Lisp and Haskell (2015) by tome on Thursday October 08, 2020 @07:07AM (#24717962)
I'm not sure what "Haskell developers" are saying, but this Haskell developer says "Haskell provides many tools that can be used to solve programming woes. One such tool is its strong and flexible types system (there are several other tools, such as immutability, functional style)."
Re:Lisp and Haskell (2015) by Kednicma on Thursday October 08, 2020 @10:32AM (#24719384)
And Python provides readable syntax. Your example has an easy-to-spot bug, just from reading the code and knowing Python's semantics. Haskell's "programmable semicolon" ends up being a problem here because, thanks to typeclasses, the programmer usually does not know which semicolon they are programming under!

"Python provides many tools that can be used to solve programming woes. One such tool is its strong and readable syntax." Do you see how empty this sort of platitude is?

Re:Lisp and Haskell (2015) by gtf21 on Thursday October 08, 2020 @10:54AM (#24719620)
I don't think that illustrates that promotion of the type system as a useful tool is an "empty platitude". A well constructed syntax is certainly a nice feature of a programming language. One might say a language like brainfuck does not have a syntax which makes the code easily readable, and therefore is the worse for it.

Similarly, one might say that a strong type system is a useful tool. That doesn't make it a panacea (and I don't think that this was the claim), but that doesn't mean one can't appreciate it as a good tool.

Re:Lisp and Haskell (2015) by ghostwriter on Thursday October 08, 2020 @11:30AM (#24720052)
The problem with this comparison is that readable syntax is a subjective notion, whereas type system is an objective thing that can be evaluated based on its strictly defined boundaries - it's either possible or impossible to encode all required software specification with a provided type system, and if something is not possible, we know exactly what are these things that don't fit into the type system, and therefore we cannot rely on the compiler to prove their correctness.
Re:Lisp and Haskell (2015) by wk_end on Thursday October 08, 2020 @01:16PM (#24721362)
Er, the difference here is that we're given the Python code for `getUsers` but not the Haskell code for it. That makes the error in Python obvious. The incorrect `userCount` in Python looks just as obviously correct as the incorrect `userCount` in Haskell; and conversely, if we were given the code for the Haskell `getUsers` (or even just the type signature!) the error would be just as easy to spot.
Re:Lisp and Haskell (2015) by tome on Thursday October 08, 2020 @10:37AM (#24719438)
I didn't intend to make this into a competition between Haskell and Python.
Re:Lisp and Haskell (2015) by vindarel on Thursday October 08, 2020 @06:51PM (#24724789)
That's why Lisp has multiple return values, which are different from returning a tuple. It's great for refactoring. (https://lispcookbook.github.io/cl-cookbook/functions.html#mu...)
Re:Lisp and Haskell (2015) by 4WIW on Thursday October 08, 2020 @11:18AM (#24719884)
To make this code clearer:

    def get_users(f) -> Tuple[List, List]:
       return (logged_in_users(f), logged_out_users(f))

    def user_count(f) -> int:
       return len(get_users())
Now it's clear this is just wrong code. To fix it:

    def get_users(f) -> List:
       return logged_in_users(f) + logged_out_users(f)

    def user_count(f) -> int:
       return len(get_users())
Re:Lisp and Haskell (2015) by wk_end on Thursday October 08, 2020 @01:13PM (#24721316)
That doesn't really "fix it". Even assuming there are no other callers of `get_users` that you might be breaking here (possibly silently, since lists and tuples in Python have overlapping interfaces), you're making `get_users` less useful by dropping structure that at least someone, at some point, thought was useful.

Also, you should annotate the type of your lists :)

  def get_users(f) -> Tuple[List[User], List[User]]:
    return (logged_in_users(f), logged_out_users(f))

  def user_count(f) -> int:
    logged_in_users, logged_out_users = get_users()
    return len(logged_in_users) + len(logged_out_users)
Re:Lisp and Haskell (2015) by ghostwriter on Thursday October 08, 2020 @07:09AM (#24717975)
Why does getUsers return Maybe [a] in the first place? It should be [a]. You can also implement your own version of length that works only with iterables of your choice. The language allows leveraging type constraints to achieve this, the fact that your team didn't use it doesn't indicate an inherent flaw in the type system you've been provided with. And if you want to go an extra mile, use liquid-base instead of base.
Re:Lisp and Haskell (2015) by jshholland on Thursday October 08, 2020 @10:11AM (#24719221)
There is a substantive difference between "couldn't get any users because of an error" (Nothing) and "succeeded but found no users" (Just []).

But, if that really is the reason for Maybe [a] over [a], I'd still prefer Either SomeUsefulErrorType [a].

Re:Lisp and Haskell (2015) by ghostwriter on Thursday October 08, 2020 @10:56AM (#24719651)
Right, so we essentially say that the provided example doesn't refute the "it works if it compiles" slogan. It just works according to the provided specification expressed in the types, and it so happened that the specification was not specific enough.

My specification doesn't make a distinction between absent users and possible error responses because I assume that errors will be processed elsewhere and the getUsers function is applied as part of the "safe core" interface that is not concerned about error handling. If the specification was more precise (even more precise than in your suggestion, for instance, getUsers :: SafeIterable a => Foo -> a and length :: SafeIterable a => a -> Integer ), it would work differently.

The issue in the example has to do with the clarity of the intent encoded in the type signature, not the inherent issue of the type system that cannot guarantee safe runtime of the example. That's why I mentioned type constraints for specific iterables as a possible solution to the encoding problem.

A better example of the compiled-but-not-working case would be something that currently cannot be expressed well by the type system, something like async exceptions or access to closed resources (will be covered by linear types soon).

Re:Lisp and Haskell (2015) by marcosdumay on Thursday October 08, 2020 @03:10PM (#24722629)
Of course, "if it compiles it works" depends on you designing good types.

Haskell will never stop you from representing every value as String, and just throwing exceptions if the strings do not convert back, for example.

Re:Lisp and Haskell (2015) by dunefox on Thursday October 08, 2020 @10:36AM (#24719427)
`Either` would be much better, no?
Re:Lisp and Haskell (2015) by 08-15 on Thursday October 08, 2020 @10:52AM (#24719598)
No, because `Either` is a `Foldable`, too.

I know, not what you meant. But a custom sum type would not only allow you to return multiple different failure cases, you also wouldn't give it a `Foldable` instance with surprising semantics.

Plain old data types are seriously underrated these days.