Live data from Hacker News

Lisp and Haskell (2015)

markkarpov.com

61–70 of 173 posts

Re: Lisp and Haskell (2015)

#61
post #5

> if your code compiles, it probably works I'm always a little frustrated whenever I see this aphorism perpetuated since I think it gives the impression that a static type system is doing more than it is actually doing. In type systems that are used outside of academia, the main thing your static types are doing is checking whether the shapes of your data and functions all line up. With some small exceptions, that's…

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)

#62
post #15

Earlier quoted context omitted.

In Haskell, it's both common and idiomatic. There's always a tradeoff between the usefulness of having static guarantees, and the complexity of the type system features that enable those guarantees. In some cases it's more trouble than it's worth to enforce certain static properties - you have to weigh the costs and benefits on a case by case basis. But it's nice to have the option, and Haskellers make use of it freq…

How many functions have you seen, in Haskell, that take Nonempty a versus taking [a]? There are reasons why this type of pattern doesn't scale well, and the Haskell designers knew it. Similarly, if they had defined head [a] -> Optional a, that would have produced complications of its own.

What complications would it have produced? Eg Elm has `List.head : List a -> Maybe a` and it's just fine I think?

Re: Lisp and Haskell (2015)

#63
post #32

Earlier quoted context omitted.

How many functions have you seen, in Haskell, that take Nonempty a versus taking [a]? There are reasons why this type of pattern doesn't scale well, and the Haskell designers knew it. Similarly, if they had defined head [a] -> Optional a, that would have produced complications of its own.

The general consensus in the community afaik, is that head :: [a] -> a in Prelude was a mistake, and the default should be to return a Maybe/Optional. Beginners are generally advised to avoid head, and all other partial functions in the Prelude, such as tail and the indexing operator, wherever possible, and rely on pattern matching instead, which is generally sufficient and more idiomatic for the relevant use cases.…

Well, since the Prelude was never changed to at least add an alternative headMaybe, perhaps it was not seen as a deep enough mistake.

Yes, I am aware that the Haskell community generally tries to push more guarantees to compile time, and that Haskell's ease of defining and using new types equivalent to existing ones but that signify some property helps a lot in this.

Still, there is some limit to this. For example, you have many libraries adding measurement units for Haskell, and many libraries doing linear algebra. But you won't find too many people doing linear algebra with measurement units, because the types of intermediate results explode too much (e.g. a type-safe unit-of-measurement matrix multiplication of 3x3 matrices has 18 type parameters, with complicated restrictions between them).

Re: Lisp and Haskell (2015)

#64
post #61
post #5

> if your code compiles, it probably works I'm always a little frustrated whenever I see this aphorism perpetuated since I think it gives the impression that a static type system is doing more than it is actually doing. In type systems that are used outside of academia, the main thing your static types are doing is checking whether the shapes of your data and functions all line up. With some small exceptions, that's…

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, excep…

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)

#65
post #64
post #61

Earlier quoted context omitted.

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, excep…

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))

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)

#66
post #60

Earlier quoted context omitted.

Which is absolutely bizarre to me because most of my experience with Python has been with people who are effectively scientists who don't give a rats-ass about good coding practices and yet I rarely had issues with receiving the wrong type. Don't get me wrong though, I spent the first 10 years of programming with C and Java so I understand the comfort that a statically typed language provides. I just haven't found it…

> Frankly, if you were to ask me what the top 3 sources of my headaches in Python are, number 1, 2, and 3 would be, receiving a None value when I expected something else Curious example, because that's absolutely something a sufficiently advanced and well-thought-out static type system can completely solve. You're right that the type systems of C and Java do not meet this bar, however.

Is null even a type though? If not, then it would seem it's not a static type system that's solving it; it's something else entirely solving it (whether or not that something is contained in that specific type system).

Re: Lisp and Haskell (2015)

#67
post #38
post #5

> if your code compiles, it probably works I'm always a little frustrated whenever I see this aphorism perpetuated since I think it gives the impression that a static type system is doing more than it is actually doing. In type systems that are used outside of academia, the main thing your static types are doing is checking whether the shapes of your data and functions all line up. With some small exceptions, that's…

> you don't even want to pay the type annotation tax for. 95% of annotations in the average haskell codebase are unneeded for compilation. People still write them, because they are a useful source of information when reading the code. So tell me, if you feel like you don't want to write down that one line of comment which happens to be checked against the compiler, I guess you probably don't write tests or documentat…

> I guess you probably don't write tests or documentation either?

Why would any experienced programmer make such a blanket statement? As always, it depends.

Sometimes there is value in documentation (or whatever unchecked types in fancy languages), especially for stuff that is read a lot more, by other people, or yourself in the future.

For a lot of stuff there is no point in writing documentation. For example, if the code is obvious and pedestrian. Or if you'll just throw away everything, or refactor a month later.

And very often, writing documentation is not only a waste of time, but downright harmful, because code and documentation easily diverge.

My personal advice (not just for documentation) is to strive on the side of doing as little as you can get away with, and only introduce structure and bureaucracy if there's a clear indication that it's required.

Re: Lisp and Haskell (2015)

#68
post #5

> if your code compiles, it probably works I'm always a little frustrated whenever I see this aphorism perpetuated since I think it gives the impression that a static type system is doing more than it is actually doing. In type systems that are used outside of academia, the main thing your static types are doing is checking whether the shapes of your data and functions all line up. With some small exceptions, that's…

I find explicit null checking almost as valuable as static typing. Tests can cover a lot of the same ground as types but it’s very easy to have tests that don’t anticipate the nulls you only get in production.

Re: Lisp and Haskell (2015)

#69
post #13
post #9

Earlier quoted context omitted.

Parentheses in Lisp is like significant whitespace in Python: A big stumbling block for people who don't use the language.

But for those who use the language one is a powerful feature and the other a minor annoyance.

The parens made me reluctant to try lisp. Then, 5ish years ago I decided to "just do it", and now they don't bother me at all.

The experience of a beginner is also important though, especially for language adoption, this might be one of the reasons why lisp flavours are not more popular.

Re: Lisp and Haskell (2015)

#70
post #5

> if your code compiles, it probably works I'm always a little frustrated whenever I see this aphorism perpetuated since I think it gives the impression that a static type system is doing more than it is actually doing. In type systems that are used outside of academia, the main thing your static types are doing is checking whether the shapes of your data and functions all line up. With some small exceptions, that's…

> I think it gives the impression that a static type system is doing more than it is actually doing

This statement probably gets to the heart of the issue.

A static type system isn't something that just "does things", nor can we conduct a proper analysis when the dialectic is reduced to "static vs dynamic".

The first thing to realize is that there exists a spectrum in how much we encode in our type system. Even in weaker type systems like those of Go or Java, we can make illegal states unrepresentable, encode invariants, create typesafe abstractions, etc. Or we could just treat everything as object and not make any attempt to encode anything at all in the type system.

In the former case, the aphorism "if your code compiles, it probably works", is more true than in the latter case.

The next thing to realize is that the expressiveness of a type system places a ceiling on what can be usefully encoded in that type system. So in languages like Haskell, it's possible to get closer to the "if your code compiles, it probably works" ideal, than in a language with a weaker type system. But even in Haskell, it's possible to just write everything in IO and not make any attempt to encode your constraints in the type system.

So in that sense, a type system is just a tool. If wielded effectively, it lets us offload a lot of work to the compiler in a sort of symbiotic back-and-forth. And some languages give us better static typing tools, to make that symbiosis more effective. But if we're not using simpler type systems to their fullest, we're not likely to be pushing against their limits, making it a lot harder to justify more advanced type systems.

So "if your code compiles, it probably works" shouldn't be read as a statement of fact, but as an aspirational goal, one that we can move towards by more effective use of our tools.

Post reply on HN