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.
See also:
https://mamememo.blogspot.com/2019/06/a-brief-history-of-pip...
``` 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.
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.
some_array
|> do_thing()
|> do_another_thing()
|> do_something_else()"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.
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.
Ruby syntax apparently is especially nice if you know Japanese: https://flatironschool.com/blog/ruby-and-the-japanese-langua...
(do_thing . do_another_thing . do_something_else) some_array
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.)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.
Which, believe it or not, they still teach function composition as good old g(f(x)) in high school algebra...
(do_something_else . do_another_thing . do_thing) some_arrayThat 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.
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.
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).
And because of easy macros (another advantage of the syntax), you can easily add syntactic sugar as you see fit where you care.
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".
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.
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.)
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.)
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.