5 ms·
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
by mmahemoff 5y ago
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.
- choward 5y agoSo it's a purely a syntax issue? Other languages like elm have a cleaner syntax for that.
- empthought 5y agoIt'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
- funklute 5y agoBut 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).
- dragonwriter 5y ago> 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.)
- funklute 5y ago> 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.
- wtetzner 5y agoShouldn't it be the other way around? (do_something_else . do_another_thing . do_thing) some_array
- samatman 5y agoHe 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...
- timr 5y agoYou 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.
- jonnycat 5y agoElixir (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.
- amw-zero 5y agoNow replace |> with . And you have the previous code example. The difference is completely superficial.
- machiaweliczny 5y agoIt's not because you can use any function with pipeline and with dots chaining you can use only methods returned by previous function call.
- kazinator 5y agoThat 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.
- jonnycat 5y agoThe 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.
- kazinator 5y ago
- deleted 5y ago[deleted]
- bryanrasmussen 5y agoI'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.
- greiskul 5y agoProgramming 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).
- didibus 5y agoThere'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.
- Twisol 5y agoA 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".
- int_19h 5y agoThe 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.
- dragonwriter 5y ago> 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.)
- greiskul 5y agoAs 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.
- Twisol 5y ago
- cuillevel3 5y ago"I bacon eat" Ruby syntax apparently is especially nice if you know Japanese: https://flatironschool.com/blog/ruby-and-the-japanese-language/ https://flatironschool.com/blog/ruby-and-the-japanese-langua...
- vlunkr 5y agoWhile composability is nice, I've always assumed they were just really into OO ideas when they wrote active support.
- kazinator 5y agoThe 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.)