Hacker News new | ask | show | jobs
by substation13 1546 days ago
A related advantage is expression-orientated code.

In ML languages, code forms a beautiful tree structure where you can see the binding definition of each binding by looking down and right.

    let x = 
      something
      really
      big 
      and 
      complicated
whereas in a language built around statements it will be scattered all over:

    let x = null;

    x = something();

    x = somethingElse(x);

    x = anotherThing(x);
In the first case the definition of x is in one indented block; you can read it and move on.
3 comments

I just did some PHP the other day and I stumbled on a structure I was happy to have forgotten:

something(x, result);

somethingElse(result, result2);

anotherThing(result2, result3);

with the occasional surprise mutation when you just have to do

something(x)

and x contains the result.

I also got caught recently with MomentJS with that one when doing mydate.add and discovering it also mutated the original instead of just returning the result.

I'm a very average programmer and far from a FP purist (I mostly use JS and Rails) and I'm surprised how much I now use some FP principles and how it feels very natural to me.

You can get something similar via method chaining: x.something().somethingElse().anotherThing()

C# extension methods aren't the perfect solution for this, but I love that they exist to at least make chaining possible without needing to modify the class that I'm applying the function to. D has an even better version of this called UFCS which makes it so `Bar something(Foo f)` can either be called as `something(x)` or `x.something()` without needing any special annotations like C# requires.

In F# (the most widely used ML?) expressions can be much more complicated than that, incorporating loops, conditionals etc.

To illustrate:

    let x = 
      let mutable maxScore = 0
      
      for item in items do
        if item.Score > maxScore then
          maxScore <- item.Score

      maxScore
Compared to:

    const x = 
      (() => {
        let maxScore = 0;
        
        for (const item of items) {
          if (item.score > maxScore) {
            maxScore = item.Score;
          }
        }

        return maxScore;
      })();
Not necessarily the best way to write this (you would probably use a sequence library) but hopefully conveys the idea.

Extension methods are definitely useful in OOP languages. However, a much cleaner language design is to remove the need for a "class" altogether and just have free-functions, function composition and a pipe operator.

None of this is something you can't simulate in OOP / procedural languages, it's just much more clunky.

You are free to structure it differently:

    let x = anotherThing(
      somethingElse(
        something()
      )
    );
That's worse because now you have to find the innermost function and then walk back outwards. It gets especially ugly when the calls require other parameters

    let x = anotherThing(
      somethingElse(
        something(
         x, y, z
        ), 1, 2, 3
      ), "a", "b", "c"
    );
Or maybe this looks cleaner?

    let x = anotherThing(
      somethingElse(
        something(x, y, z),
        1, 2, 3
      ),
      "a", "b", "c"
    );
Neither is easy to follow.
I take you are no big fan of Lisp? /s

Well, actually I agree with you - sometimes. I have actually written helper functions to get similar pipeline processing capabilities in js.

    function pipe(...args) {
      return args.reduce((x, fn) => fn(x), null);
    }

    pipe(
      () => 2,
      (x) => 7*x,
      console.log
    )