6 ms·
Impressive amount of code and words. I think I'd think twice before approving this in a code review, though. Why is this not the obvious solution to the stated
by donut 7y ago
Impressive amount of code and words. I think I'd think twice before approving this in a code review, though.
Why is this not the obvious solution to the stated problem?
function avgPopularity(slang) {
let sum = 0;
let count = 0;
for (item of slang) {
if (item.popularity > 0) {
sum += item.popularity;
count += 1;
}
}
return sum / count;
}
- dheerajvs 7y agoNitpick: There should be a check for zero count to avoid a divide-by-zero. It's missing in the original article as well.
- ec109685 7y agoWhy isn’t NaN the right result for the average of an empty set?
- sooheon 7y agoOf course trivial toy examples demand simple answers. But as you add more processing (partitioning + further operations on partitions, take every nth, stop when some predicate is true...), transducers let you build each in isolation and compose them easily. Your obvious solution might start to get a bit strained, and the code for any single process in the chain can't be reused.
- agumonkey 7y agoThe article obfuscates the point. Reducers/transducers are from the FP world. In FP avoiding mutable state is one the main point. dot = field => obj => obj[field] // unsafe Array.average = self -> reduce(add, self) / self.length // unsafe slang.filter(s -> s.popularity > 0) .map(dot('popularity')) .average() Bonus points: - no state - reusable pure functions - a tad shorter - maybe as readable as the `for over state` idiom ? (depending on habits)
- chris_st 7y agoDownside: Still going over the array multiple times.
- agumonkey 7y agofair point, in js it will, in other languages that idiom gets deforested into a single pass though
- sli 7y agoYou could (usually, but not always) compose the functions you're passing into filter and map and use reduce instead to do both operations in a single pass. (Edit: This is a big point in the article -- apologies, I hadn't finished reading it yet.)
- southerndrift 7y agoYours is the obvious solution but the problem is a simple example to help people understand the more complex idea of transducers. It's for situations where the code inside the for loop is so complex that it would be nice to organize it with functional programming principals. The problem with functional programming is that the obvious approach is not efficient since it passes the array several times. So the goal is to make the functional approach as efficient as the imperative for loop. Now if you do that by hand, you end up with some messy code. So the article shows how you can combine the operations with a helper library in a structured way. That said, it kind of reminds me of The Evolution of a Haskell Programmer [0] [0] https://www.cs.utexas.edu/~cannata/cs345/Class%20Notes/10%20Haskell%20Programmer%20Evolution.html https://www.cs.utexas.edu/~cannata/cs345/Class%20Notes/10%20...
- cousin_it 7y agoFor me it's the other way around. A for loop is a general purpose solution that changes continuously with the problem: whenever I need to gather some extra data, just add some hairs to the for loop. The time complexity is obvious, so is the space complexity (how much data is in memory at any given time), and it's easy to pause and debug. But if you have a bunch of FP combinators and recursion schemas, when the problem changes slightly, you have to unfold the whole origami crane and re-fold it carefully again. It's what James Hague called a puzzle language. The puzzle nature of FP can sometimes prevent the simplest solution to a problem. For example, try writing a function that accepts a list of N numbers between 1 and N and returns its histogram (list of counts of each number). There's an imperative solution in O(N) time with one for loop. But in FP, I'm not sure O(N) can be achieved, and even O(N log N) requires tree-like data structures that aren't needed in imperative.
- aurelwu 7y agoThat seems to be such a basic task. Please take it not as me not believing it but could you provide some source about there not being a known O(N) solution for that problem, I'd like to know what the hurdles are and why there might be no solution at all or why finding one is so difficult. (I tried googling it but failed to find something useful)
- ec109685 7y agoYour solution requires that slang be fully populated upfront, while the article shows a solution that can operate on a stream of data, an item at a time.
- layoutIfNeeded 7y agoFalse. It can just as well be a lazy iterator that returns data one by one from a stream.
- Gormisdomai 7y agoImo replies here all miss the point. The reason to use transducers instead of a for loop here is that it allows you to expose a library function which takes a transducer as input. It's hard to refactor the for loop above to allow someone consuming this function to specify additional things they want to aggregate due to an API change in the definition of the 'slang' type, but with transducers you just take one as an argument.
- psadri 7y agoAmen.
- edjrage 7y agobecause something something mutable state
- bjoli 7y agoTransducers in theclojure sense often carry hidden mutable state. This is a solved problem for languages with either heavily optimizing compilers (the atria transducers by ableton for c++ are pure and with visible state) or by type system magic (Haskell, but there you should just use conduits).
- dan-robertson 7y agoBecause a bunch of those lines are concerned with the boring mechanics of how to compute this thing rather than what is being computed. It’s reasonable to say having a big system of composable transducers is pointless for one computation. It’s harder for 1000. This point isn’t about transducers specifically but more about avoiding specifying the things you don’t care about. Eg, in Common Lisp (which was old enough to somewhat care about how to iterate things): (defun avg-popularity (list) (loop for item in list when (plusp (popularity item)) sum pop into s count t into c finally (return (/ s c)))) This avoids having to care about the mechanics of how to sum or count things and I think it’s too sequential, when you don’t really care about that. One can certainly imagine a simpler to express solution in e.g. apl.
- TeMPOraL 7y agoNote that to make it correct Common Lisp, you'd write it like that: (defun avg-popularity (list) (loop for item in list for pop = (popularity item) when (plusp pop) sum pop into s and count t into c finally (return (/ s c)))) Two changes: 1) making `pop' be a thing, and 2) `when' clause of loop only applies to the next clause by default (here, `sum pop into s'), you need to include `and' to chain it with the next one so that both are guarded by `when' condition.
- serpix 7y agobecause there is dirty mutation and it relies entirely on side effects to work. (this is sarcastic, that is exactly how one should go about this one example case.) It however is a completely different thing when longer chains of filter / map need to come along and the true power of transducers is needed. In clojure I've had to use transducers at least three times in the course of the last four years!