3 ms·
One of the things I like about Java's streams is that it makes the verb-chaining more obvious, at least to me. list.stream().filter().mapToInt().sum() just rea
by thomascgalvin 7y ago
One of the things I like about Java's streams is that it makes the verb-chaining more obvious, at least to me.
list.stream().filter().mapToInt().sum() just reads better to me than sum( mapToInt( filter(list) ) )
- rtkwe 7y agoYeah I much prefer that style because it reads left to right like I naturally read as an English speaker. It flows more naturally from the original object and the changes applied to it.
- Twisol 7y agoAgreed. Some functional languages (like F#) have a pipelining operator |> that gives the same syntactic impression: list |> map (1+) |> filter odd |> sum
- codr7 7y agoAnd other languages, such as Forth; don't need one. list map filter odd sum
- Twisol 7y agoHuh. I would've expected it to be `odd filter`, not `filter odd`.
- the_gipsy 7y agoI have found myself using pipes many times in Elm, because they make the code so much more readable. Now I understand why! I haven't seen an autocomplete that works on pipes. But with a good type system, it is technically possible to list all compatible functions that take "noun" as first parameter. Sometimes you supply partially applied functions (also with pipes in noun->verb order) and that's not so easy to match: noun |> verb1 |> (otherNoun >> verb2) |> etc But perhaps even functions that take "noun" as nth parameter could be suggested.
- zwaps 7y agoInterestingly enough, the now standard data manipulation package in R, dplyr, uses pipelines.
- karmakaze 7y agoGood to learn me some F#, looks like Elixir.
- jodrellblank 7y agoIf verbs from the object flowing you want, "That style to me is preferable" you should say. (Style is the object, preferring it is the verb) English reads left to right overall, but writing instructions in English does not flow smoothly either way. e.g. "shuffle a deck of playing cards then deal four" reads more naturally than either "a deck of playing cards shuffle then four cards deal" or "deal four cards after shuffling a deck of playing cards". shuffle(cards).deal(4) is a mix of both approaches.
- rtkwe 7y agoYes yes, english doesn't follow this noun-verb order but that's not what I'm actually talking about. Usually the languages either encourage cards.shuffle().deal(4) or deal(shuffle(cards),4). In the 'noun-verb'/OOP version the sequence of modifications follows the english reading order where basically I only have to keep information about the last result in my head. The functional verb-noun version I have to pop in and out of layers: 'ok we're dealing what?, something shuffled, what are we shuffling?, the cards object/variable, ok we're dealing shuffled cards how many are we dealing? 4.' In general the noun-verb follows the sequence of operations applied by the computer so it's easier to read.
- jodrellblank 7y agoThat's where I was going as well, LISP style and APL style claims "reads left to right" but having to build up a stack of buffered work which you can only unwind once you get to the end, is annoying and unhelpful and limiting. But simply turning it around to cards.shuffle().deal(4) isn't a good answer, it's still the case that you can only put a small number of things together in a chain because it stops making sense. If the next move was to start the game, "cards.shuffle().deal(4).startgame('some-card-game')" does not make sense because starting the game is not something the cards do, but "start(deal(shuffle(cards), 4), 'some-card-game')" can make sense because start is a function which takes a board state and a game to start. It describes the world state, not the cards and their abilities. That is, neither style is right, but a mixed style where both approaches are available and you can mix and match to be more expressive, works much better, IMO. Chunking a small number of things with prefix into one operation, or with postfix into one operation, but combining those chunks flexibly at a larger scale.
- Garph 7y agoI don't enjoy balancing parentheses in Python so I tried to bring Clojure's threading macro into the language... def do(x, fs): for f in fs: x = f(x) return x Which allows left-to-right style programming do(list, filter, partial(map, int), sum) I haven't put this into production code yet, but love pipelining scripts with do
- karmakaze 7y agoI made a thing[0] just like that to do pipelined async db queries: CompletableFuture<Member> asyncProjectMembers = Async.pipeline(Accounts.where("id >= ?", 1000);, (accounts) -> accounts.loadProjects("updated_at >= ?", Instant.now().minusDays(30)), (projects) -> projects.loadMembers()); If you assign the intermediates to variables, each instance will have associated with it the related collection that's queried while avoiding N+1's. [0] https://github.com/karmakaze/safeql https://github.com/karmakaze/safeql
- sooheon 7y agoAlso see PyToolz's implementation: https://toolz.readthedocs.io/en/latest/api.html#toolz.functoolz.thread_first https://toolz.readthedocs.io/en/latest/api.html#toolz.functo... They differentiate thread first/last for Fns with arity > 1, just like Clojure.
- greggman2 7y agoI know most will disagree but I find code the above hard to read. I'd much prefer const people = list.stream(); const peopleOlderThan50 = people.filter(??); const average = peopleOlderThan50.mapToInt().sum() / peopleOlderThan50.length; Or some such thing. The more complex the chain the harder it is for me to read. Does anyone else have that experience?
- joostdevries 7y agoI agree. Naming parts of your code is important. An alternative to named local vals is to either use named functions instead of lambdas: people.filter(olderThan50) or (I use Kotlin) use named extension functions: people.countOlderThan50()
- splittingTimes 7y agoIn any nontrivial application, doing this for many of your types, does this not bloat your API considerably, which adds to the cognitive load?
- sooheon 7y agoThere's a spectrum here. Someone even more explicit than you may insist that `average` should be split up into explicit bindings for the mapping to Int, the sum, and the count. Also, for non-toy examples, there are real benefits to composing transformations, so that you don't pay the memory and performance costs of assigning intermediate sequences.
- ThrowawayR2 7y agoYou're not alone; those long chains are annoying to decipher. The other benefit of what you wrote is that if you have to track down a bug in the chain, you can actually examine the intermediate variables directly instead of having to pick apart a long chain so that a variable is exposed for you to look at in the debugger.
- iameli 7y agoYeah, Lisp-y parenthesis syntax isn't the best for that, but Ruby and CoffeeScript express it reasonably well: `sum mapToInt filter list`