36 comments

[ 4.3 ms ] story [ 95.3 ms ] thread
got 4/6 on PureScript, and 5/6 for Elm, and I don't know either language.

The page could probably stand to have a sentence saying that it is a quiz about "can you tell which of these 4 names of functions are the name of a function with the following type signature?" . Instead it has a picture of a unicorn's face?

Neat idea!

Anecdotally, though, I got 4/6 while knowing nothing about Haskell just by meta-analysis - a type signature including a "Foldable" term is most likely to relate to a method with "Fold" in the name :)

I was thinking today that there's something wrong about thinking too much in types and type relations.

Rule 5 of Rob Pike[1] says:

> Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.

which makes a lot of sense to me. You see this concept being used all over the place in Golang/Rust.

But functional languages make you think in very different ways, which makes your program organized in much different ways as well. Although I can't quite put my finger on what exactly is so different.

[1]: http://users.ece.utexas.edu/~adnan/pike.html

I loosely learned this too and live by it. In particular, it’s so easy to change algorithms but so hard to change data structures. The latter inform (pollute) everything about your functions and architecture.

Functions are just boring implementation detail. The structures tell you everything.

Types just describe your data structures, and type relations describe mappings between data structures. In this sense, functional programming is actually the purest form of data-first programming: It discourages inventing algorithms at any point in the process, instead asking you to think exclusively about what data structure you have and what data structure you want to end up with. You may have to step between multiple data structures to get from A to B, but the focus is always on the data, never on the algorithms.

From what I've seen, this is where people usually struggle when it comes to learning functional programming. People are so wrapped up in the algorithms that they have a hard time thinking in data in the way that is required to do functional programming effectively.

I guess it wouldn’t disagree with you as hard if most relations weren’t typed so loosely because of the polymorphism
Thinking in types and type relations sounds to me like Rule 5 taken to its logical conclusion. Types are your data structures, and they guide how you write your functions.

Agda[1] and Idris[2] turn this idea into interactive editing with typed holes. You put a "?" in the code and the compiler fills the hole in for you based on the type that it infers for it. Haskell has a less expressive type system but it also has typed holes and Wingman[3] extends the Haskell Language Server to use them for interactive editing.

[1] https://www.youtube.com/watch?v=bRudW0aBNg8

[2] https://www.youtube.com/watch?v=X36ye-1x_HQ

[3] https://haskellwingman.dev/

> Types are your data structures

Is this true? I understand types are abstract, while data structures are concrete.

Like, the type...

x -> a x y -> z y

...tells me nothing. But that is, among many things, the type for a hash map lookup. Still, a hash map is a particular implementation, and the type alone doesn't capture this – it could mislead me into thinking any implementation that fits this shape is fine.

You made that type artificially abstract by leaving out the type names, which in a nominally-typed language are a first-class part of the type signature, not arbitrary symbols you can leave out.

This is the actual type of Haskell's dictionary lookup:

k -> Map k a -> Maybe a

I don't know about you, but that's pretty self-explanatory to me.

The moment you name the type, you're talking about a particular implementation, no? The type system doesn't know about the semantics humans ascribe to names, it just matches shapes.

That's only because you, the programmer, expect a type named "hash map" to implement a hash map – but this information is not being described by the type itself, it's out-of-band. The actual definition of the hash map data structure is given by the algorithm. In fact, without inspecting the algorithm, it's impossible to know it correctly implements a hash map, you're just trusting the arbitrary name of the type.

> The moment you name the type, you're talking about a particular implementation, no?

Depending on the language, no. In many languages types can be subtyped, and if you name the supertype you do not know necessarily which implementation you have. (Incidentally, in Haskell, a Map is not a HashMap, it is a tree map.)

> The actual definition of the hash map data structure is given by the algorithm. In fact, without inspecting the algorithm, it's impossible to know it correctly implements a hash map, you're just trusting the arbitrary name of the type.

This is exactly my point! The question we started with is: does the type signature help you to reason about how to transition from one data structure to another data structure without having to think about the details of the algorithm? I argue that it does, because I tend to trust that standard library designers aren't capriciously violating my expectations for what a Map is.

If you trust the standard library designers, then the type signature abstracts away the implementation details of the algorithm so you can do what Rob Pike recommends and focus on the data.

If you don't trust your language's standard library, you have worse problems than difficulty reasoning about type relations.

> trust that standard library designers aren't capriciously violating my expectations for what a Map is.

You don't even need to trust them. The compiler checks that their Map is the same as what you call a Map.

In the scenario that I am imagining, you have defined your data structures as types. You're looking for an algorithm to perform a particular transformation on your data. This transformation is from one data type to another, so you know the "type signature" of the algorithm. If the algorithm is already implemented, then you can find it with typed holes or Hoogle. If it doesn't exist yet, you can write it.

Rob Pike's Rule 5 says that if your types are good, it will be self-evident what functions to use to transform them.

> You don't even need to trust them. The compiler checks that their Map is the same as what you call a Map.

Sort of. I think the person I was replying to was indicating that the implementation could be wrong and still match the type signature. As a simple example, `insert` could return the exact same Map object as was passed in (rather than one that has the key updated) and still match the type signature of `insert`.

On the whole you're right, because if your standard data structures are that bad you should really be using a different language, not choosing to ignore types because they might not be helpful this time.

> the type...

> x -> a x y -> z y

> ...tells me nothing.

I am guessing that you wouldn’t see a type signature like this in a real codebase. The type signature of Data.HashMap.Strict.lookup is

  (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
which is quite a bit more readable. You can even search Hoogle for x -> HashMap x y -> y and find it, try it!

https://hoogle.haskell.org/?hoogle=x%20-%3E%20HashMap%20x%20...

That's like saying "words are abstract, while nouns are concrete".

Not really. Types include datastructures. They define the set of values that a datastructure can assume. They do the same for everything else, like variables, functions etc.

You should look into "category theory for programmers" by Bartosz Milewski, or look into the dependently typed languages other people mentioned (Idris, Agda...), or how type theory is replacing set theory as the foundation of mathematics because it makes things computable and is not fundamentally broken (there is an interesting "HoTT book" you can look up that might be relevant).
There's one perspective (popular in funtional programming circles) that says data structures simply _are_ a special sort of function; that is, one that take no arguments.

Really, in practice this means data is a partially applied, curried, identity function. Put something in, get the same thing out later, acting as memoization.

In my perusals into the Haskell ecosystem, discovering Hoogle[1] was definitely a revelation on the power of a strongly-typed language. Sometimes, you know the _shape_ of the thing you are looking for, but not the name. The ability to search a repository of packages for all functions conforming to a certain type signature (e.g., (a -> Bool) -> [a] -> [a]) is a superpower.

[1] https://hoogle.haskell.org/

A minor nitpick: the crucial element here is that Haskell is statically typed. "Strong" typing is not well-defined, and even in contexts where that word is appropriate, it only exists as a relative descriptor (not in a vacuum).

But I agree with your takeaway. Hoogle is one of the coolest development tools I've ever used, and I desperately wish every other statically typed language had their own Hoogle.

"Strong" as a descriptor makes more sense if you see a type as a kind of contract: You agree that you will only perform these operations on this value, and the language agrees that all of those operations will be well-defined. "Strength" is how difficult it is to disregard those contracts and peel back the abstraction they enforce to get at some underlying representation, possibly all the way down to where "Bits Is Bits" and everything is permitted. You'll notice that this makes Python's type system stronger than the one C++ has, once you get over the idea that Python's types attach to values, not variables.
Right, so it's not very rigorously defined; it's kind of a "feeling" about how easy it is to subvert the high-level system, and it only makes sense to describe a language as "strong" in contrast to another language — which is kind of what I'd said originally. ;)

In any case, I think we agree that it's not what the parent comment of mine had intended to refer to!

And it isn't even all that useful as your only metric: A hypothetical language with only strings can have strong typing if it only allows you to do stringly things on those strings and doesn't, for example, allow you to ever see the codepoints as sequences of bytes; however, you'd have to represent everything in that language as strings, so the semantic types of those objects would be purely conceptual, like types in BCPL, where the compiler only knew about machine words, or, indeed, assembly language.

So we have at least three axes: Static vs Dynamic, Weak vs Strong, and Conceptual vs Semantic, where more Semantically-typed languages allow you to represent more of the meaning you're going for in the type system, and have the language enforce semantic rules like not being able to add person-height to person-weight even if they're both integral types.

(Note that my imaginary string-only language is also statically typed of necessity, given that all variables must have type string and no value can ever move from a variable of one type to a variable of another type.)

(comment deleted)
This is by now also a build-in feature of Scala docs, in case someone prefers a mainstream language with such superpower.

https://docs.scala-lang.org/scala3/guides/scaladoc/search-en...

Is Scala meaningfully more mainstream than Haskell?
Of course it depends on whom you ask.

But at least the usual programming language rankings would suggest that Scala is quite mainstream by now.

We have it currently at place 15 on RedMonk, place 15 on the IEEE Spectrum ranking, place 19 on PYPL; whereas Haskell does not show up in the Top20 anywhere there.

Also Scala job opportunities and salaries are quite good when looking at the IEEE and Stackoverflow surveys, ranking quite a bit ahead of Haskell.

I would say Scala is pretty much a mainstream language. There's not much room in the Top20 given there are hundreds of languages out there.

Yep, I've had multiple scala roles, no haskell roles yet.

Still, scala has always been promoted as being easier to learn (more like Java?) and being on the JVM, rather than winning on its own merits. So kotlin will probably eat scala.

One of the problems around Scala is that people come in with an expectation that Scala would be like Java. No, it isn't. It's a whole different language. It's much more similar to OCaml (besides the syntax, of course) than it's similar to Java.

Kotlin is only competition in the "better Java" space. But that's more or less quite a small part of Scala. Scala starts to be an interesting language in its own regard beyond this point. Kotlin OTOH does not even offer comparable features. So it can't replace Scala where Scala shines.

I would say that in the long run it's Kotlin that's "in danger" as Java becomes more and more a "better Java" itself. There is not much stuff left in Kotlin these days (besides the nicer syntax) that is still missing in Java. And it gets less with every Java release. As Kotlin, mostly "just a better Java", has not much to offer besides some syntax sugar it will have a hard stand against a future Java, imho. Scala does not have this problem as people mostly given up using it as a "better Java", or migrated to Kotlin (or even back to Java) for this purpose anyway, and are using it now in a more idiomatic way that can't be simply replaced by any "better Java".

Regarding simpler: That's for sure a strong point in favor of Scala compared to Haskell. Scala 3 is on its surface not more complicated than Python, I would say[1]. It's also easier to pick it up gradually, compared to Haskell where understanding even "Hello Wold" will require some understanding of monads.

For people who don't know Scala by now or didn't look into it for a long time: It's one of the better FP languages currently out there, but it won't drown you in alien concepts right form the start. It's easy to pick up. But it's still at least as powerful as Haskell. But more pragmatic and down to earth. It's worth having a look for sure!

The simplest and quickest way to give it a try is installing scala-cli[2], I guess.

Than, for bigger / real projects you would use likely Metals[3] for VS Code / vim, the Scala IDEA plugin, or, for the Emacs diehards, the new Ensime. (Where the Metals experience is imho currently the smoothest).

---

[1] https://docs.scala-lang.org/scala3/book/scala-for-python-dev...

[2] https://scala-cli.virtuslab.org/

[3] https://scalameta.org/metals/

> I would say that in the long run it's Kotlin that's "in danger" as Java becomes more and more a "better Java" itself. There is not much stuff left in Kotlin these days (besides the nicer syntax) that is still missing in Java. And it gets less with every Java release. As Kotlin, mostly "just a better Java", has not much to offer besides some syntax sugar it will have a hard stand against a future Java, imho.

There are two things that I miss any time I try to go back to Java from Kotlin, and recent improvements haven't helped matters:

1) Null safety. Java has Optional now, but most of the standard library still may or may not return null, and you have to read the javadoc to find out if you should be handling for null.

2) The collections library. Kotlin has the most comprehensive and consistent collections library I've ever seen in any statically typed language; it's comparable to Python in expressive power. Java doesn't even come close.

The features that Java has adopted (sealed interfaces, records, etc) are nice, but not the core of why I use Kotlin.

Tip for anyone who doesn’t know Haskell (eg me): if the signature has an IO return type, the answer has a verb in the name.
Amazingly, I got 6/6 on Haskell despite only dabbling in it a few years ago.

I suppose that says something about the descriptiveness of type signatures, or the generality of Haskell's functions.

WTF. 5/6 on Haskell despite having barely touched it 10+ years ago. Pretty descriptive signatures, although the function names could do with some clarification.
Yeah, I've never used purescript and got the same.
Haha I got 4/6 without knowing any Haskell. Maybe there is still hope!
(comment deleted)