Hacker News new | ask | show | jobs
by endgame 3247 days ago
I'm really looking forward to the day when all these new-to-Elm people start hitting the complexity ceiling of their language and convince the maintainers to add just a little more power. Example: Haskell's typeclasses have a high power-to-weight ratio, and Elm has to work around their absence (e.g., writing a fresh map function for each data type). Once there's a critical mass of frontend types who understand the power of FP, convincing people to try FP won't be the difficult step any more, and Elm won't need to try so hard to be un-intimidating.
5 comments

Every added feature also adds complexity (and to be fair, in some cases, removes complexity). I love type classes, but I discovered in playing with Elm and specifically, TEA (The Elm Architecture) that the simplicity is also really nice even if you're comfortable doing type-level programming. There's something about Elm's simplicity that really works.
Agreed, but Elm is hurting for some way to deal with this stuff. Currently, the API reference lies by claiming things like (==) : a -> a -> Bool, because it lacks a generic mechanism to let the programmer think the necessary thoughts. (The story for comparison functions like (>) is similar.)

Now maybe Elm should stay simple and not jump all the way to multi-parameter type classes with functional dependencies, but it is already forced to pay some of the complexity burden to decide which types admit equality testing, comparison and so on.

Can you elaborate on what you mean when you say that (==) : a -> a -> Bool is a lie? I don't have enough experience with more powerful type systems to know what the more accurate claim would be. Do you mean that it should really be Eq a => a -> a -> Bool, because not all types can be checked for equality?
That's exactly what I mean: A type like a -> a -> Bool says that it will work for _any_ a, but we can easily find counter examples. We could pass in functions (say) and write something like (\x -> x * 2) == (\x -> x + 1). The type signature for (==) as written says it _should_ work, but it doesn't because there's no general way to compute function equality.

Instead, there's this temporary hack[0]:

> Note: Equality (in the Elm sense) is not possible for certain types. For example, the functions (\n -> n + 1) and (\n -> 1 + n) are “the same” but detecting this in general is undecidable. In a future release, the compiler will detect when (==) is used with problematic types and provide a helpful error message. This will require quite serious infrastructure work that makes sense to batch with another big project, so the stopgap is to crash as quickly as possible. Problematic types include functions and JavaScript values like Json.Encode.Value which could contain functions if passed through a port.

As you say, Haskell's version of (==) has the type Eq a => a -> a -> Bool, and function types do not belong to Eq.

[0]: http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Bas...

It we would be easy to add simple built-in constraints for things like equality, without jumping all the way to full-blown type classes. This would be a very minimal change to the type checker and should not require a rewrite.
This example with the absence of an Eq class is not related to just equality, it’s a symptom of a lack of generics. You’ll end up with a lot of special, built-in classes (e.g. Ord), while just adding simple type classes to Elm would make all of this solvable in a library (e.g. Elm’s Prelude).

Surely, if we agree that Eq, Ord, and Foldable are useful, they’re probably not the only useful type classes in existence.

I'm not thrilled by languages the do "rules for thee, not for me". (Another example: old Java had a two-valued enum type (bool) but you weren't allowed to make your own.)
I used several FP languages before coming to Elm, like F# and ClojureScript. I prefer Elm, been using it for about two years, and haven't missed something like interfaces at all.

Then again, I'm also one of those wierdos who think Go works just fine despite the lack of generics (though I think Go needs generics more than Elm needs something interface'y). Still, things are getting done and I'm happier than with most other languages.

> writing a fresh map function for each data type

You have to do that in Haskell as well (where »you« can be you or the compiler via DeriveFunctor); the key difference is at the use site: there is one map function (called fmap) that does the mapping, whereas in Elm (and pretty much all other languages that don’t have higher kinds while we’re at it) you have to use the specific map function for that type.

Can't for the life of me figure out why people equate FP with the abomination that Haskell is.

Erlang is FP. Javascript is FP. Ocaml is FP.

Type classes, or anything else that has "types" in them such as dependent, or liquid, are not FP, they are types. Types that found their way into a couple of FP languages.

I don't think many people (and not the parent) "equate" FP with Haskell, but Haskell is in a way the most functional mainstream language because it is the only lazy one. You can have a purely functional language without laziness in principle, but, as Simon Peyton-Jones points has said, laziness, despite having serious costs, keeps a language designer honest by making it impossible to add side effects. The non-lazy languages all have side effects. A language like OCaml can still be relatively functional because of the way it is typically used. While JavaScript is not traditionally used in a functional way but can be.

http://www.cs.nott.ac.uk/~gmh/appsem-slides/peytonjones.ppt

You’re mixing up laziness and purity. Purity makes side effects impossible, so to speak, not laziness.

Haskell is the most functional language because a Haskell function is a mathematical function, which can only transform its arguments into a value. Everything is a constant in Haskell, and functions transform one or more constants into a single, new constant.

No he/she isn't. The argument (as advanced by SPJ) is that it is so awkward for a programmer to use side-effects in a lazy language that it is (practically, not theoretically) impossible for a language designer to add them.
I don't know Haskell but how is laziness related to FP?
The only way I can think to relate them is (a) FP tends to highlight the importance of value-semantics over all others, (b) non-termination is an effect, (c) FP also, subsequent to a, tends to emphasize control of side effects, (d) in a terminating lambda calculus all evaluation strategies are confluent/equal under the value-semantics, thus (e) laziness is particularly _available_ in a FP language.
Attempt at translation from CS-speak:

Laziness matters less in a FP language because if we consider non-termination an effect (impure), all pure functions should behave exactly the same regardless of whether they're "lazy" or not (plus in FP sameness is defined as same values, due to "value-semantics" - unlike OO where every object has a unique identity, different from all others) because in the absence of side-effects order of evaluation is not important.

Of course reality is different, and laziness has very visible and important effects when Haskell programs run on today's computers :)

Ha, thank you.
> Haskell is in a way the most functional mainstream language

1. Is it the most functional language because it's lazy? No

2. Is it the most mainstream language because it's lazy? No

3. Is it the most functional mainstream language because it's lazy? No + no = no

Laziness does not a functional language make.

> You can have a purely functional language

What would be the purpose of a pure FP? Oh. There would be no purpose.

> laziness ... keeps a language designer honest by making it impossible to add side effects

wat

Laziness is delayed execution. That's it. There's _nothing_ stopping you from delaying a side effect.

> Laziness is delayed execution. That's it. There's _nothing_ stopping you from delaying a side effect.

Laziness is about more than just side effects.

I think "evaluation" or "reduction" would be better words than "execution" here. Laziness (call by need) is an evaluation strategy for (beta-)reducing expressions, which has two nice properties:

- If an expression can be reduced without diverging by some evaluation strategy, then it can be reduced without diverging using call by need.

- Efficiency, in the sense that no duplicated work is performed.

The other common evaluation strategies are call by name and call by value. Call by name has the first property, but not the second; so there are cases when it's exponentially slower than call by need. Call by value has the second property, but not the first, so there are cases when it diverges unnecessarily.

This 'unnecessary divergence' is a major reason why most programming languages end up overly complicated to understand (at least, mathematically). For example, consider something like a pair `(cons x y)`, and its projection functions `car` and `cdr`. We might want to describe their behaviour like this:

    ∀x. ∀y. (car (cons x y)) = x
    ∀x. ∀y. (cdr (cons x y)) = y
This is perfectly correct if we're using call by name or call by need, but it's wrong if we're using call by value. Why? Because under call by value `(car (cons x y))` and `(cdr (cons x y))` will diverge if either `x` or `y` diverges. Since the right-hand-sides only contain one variable each, they don't care whether or not the other diverges.

This is why Haskell programs can focus on constructing and destructing data, whilst most other languages must concern themselves with control flow at every point (branching, looping, divergence, delaying, forcing, etc.).

Thank you! I clean forgot about call-by-need vs. call-by-value
>Laziness is delayed execution. That's it. There's _nothing_ stopping you from delaying a side effect.

This is true, but unconstraimed side effects are too difficult to reason about in a lazy language to make them practical. So in practice, very few lazy languages have unconstrained side effects.

What definition of FP are you using? Because if it's just first-class functions, even Visual Basic has this now.
Exactly.

Let's take Wikipedia:

-- start quote --

In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions[1] or declarations[2] instead of statements. In functional code, the output value of a function depends only on the arguments that are passed to the function, so calling a function f twice with the same value for an argument x will produce the same result f(x) each time

-- end quote --

Only Haskell strictly conforms to that definition. None of your other examples do.
No. It only means that other languages support multiple paradigms.

Haskell doesn't strictly conform either, because it allows side effects (event though it's "only through escape hatches"). There's no such thing as a "pure functional programming language".

Let's take it from the top:

"In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data."

All of the languages I listed support this style. The moment you write an IO monad inside a function in Haskell, you break the illusion of Haskell's strict conformation to the definition.

Now you have the condescending tone! However, it appears you don't understand the IO monad. Programming using the IO monad is pure functional programming with full referential transparency. Please read Phil Wadler's paper, you will not understand this from that JavaScript snippet. The only backdoors are escape hatches like unsafePerformIO which are used for low-level libraries and FFI, they can be disabled with pragmas and/or compiler switches.

It is much harder to stay true to that definition using the other languages, multi-paradigm or not. That is why Haskell is so often mentioned in the context of FP.

What makes you say Haskell is an abomination?
Short version: You need a PhD in type theory to get anywhere.

Basically every obscure overly complicated concept that Haskell throws at you (all the while pretending to be the only true FP language out there) can be explained in 5 to 10 lines of Javascript: https://github.com/hemanth/functional-programming-jargon

Compare and contrast.

- Monad explained in Javascript: https://github.com/hemanth/functional-programming-jargon#mon...

- Timeline (sic!) of monad tutorials for Haskell: https://wiki.haskell.org/Monad_tutorials_timeline

The worst crime against humanity though is Haskell crap seeping into other languages (such as ramda, for instance: http://ramdajs.com)

> You need a PhD in type theory to get anywhere.

That is extremely false. Haskell isn't even a good playground for academic type theory -- you'd want Agda etc. for that. The development of the language over the last few years has been characterized by pragmatism and a focus on backwards-compatibility, which is why you can take code from something like ten years ago and have it run without issues on modern versions of the Haskell compiler with little to no modifications. (Let's not talk about how long code written in "modern" JS lasts.)

And I'd really like to see type class constraint resolution with functional dependencies, or Hindley-Milner type checking, or something of that sort implemented in "5-10 lines" of JS.

"There he goes again with his mumbo-jumbo," you say. That's right, you don't need to care about those things to write Haskell. What you meant is implementations of typeclasses ("interfaces") like Monad, Functor, and so on: they don't take much more code in Haskell.

  Array.prototype.chain = function (f) {
    return this.reduce((acc, it) => acc.concat(f(it)), [])
  }

  instance Monad [] where
    xs >>= f = concat (map f xs)
    return = pure
And we didn't even have to go the "this" route! Notice that your 5 - 10 lines of JS don't let you write code that works in any monad, whereas I can easily write

  whenM :: Monad m => m Bool -> m () -> m ()
  whenM cond action = do
    condition <- cond
    if cond then action else pure ()
In Elm, you'd have List.whenM, Array.whenM, Maybe.whenM, ... or a straight-up false type signature like their Eq ones, and in JS, a bunch of prototype methods with no unifying threads.

--

As for an example of why I think Haskell has the right ideas (few of us will say it's the "best language evar"):

I'd really like to see a JS version of the Servant library, which takes an API spec for a server and actually generates a fully functional server from that. Here's a description:

https://news.ycombinator.com/item?id=14149200

Does this strike you as idle theoretical self-enjoyment?

> This is extremely false [referring to "You need a PhD in type theory to get anywhere."]

almost immediately followed by

> And I'd really like to see type class constraint resolution, or Hindley-Milner type checking

You don't even see the irony in that, do you?

> they don't take much more code in Haskell:

riiight. I won't even go into the number of things that need to be explained there before you even start explaining what the code does.

I hadn't really finished editing my comment then. (I was eating at the time, haha.) I just saw your reply now.
Commenting on the edited comment :)

> Notice that your 5 - 10 lines of JS don't let you write code that works in any monad, whereas I can easily write

Maybe, maybe not. Depends on your requirements, really. The core language might never get this, but these 5-10 lines of code do some very important things:

- they explain monads faster and clearer than any of the countless monad tutorials that exist for Haskell

- they demystify monads and show that: hey, you've probably been writing monads all along (and re-implemented them yourself countless of times, no doubt)

- they (by necessity) dumb down the jargon-heavy lingo for easy consumption by average Joes like me :)

Edit: that page in particular has also shown me that I have used easily half of Haskell's things (functors of all flavors, monads, comonads, etc. etc. etc.) countless times over the years in Javascript and Erlang. I didn't even know I did, because no one scared me off with the theory, and strange explanations and names :)

Is it fair to say that your argument here is that "this resource was extremely valuable to me for understanding certain concepts in ways that Haskell-oriented resources in the past have not been"?

I think that's a totally fair criticism. I also believe that the Haskell resources can provide further value to you (and others in your position) over time if you choose to study them. Similarly, studying category theory or type theory or logic could.

Are these practical things to do? It depends upon your goals.

Sure! I don't disagree: Haskell learning materials are a far cry from adequate, and we definitely need to learn from, e.g. the Rust/Elixir/Elm communities here. For now, this is worth trying:

http://haskellbook.com/

Also, #haskell on IRC has been, without a doubt, one of the friendliest learning environments I've ever seen. Drop by sometime if the mood strikes you. :)

This is not really a definition of a Monad. For example, there's no mention of the Monad laws.

Monads are a very general and powerful abstraction that are not adequately described by your example. My advice to anyone is to read Phil Wadler's seminal paper, it is very easy to read.

> Does this strike you as idle theoretical self-enjoyment?

It does.

How many PhDs does one require to understand/correct/debug all the :> and :<|> etc.?

Speaking from experience, zero.

> debug

But the whole point of a good compiler is that it tells you when you're wrong! (Instead of having to write hundreds of tests (and thousands of Node test runners).)

> :> and :<|>

You can just treat them as syntax, like the largest proportion of every other language, but with the opportunity of actually being able to write things like that yourself later.

Observe:

    type API = "polls"                                           :> Get  '[JSON] [Poll]
          :<|> "polls" :> Capture "question_id" Int              :> Get  '[JSON]  Poll
          :<|> "polls" :> Capture "question_id" Int :> "results" :> Get  '[JSON]  PollResults

The :> operator separates parts of a path, and the :<|> separates different URL patterns. This is the equivalent of this API from the Django documentation:

    urlpatterns = [
        # ex: /polls/
        url(r'^$', views.index, name='index'),
        # ex: /polls/5/
        url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
        # ex: /polls/5/results/
        url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results')
    ]
The only difference is it has less regexes in it, is capable of being checked for nonsense by a compiler much smarter than me, and gives you the aforementioned "server for free". I have had URL pattern-match errors with Django in the past, and having your compiler check that there aren't any is excellent.

Easier to maintain? Check.

Easier to read? Check. (If nothing, because of the lack of regexes.)

Defines the response type too? Check.

Easy to refactor? Check! Tired of typing "polls" at the beginning? Just lift it out: turn

    type API = "polls"                                           :> Get  '[JSON] [Poll]
          :<|> "polls" :> Capture "question_id" Int              :> Get  '[JSON]  Poll
          :<|> "polls" :> Capture "question_id" Int :> "results" :> Get  '[JSON]  PollResults
into

    type API = "polls" :> 
             (                                            Get  '[JSON] [Poll]
          :<|>  Capture "question_id" Int              :> Get  '[JSON]  Poll
          :<|>  Capture "question_id" Int :> "results" :> Get  '[JSON]  PollResults
             )
Types are first-class :)

> How many PhDs does one require to understand/correct/debug all the :> and :<|> etc.?

Definitely less than it takes to become comfortable with the quirks of literally everything in JS: perhaps you should give something an honest shot before telling people who have derived real-world benefits from using it in production that it's useless?

You certainly would need a PhD to fully understand Monads from that small JavaScript snippet. The Haskell link you gave gives Phil Wadler's original paper as the first link. It is easy to read, explains everything beautifully and full of many examples. Learn some basic Haskell for no other reason than to read seminal papers such as these. To favour some random JavaScript hacker on the internet and steer others away from the original work is anti-intellectualism.
Ah, here comes the condescending tone I've so come to appreciate from the Haskell programmers.

"Go and read", "anti-intellectualism".

Wadler's paper is an excellent piece of exposition that's us at the level of an upper-year undergraduate textbook. There's nothing condescending about referring a professional to a relevant paper in their discipline, but it is troubling when a professional won't even read over a paper.
You are misquoting me. I said steering others away from the original source of work to an interior source (incomplete at best) is anti-intellectualism.

I do not mean to be condescending, but I feel very strongly about this.

Wait... you're tone policing haskell users after referring to even the _adaptation_ of functional techniques as a "crime against humanity?"

Please rethink this approach. It is a bad approach. It fails to capture (what I think you) your argument (is) and antagonizes people needlessly. And quite frankly, a lot of people are being VERY nice by not following in the tradition of absolutely burying javascript for its nonsensical primitive type semantics.

> Basically every obscure overly complicated concept that Haskell throws at you (all the while pretending to be the only true FP language out there) can be explained in 5 to 10 lines of JavaScript

So presumably the same concepts can be explained in 5 to 10 lines of Haskell too.

I think you're confusing the refinement and polishing of ideas that's taken place in Haskell over the last two decades with the succinct presentation of those ideas once they've been worked out.

But they can't, can they. Or we wouldn't have the bazillion monad tutorials.

The funny thing, this is the trouble that plagues other Haskell-inspired work (such as Purescript)

It's obvious that the concept of monad can either be explained in 5-10 lines in both JavaScript and Haskell, or neither. Which are you claiming?
Here are some relevant discussions related:

https://news.ycombinator.com/item?id=14870642