← Back to context

Comment by IshKebab

1 year ago

This is the first justification of not using Rust that I actually agree with. Well written.

I recommend reading Roc's FAQ too - it's got some really great points. E.g. I'm internally screaming YESSS! to this: https://www.roc-lang.org/faq.html#curried-functions

But then it has other weird features too, like they seem to be really emphasising "friendliness" (great!) but then it has weird syntax like `\` for anonymous functions (I dunno where that dumb syntax came from by Nix also uses it and it's pretty awful). Omitting brackets and commas for function calls is also a bad decision if you care about friendliness. I have yet to find a language where that doesn't make the code harder to read and understand.

The syntax came from Elm, which got it’s syntax from Haskell (where Nix also got it from) which got its syntax from ML.

It’s a syntax that’s several decades old at this point.

It’s different, but not harder. If you learned ML first, you’d found Algol/C-like syntax equally strange.

  • (ETA: speaking strictly about anonymous functions; on rereading you might be talking about the absence of parens and commas for function application.)

    That's not ML syntax. Haskell got it from Miranda, I guess?

    In SML you use the `fn` keyword to create an anonymous function; in Ocaml, it's `fun` instead.

They recently changed the syntax to add parens, commas and use `|arg|` for closures :)

https://github.com/roc-lang/roc/releases/tag/0.0.0-alpha2-ro...

  • Are the Roc people really doing everything they can to ruin everything they had going for them? Appealing to those who know nothing and won't be willing to touch anything FP at the cost of annoying those who actually want to try it is just so stupid

  • I say this as someone who enjoys reading Rust more than Haskell or Elm -- that looks like a really bad idea for aesthetic reasons anyway. I mean if you want the syntax to look like Zig or Rust, perhaps go all the way there instead of making a kind of a mutant hybrid like this. Syntax is superficial and the semantics actually matter, but that doesn't mean the syntax can be just anything.

    Is there some deeper technical reason for making such changes?

  • oh wow it went from being a very clear language to looking more like a hodgepodge of a few different languages.

  • Ah great improvements! I don't know why the sibling comments are so negative; this is clearly better.

    • Why? I'm used to \ meaning lambda. I understand you aren't, and that's fine. But it's just your weird opinion determined by what programming languages you've learned.

      Ultimately, it's just syntax and not so important. Semantics are important.

      15 replies →

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.

      1 reply →

    • 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.

It's the closest you get to 'λ' on a US keyboard.

  • So this assumes knowledge of an obscure theoretical programming language, and a dubious symbol replacement. Yeah...

    • It doesn't assume it, it's syntax, you can just use it without knowing where it comes from.

      It's like english. You don't need knowledge in obscure proto-germanic linguistics to use articles in your sentences. But if you want to understand why we seem to randomly attach "a" before nouns— proto-germanic linguistics has the answer (probably, I just speak english I don't know where all its syntax originates).

    • Functional programming is common in university curriculum. Terrifying that it can still be considered obscure.

  • IIRC, Richard explain that in one of his videos about Roc. I have seen at least a handful of them.

  • my hot take: the language should accept \, but formatters should replace it with λ

    • Lean does symbols very well. You can just type \r and it will replace it with a right arrow as you type.

> `\` for anonymous functions

A one-character ASCII rendering of the Greek lowercase letter lambda: λ

λx → x + 5

\x -> x + 5

Opposite effect one, lost interest in Roc after reading that.

If anything I don't think Haskell goes far enough the automatic currying, points free stuff. If you're going to be declarative, don't half ass it.

We recently changed Roc's lambda syntax from the syntax that languages like Elm and Haskell use...

    foo = \arg1, arg2 ->
        body

...to this:

    foo = |arg1, arg2|
        body

The reason for this change was that we have a new and extremely well-received language feature (landed but not yet formally announced) which results in `->` and `=>` having different meanings in the type system. This made it confusing to have `->` in the syntax for anonymous functions, because it seemed to suggest a connection with the type-level `->` that wasn't actually there.

The most popular syntax that mainstream languages use today for anonymous functions is something like `(arg1, arg2) => body` but of course that has the same problem with having an arrow in it, so changing to that wouldn't have solved the problem.

Rust uses `|arg1, arg2| body` (and Ruby kinda uses it too for blocks), and we'd all had fine experiences using that syntax in Rust, so we chose it as the new lambda syntax. You can see the new syntax in the code example at the top of roc-lang.org.

Yeah Rust compiles slowly, so we need two more half-baked languages - Zig and Roc, both of which I couldn't care less.

Rust's slow compilation comes from lots of features and an excellent generated machine code quality. Both Zig and Roc will be equally slow or slower if they match what Rust offers.

If all they want is fast compilation, they can just try Pascal.

> \ for anonymous functions

this didn't faze me in the least because it's just a more easily typed λ, the lambda character, which has for a long time now (many decades) been used to describe anonymous functions (i.e. "lambdas")

did you never take any formal CS education? if not, that might explain it

so before you jump to calling it "dumb", maybe next time lean on Chesterton's Fence for a bit.

https://sproutsschools.com/chesterton-fence-dont-destroy-wha...

That said, granted, the fact that it's also the escape character is problematic. Maybe /\ might have been better but that's even harder to type.