← Back to context

Comment by hajile

1 year ago

I feel that he got a lot of pressure from the FP community and wrote a bunch of nonsense instead of being straightforward with them.

The only relevant reason he lists is point-free, but he doesn't go far enough. Point-free very often turns into write-only balls of unmaintainable nastiness. Wanting to discourage this behavior is a perfectly reasonable position. Unfortunately, this one true argument is given the most tepid treatment of all the reasons.

Everything else doesn't hold water.

As he knows way better than most, Elm has auto-curry and has been the inspiration for several other languages getting better error messages.

Any language with higher-order functions can give a function as a result and if you haven't read the docs or checked the type, you won't expect it. He left higher-order function in, so even he doesn't really believe this complaint.

The argument about currying and pipe isn't really true. The pipe is static syntax known to the compiler at compile time. You could just decide that the left argument is applied/curried to the function before the right argument.

I particularly hate the learning curve argument. Lots of great and necessary things are hard to learn. The only question is a value judgement about if the learning is worth the payoff. I'd guess that most of the Roc users already learned about currying with a more popular FP language before every looking at Roc, so I don't think this argument really applies here (though I wouldn't really care if he still believed it wasn't worth the learning payoff for the fraction of remaining users).

To reiterate, I agree with his conclusion to exclude currying, but I wish he were more straightforward with his one good answer that would tick off a lot of FP users rather than resorting to a ton of strawman arguments.

No trolling/nitpicking from me: You wrote <<The only relevant reason he lists is point-free>>. What do you mean by "point-free"... or did you write "point three" and it was auto-corrected on a mobile phone?

I also tried Googling for that term (never heard before), and I found these:

    https://stackoverflow.com/questions/944446/what-is-point-free-style-in-functional-programming

    https://en.wikipedia.org/wiki/Tacit_programming

If you really meant "point-free", can you tell me where in his post he mentions it? I would like to learn more.

  • Point-free is a functional programming style where you avoid assignments and prefer to do everything as a group of nested, mapped, and composed functions. It relies heavily on partial application. Like everything, it can be good when used in moderation.

    Here's a made-up JS example. I've attempted to be fair to both approaches doing it how I personally would with each.

        //typical implementation
        const howManyAdults = (data) => {
          const adults = data
            .flatMap(obj => obj.type === 'parent' ? [obj, ...obj.children] : obj)
            .filter(obj => typeof obj.age === 'number')
            .filter(obj => obj.age >= 18 && obj.age < 150)
    
          adults.forEach(adult =>
            console.log(`${adult.name} is an adult age ${adult.age}`)
          )
    
          return adults.length
        }
    
    
        //nice "point-free" implementation using helper functions
        const transformData = obj => obj.type === 'parent' ? [obj, ...obj.children] : [obj]
        const isAdult = obj => obj.age >= 18 && obj.age < 130
    
        const howManyAdults = pipe(
          flatMap(transformData),
          filter(hasTypeOf('age', 'number'),
          filter(isAdult),
          logEach`${pick('name')} is an adult age ${pick('age')}`,
          len,
        )
    
    
        //completely point-free gets crazy
        const howManyAdults = pipe(
          flatMap(ifElse(
            compose(eq('parent'), pick('type')), 
            juxt([identity, pick('children']),
            identity,
          ), []),
          filter(hasTypeOf('age', 'number'),
          filter(both(gte(pick('age',18), lt(pick('age'), 130)))),
          logEach`${pick('name')} is an adult age ${pick('age')}`,
          len,
        )
    

    I went through a point-free phase early in my career, but even with lots of practice, I can't believe that most devs ever find the third example as readable as the first or second. I'd also note that this is a trivial example and doesn't require you to track any monad wrappers either.

    Personally, I rather like reading the moderate middle example because it removes the boilerplate and allows me to easily follow the overall flow without getting caught up in the details. But I'll take the first example every time if it means never dealing with the third example.

  • It's in the FAQ. Someone else posted a link to it in the comments, check it out. And yes, it is about tacit programming.