News for hackers. Stuff that matters.
Re:Rails is not written in Ruby by mmahemoff on Friday February 04, 2022 @05:31PM (#30214304)
Composability.

some_array.do_thing().do_another_thing().do_something_else()

versus

do_something_else(do_another_thing(do_thing(some_array)))

Note the first example could be an example of chaining - where each function returns "self" and we progressively transform the object. Or it could be simply each function returning different values which are operated on. Either way, the pattern is usually cleaner and easier to debug than the nested equivalent.

Re:Rails is not written in Ruby by jonnycat on Friday February 04, 2022 @05:44PM (#30214486)
Elixir (and some other languages as well) solves this with the pipe operator:

some_array |> do_thing() |> do_another_thing() |> do_something_else()

Even though Elixir is functional and aggressively not object oriented, if you squint at this pattern you get the readability of an OO-style method chaining API.

Re:Rails is not written in Ruby by omoikane on Friday February 04, 2022 @06:03PM (#30214724)
Re:Rails is not written in Ruby by leifg on Friday February 04, 2022 @06:41PM (#30215135)
If I understand the Ruby pipeline operator it's essentially an alias of the dot. The example in the feature tracker is:

``` 1.. |> take 10 |> map {|e| e2} |> (x) ```

which is equivalent to

``` (1...).take(10).map{|e| e2} ```

So in order to use it you still need extensions on every single object. The only reason that looks nice is because in this example you continuously call methods on lists that return a list. You can't add `my_custom_list_operation(list_input)` in between.

Re:Rails is not written in Ruby by WallowingInShit on Saturday February 05, 2022 @10:31AM (#30222028)
Same thing, but for JavaScript: https://github.com/tc39/proposal-pipeline-operator

Going forward, the pipe operator is going to be showing up in many new languages. I've used elixir a lot, and the pipe operator is a genius piece of syntactic sugar.

Re:Rails is not written in Ruby by dragonwriter on Friday February 04, 2022 @07:10PM (#30215414)
It's worth noting that that version of pipeline operator was abandoned, while there are still (some newer) open feature requests for a more traditional functional-style pipeline operator.
Re:Rails is not written in Ruby by tylerscott on Friday February 04, 2022 @09:38PM (#30216519)
yield_self is an less elegant way to achieve basically the same thing as |>.
Re:Rails is not written in Ruby by jon1628492 on Friday February 04, 2022 @06:03PM (#30214722)
Or alternatively,

    some_array
    |> do_thing()
    |> do_another_thing()
    |> do_something_else()
Re:Rails is not written in Ruby by amw-zero on Saturday February 05, 2022 @01:54AM (#30218524)
Now replace |> with . And you have the previous code example. The difference is completely superficial.
Re:Rails is not written in Ruby by jonnycat on Saturday February 05, 2022 @07:28AM (#30220380)
The end result is superficially different. That's the point - we like the flow of the end result! But the implementation is completely different, as it's pure functional compared to object oriented.

"string".do_stuff() - do_stuff() is a method on the "string" class, with all of the potential gotchas that might come with an OO implementation. Does the method mutate the internal state of the object? Does the particular class override the default implementation? Is it a monkey-patched implementation (the issue raised by this article)?

"string" |> do_stuff() - do_stuff() is a standalone pure function. The data is immutable and does exactly what it says.

Re:Rails is not written in Ruby by machiaweliczny on Saturday February 05, 2022 @06:40AM (#30220162)
It's not because you can use any function with pipeline and with dots chaining you can use only methods returned by previous function call.
Re:Rails is not written in Ruby by kazinator on Saturday February 05, 2022 @01:17PM (#30223938)
That is false; you cannot chain functions that do not return a value that is suitable as an argument to the next function.

a.this().that() requires that a.this() returns an object on which .that() can be invoked.

(that (this a)) requires that (this a) returns an object that is a suitable argument to that.

Re:Rails is not written in Ruby by kazinator on Saturday February 05, 2022 @01:15PM (#30223914)
Not completely, because the . has a meaning: select a member/field/slot from a class object.
Re:Rails is not written in Ruby by cuillevel3 on Friday February 04, 2022 @06:19PM (#30214905)
"I bacon eat"

Ruby syntax apparently is especially nice if you know Japanese: https://flatironschool.com/blog/ruby-and-the-japanese-langua...

Re:Rails is not written in Ruby by empthought on Friday February 04, 2022 @05:38PM (#30214411)
It's super ironic that you used the term "composability" and didn't actually use functional composition for the second example.

(do_thing . do_another_thing . do_something_else) some_array

Re:Rails is not written in Ruby by funklute on Friday February 04, 2022 @05:48PM (#30214531)
But this kind of functional composition often doesn't have first-class support in the language syntax. That is, it often requires you to rely on helper functions or libraries. As a result, it's not nearly as readable or intuitively obvious as the chaining example (and this is in contrast to mathematical notation, where functional composition is very clear, in my opinion).
Re:Rails is not written in Ruby by dragonwriter on Friday February 04, 2022 @07:51PM (#30215784)
> But this kind of functional composition often doesn’t have first-class support in the language syntax.

Are we talking about Ruby? Because it does.

It has both:

  (do_thing >> do_another_thing >> do_the_last_thing)[arg0,…,argn]
And:

  (do_the_last_thing << do_another_thing << do_thing)[arg0,…,argn]
(Where the things in the composition are procs/lambdas, the closest thing Ruby has to functions as distinct from methods.)
Re:Rails is not written in Ruby by funklute on Saturday February 05, 2022 @04:34AM (#30219456)
> Are we talking about Ruby?

I wasn't thinking specifically of Ruby (and indeed I don't know Ruby, nor was I aware that it has this kind of support).

Rather, I was thinking about programming languages in general, since the top-level comment seemed to be talking about the general principle.

Out of 5 languages I use on a regular basis, only one of them has _some_ support for function composition. But even then, not as flexible as what you're demonstrating here. So my point is that across programming languages, the chaining approach is generally more readable than the composition approach. And that in turn favours methods over global functions.

If I ever start using Ruby, I would probably be on the fence about which approach is better though.

Re:Rails is not written in Ruby by samatman on Friday February 04, 2022 @07:54PM (#30215809)
He did in fact, you're using points to represent (ironic!) the point-free style, prefix style and circumfix style, they're all function composition.

Which, believe it or not, they still teach function composition as good old g(f(x)) in high school algebra...

Re:Rails is not written in Ruby by wtetzner on Friday February 04, 2022 @07:26PM (#30215586)
Shouldn't it be the other way around?

    (do_something_else . do_another_thing . do_thing) some_array
Re:Rails is not written in Ruby by timr on Friday February 04, 2022 @05:44PM (#30214472)
You don't need to implement chaining for X.foo() to make sense. It's object-oriented syntax. If anything, languages like Python are the odd ducks here, mixing magical globals (len(X) => X.__len__()) with plain-old OO syntax conventions.

That Ruby also implements X.foo().bar().baz() is a weird thing about Ruby that can be very powerful, but is unrelated to the dot syntax.

I feel like roughly half of Ruby programmers treat it as "Lisp but better", which leads to confusion about the OO aspects of the language.

Re:Rails is not written in Ruby by choward on Friday February 04, 2022 @05:38PM (#30214400)
So it's a purely a syntax issue? Other languages like elm have a cleaner syntax for that.
Re:Rails is not written in Ruby by vlunkr on Friday February 04, 2022 @06:36PM (#30215094)
Well you can't write your backend in elm, so that's not much help.
Re:Rails is not written in Ruby by choward on Saturday February 05, 2022 @12:36PM (#30223480)
That's why I said other languages. Apparently elixir has that feature too.
Re:Rails is not written in Ruby by bryanrasmussen on Friday February 04, 2022 @05:54PM (#30214615)
I'm pretty sure they're both equally composable as I understand the term for programming language usage, it's just that you prefer the first way of writing it.

Admittedly I don't think anyone really prefers the second way of writing it, but that's not the same as being ecstatic over the first one.

Re:Rails is not written in Ruby by greiskul on Friday February 04, 2022 @06:27PM (#30214976)
Programming language syntax matters. It's the interface in which the programmer writes the code, synthetic sugar can reduce the "mental overload" of a feature.

For example, in C, the array operator is mostly synthetic sugar for pointer addition, and could be removed. Should it be removed? Of course not, cause programmers are not thinking of pointer arithmetic when indexing an array.

And as much as I love lisp, it's one of the top reasons none of them ever had widespread adaption. Nobody likes having to do math like this (* (+ (sqrt 4) 2) 4).

Re:Rails is not written in Ruby by didibus on Friday February 04, 2022 @09:44PM (#30216572)
There's a lot of advantages to having the Lisp syntax, especially with code navigation and editing. I personally much prefer it.

And because of easy macros (another advantage of the syntax), you can easily add syntactic sugar as you see fit where you care.

Re:Rails is not written in Ruby by vlunkr on Friday February 04, 2022 @06:37PM (#30215100)
While composability is nice, I've always assumed they were just really into OO ideas when they wrote active support.
Re:Rails is not written in Ruby by Twisol on Friday February 04, 2022 @05:56PM (#30214635)
A now-deleted (v_v) comment rightly called out that in many languages, method-style invocations are inextensible: a fixed set of methods exists when the class is created, and you can't add any more after the fact. So at some level you're forced to use direct-style invocations, and mixing the two can become ugly.

Some languages (like C# and Rust) support extension methods, so you can use method-style to invoke separately-defined procedures. But in other languages, method-style APIs become an inextensible privileged zone, and I personally prefer to minimize the number of inherent methods as much as possible. By preferring direct-style invocations, I avoid the temptation to stick a useful method on the class itself "for convenience".

Re:Rails is not written in Ruby by greiskul on Friday February 04, 2022 @06:38PM (#30215108)
As someone that used to have to program in Java, moved over to Kotlin, but now have to work with Java again... I miss extension methods so much. Util classes feel so hacky, and are so ugly to use. Specially for cases like streams, where you are invoking a chain of operations, and then have to store the temporary result in a variable, or have worse readability.

Or for quick and dirty debugging, with print statements, being able to put a println in the middle of any method call, just by adding a .also{println(it)} to the chain.

Re:Rails is not written in Ruby by Twisol on Friday February 04, 2022 @06:46PM (#30215172)
One pattern I see sometimes is to define a util class, but rather than having a bunch of static methods, you wrap the underlying object in a util wrapper that adds all of the utility methods. Unfortunately, Java also has no good way to delegate methods without simply redefining all of them, so it's by no means a perfect solution. (And it's not really worthwhile unless you've got a fluent API that returns the same object every time; otherwise you're wrapping for just one call, which is arguably noisier.)
Re:Rails is not written in Ruby by int_19h on Friday February 04, 2022 @06:27PM (#30214980)
The most straightforward solution to this is the one that D employs, whereby A(B) is syntactic sugar for A.B() if the former is not otherwise valid.
Re:Rails is not written in Ruby by dragonwriter on Friday February 04, 2022 @06:36PM (#30215087)
> The most straightforward solution to this is the one that D employs, whereby A(B) is syntactic sugar for A.B() if the former is not otherwise valid.

Isn't the D solution actually that:

  A.B(...)
is syntactic sugar for:

  B(A,...)
if A.B() is not specifically defined.

(Yours both flips the elements and reverse the direction of sugaring, as I understand it.)

Re:Rails is not written in Ruby by kazinator on Saturday February 05, 2022 @01:09PM (#30223837)
The first example is three characters longer and dispatches methods under the hood. There could easily be a dozen do_thing, do_another_thing and do_something_else methods.

In the second example, we have only function application. Put a breakpoint or print statement into do_another_thing and it is sure to go off.

(do_another_thing could support OOP dispatch as a generic function. A generic function is still something that is called first then dispatches methods based on its arguments. E.g. in Common Lisp we can (trace do-another-thing) to trace the calls.)