Hacker News new | past | comments | ask | show | jobs | submit login

The article lost me at following sentence:

> A double arrow => describes constraints on the type variables used, and always come first: add1 :: Num a => a -> a describes a function which takes any type a which satisfies Num a, and returns a value of the same type.

Here, I don't understand what `Num a` syntax means. It was not defined before. And, what does "satisfies" mean? It is also not defined before it is used. (It is also never mentioned again in the article.) It is maddening to read such sloppily constructed prose. Define your terms before you use them!




If I may, as the author of "such sloppily constructed prose" (which I think might be a little unfair as a summary of all 6.5k words):

In this syntax note, I was not trying to teach someone to write Haskell programmes, but rather to give them just enough to understand the examples in the essay. I did test it on a couple of friends to see if it gave them enough to read the examples with, but was trying to balance the aim with not making this section a complete explainer (which would have been too long).

Perhaps I got the balance wrong, which is fair enough, but I don't think it's required to define _every single_ term upfront. It's also not crucial to the rest of the essay, so "The article lost me at following sentence" feels a bit churlish.


"Satisfies" is a common math term. For instance given

  x + 3 = 4
the value of x which satisfies the equation is 1. To satisfy is to be a value which makes true some truth valued formula (such as an invocation of a predicate like blue(x) or equation like the above, or inequality or such).

Satisfiability comes up in logic; a "SAT" problem is, in a nutshell, the problem of finding the combination of true/false values of a bunch of Boolean variables, which make some formula true.

When the Haskeller says that "Num a" is something that is satisfied by a, it means that it's a kind of predicate which says a is a Num. That predicate is true of an a which is a Num.


This terseness is what makes Haskell so hard to approach for beginners, unfortunately.

After you went through the effort of learning the syntax, it becomes very clear what it means, but I agree that dropping punctuation between a bunch of names isn't the clearest communication.

It becomes even worse when you start using third party libraries that abuse Haskell's ability to define custom operators, so you get entire APIs defined as arcane sigil invocations instead of readable functions.

That's why I gave up the idea of using Haskell for actual programming, and just took the functional programming philosophy from it to other languages.

As for the meaning, in a more conventional (rust) syntax, it'd be similar to this:

    fn add1<A: Num>(a: A) -> A


The problem with A -> B -> C is that it could be (A -> B) -> C or A -> (B -> C).

The -> operator in C is obvious. Though it does have left to right associativity, it's largely moot because only A would be a pointer-valued expression. The B and C would have to be member names:

  A->left->value.
Even if the associativity went right to left, the semantic interpretation would have to be that the left member of A is selected, and then the value member of that.

When X -> Y means "mapping from X to Y", the associativity could be anything, and make sense that way. Moreover (X -> Y) -> Z is different from X -> (Y -> Z). One is a function which maps an X-to-Y function to Z, whereas the other is a function which maps X to a Y-to-Z function.


I disagree that the 'arcane sigil invocations' are necessarily a problem. Yes, they can be, but I also think they can definitely be preferable!

Naming everything as a function often leads to a problem of very deep visual nesting. For example, map-then-filter can be written as "filter p . map f" in Haskell, whereas in sigil-free languages you'd write a mess like (lambda (x) (filter p (map f x))) in Lisp or "lambda x: filter(p, map(f, x))" in Python.

Of course, function composition is a very simple case, but something like lenses are another less simple case where a library would be unusable without custom operators.


Right off the bat, the problem is that filter p . map f looks like it wants to be filter, then map. Nearly all modern languages that have pipelining, whereby nested function calls are extraposed into a linear formm, go left to right.

In the Lisp or Python, it is crystal clear that the entire map expression is a constituent of the filter expression.


It just means that ‘a’ must be a Number [0]. In this context, I believe satisfies means that it implements the things defined in the ‘minimum definition’ in the link below. If you’re familiar with Go, it’s similar to something implementing an interface.

[0] https://hackage.haskell.org/package/base-4.20.0.1/docs/GHC-N...


well why does Num then come before a ? If a :: Num would mean a is a value of type Num, why does this "satisfies" constraint does not follow the pattern?


Technically, `a :: Num` would be declaring, or defining that `a` is of type `Num`. After you see `a :: Num`, you can assume from then on as you're reading the program that `a` has type `Num`; if something is incompatible with that assumption it will result in a compiler error. This is different from `Num a`, which is making the assertion that `a` is of type `Num`, but that assertion may evaluate as true or false. It's similar to how assignment is different from equality, so that most programming languages with C-style syntax make a distinction between `=` and `==`.

There's also the fact that `Num` is technically not a type, but a type class, which is like a level above a type: values are organized into types, and types are organized into classes. Though this is more of a limitation of Haskell: conceptually, type classes are just the types of types, but in practice, the way they're implemented means they can't be treated in a uniform way with ordinary types.

So that's why there's a syntactic distinction between `Num a` and `a :: Num`. As for why `Num` comes before `a`, there's certainly a reasonable argument for making it come after, given that we'd read it in English as "a is a Num". I think the reason it comes before is that it's based on the usual function call syntax, which is `f x` in Haskell (similar to `f(x)` in C-style languages, but without requiring the parentheses). `Num` is kind of like a function you call on a type which returns a boolean.


> If a :: Num would mean a is a value of type Num

`a` is the type. Num is a `class`.

Here's an example. x is an Int32 and y is an Int64. If they had type Num, then this would be valid:

  add :: Num -> Num -> Num           -- Not valid Haskell
  add x y = x + y
However it's not valid, because you can't add an Int32 and an Int64:

  add :: Int32 -> Int64 -> ?     -- Doesn't compile
  add x y = x + y
But you can add Nums together, as long as they're the same type. You indicate they're the same type by using the same type variable 'a':

  add :: a -> a -> a      -- Doesn't compile
  add x y = x + y
But now the above complains because you used (+) which belongs to Num, so you have to declare that these `a`s can (+) because they're Nums.

  add :: Num a => a -> a -> a
  add x y = x + y
And it comes out shorter than your suggestion of putting the constraints afterward:

  add :: (a :: Num) -> (a :: Num) -> (a :: Num)       -- Not valid Haskell
  add x y = x + y


Yes, this bad mathematician's lingo is really unnecessary. It means someone else wrote an implementation of an interface called Num for your `a` type. Well, it is not really an interface. The correct term is type class, but that is details.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: