IMO it should be way more common for a programming language to let you express a series of data-transformations as a left-to-right pipeline
like instead of doing this:
let foo = someCall(args);
foo = anotherCall(foo);
foo = aThirdCall(foo);
let me do this:
let foo = someCall(args)
|> anotherCall
|> aThirdCall;
a lot of functional languages do this even worse than in my first example, because you have to write it like this instead:
let foo = aThirdCall( anotherCall( someCall(args) ) );
or in Haskell syntax it would be more like this, I think:
foo = aThirdCall . anotherCall . someCall $ args
in both cases, the functions go in right-to-left order instead of left-to-right which IMO is pretty counterintuitive
(Lisps do this really well though, thanks to threading macros 💙)
@kasdeya <joke> Shell being forward thinking yet again </joke>
TIL that someone made a Typst package to do this..if a little ugly.
in the past I’ve played with the idea of reversing the order of a function and its arguments:
(args)someCall
so that my “functional language” example would look like this instead:
( ( (args)someCall )anotherCall )aThirdCall
that way, for a simple example like this you can read the order of the operations from left-to-right. although this is very unintuitive to me. I think a much better solution would just be to make it so that function composition is left-to-right instead of right-to-left:
someCall . anotherCall . aThirdCall $ args
but even then, things get trickier once you have functions which need multiple arguments:
let foo = someCall(args);
foo = anotherCall(moreArgs, foo);
foo = aThirdCall(foo, yetMoreArgs);
so you really need proper pipeline syntax for that:
let foo = someCall(args)
|> anotherCall(moreArgs, %)
|> aThirdCall(%, yetMoreArgs);
(~> (someCall args)
(anotherCall moreArgs _)
(aThirdCall _ yetMoreArgs))
@benrob0329 oohh - it’s amazing that Typst is powerful enough for it to even be possible to create the pipeline operator in it!
@kasdeya Haskell has the (&) for this purpose: you can write
args & aCall & anotherCall & aThirdCall
Or if it's monadic code you can write
args >>= aCall >>= anotherCall >>= aThirdCall
@dregntael oohh that’s amazing! I wish I had known about & while I was learning Haskell
@kasdeya Have you considered concatenative languages like Forth?
@kasdeya They're convenient.
I first encountered them in Clojure.
It's interesting how they've changed over the years.
@kasdeya Can I offer you a nice De Bruijn notation in this trying time? https://en.wikipedia.org/wiki/De_Bruijn_notation
@kasdeya Correct! So with lambda calculus your function definitions look like
`function = λ arg1 arg2 arg3 . body`
and applying functions look like
`function input1 input2 input3`
which is short for
`(λ arg1 arg2 arg3 . body) input1 input2 input3`
but with De Bruijn notation things flip around a bit. Function definitions still look like
`function = [arg1] [arg2] [arg3] body`
but now applying functions looks like
`(input3) (input2) (input1) function`
which... may look worse. But consider that it's short for
`(input3) (input2) (input1) [arg1] [arg2] [arg3] body`
and you may see that directly adjacent pairs of parentheses and square brackets now match up. The above will evaluate to
`(input3) (input2) [arg2] [arg3] body-with-arg1-filled-in`
which in turn evaluates to
`(input3) [arg3] body-with-arg1-and-arg2-filled-in`