3 ms·
IMHO, the Haskell version using mapAccumL[1] is actually quite a nice demonstration of the power of having lots of computational patterns available immediately
by Chris_Newton 6y ago
IMHO, the Haskell version using mapAccumL[1] is actually quite a nice demonstration of the power of having lots of computational patterns available immediately from the standard library.
The biggest problem in Haskell that a language like JS doesn’t have is the types: you’re converting both the outer type for sections and the inner type for lessons into something else, so in any language with explicit static types you’re going to have the overhead of specifying those, or you have to hack around it a bit as we see here.
However, if you ignore the type boilerplate and the definition of the input data, the substance of the algorithm here would be around the same length as the imperative JS for-of version, particularly with less verbose names for the local variables as idiomatic Haskell tends to use.
With all four types explicitly defined and using another map-accumulate instead of the zipWith so updating the lessons with their positions was done similarly to updating the sections, I think it could be quite readable as well, even with Haskell’s limited support for record types.
[Edit: Having actually tried this now, even with DuplicateRecordFields, my Haskell code still seems unnecessarily verbose because of the record manipulations, but I still quite like the use of mapAccumL for making the pattern of computation explicit.]
[1] https://github.com/josevalim/nested-data-structure-traversal/blob/master/haskell/AccumL.hs https://github.com/josevalim/nested-data-structure-traversal...
- momentoftop 6y agoI've always liked `mapAccumL`, and nowadays, it amuses that it is implemented by mapping with a state monad (and thus it works over arbitrary Traversables). With that precedent, I'm happy to implement the given problem using State and traverse, so it's structured much the same as the obvious imperative version: incr :: (MonadState s m, Num s) => m s incr = get <* modify (+1) annotate :: [Section x] -> [Section Int] annotate ss = flip evalState 1 . forM (zip [1..] ss) $ \(i,Section title reset lessons _) -> do when reset (put 1) lessons <- (traverse . traverse) (const incr) lessons pure $ Section title reset lessons i