
On Fri, Sep 17, 2021 at 02:15:28PM +0900, Michael Turner wrote:
"The contribution of each element to the final result is combined with an accumulator via an /operator/ function. The operator may be explicitly provided by the caller as in `foldr` or may be implicit as in `length`. In the case of `foldMap`, the caller provides a function mapping each element into a suitable 'Monoid', which makes it possible to merge the per-element contributions via that monoid's `mappend` function."
This is a little better,
I'll open an MR for that then, it is likely to be seen as an overall improvement by others too I think. This is perhaps another opportunity for any other wordsmithing suggestions that anyone wants to make while the patient is on the operating table...
but I'd write it this way, I think.
"Folds take operators as arguments. In some cases, it's implicit, as in the function "length".
OK, but perhaps s/it's/the operator is/
These operators are applied to elements when lazy evaluation requires it,
Laziness has little to do with this. Whether the result of the fold contains lazy thunks or is strict, the conceptual value is still a result of applying the operator to the next element and the accumulated intermediate result built from the initial value and any previous elements whose contributions have already been folded into the accumulator (whether lazily or strictly).
with a fold 'accumulator' as one of the operands. 'foldMap' uses a function (implicit? explicit?) that maps elements into . . . ."
The `foldMap` method takes an explicit `a -> m` function that maps each element into a Monoid (a structure with an associative binary operation and an element for this binary operation). Combining elements in monoid is often called "concatenation", and thus `foldMap f` just applies (f :: a -> m) for each element `a` and then concatenates all the `m` values using the Monoid's binary operator (mappend or infix <>). I was indeed assuming that the reader has already seen Monoids, or, if not, that the reader would just move on and read the parts that don't require knowledge of Monoids. Since such a reader wouldn't be using foldMap until Monoids "click".
And this is where I bog down, because I don't know what makes a monoid 'suitable' or even really what a monoid is -- it's been a long time (25 years or so) since my last abstract algebra course.
Well it is "suitable" when the fold you want to perform is in fact concatenation of monoid elements computed from the elements of the foldable structure.
I don't doubt that you're doing something good here. It's just that I'm frustrated about what's in the way of benefitting from it. I gather that there's something about the algebraic properties of folds that makes some code easier to write and understand, once you have the underlying concepts fully understood.
Yes, that's the idea. Folds are either strict reductions (iterative strict computation of an accumulator value), or essentially coroutines (corecursion) that lazily yield a stream of values (a lazy list or similar). Folds can be left-associative or right associative, and the typically the left-associative ones are best used strictly, and the right-associative ones are best used corecursively, but as with many rules there are exceptions, and the pages of prose try to lay the groundwork for reasoning about how to use the library properly.
My biggest problem is probably out of scope for what you're working on: I need motivating examples. I've used folds in other programming languages.
Laziness makes it it possible to use folds as coroutines that lazily yield a sequence of values. This is not possible in strict languages, where you'd need explicit support for coroutines (generators) via something like a "yield" primitive. Haskell's lazy lists, used exactly once are coroutines when the elements are computed as needed. When the fold operator is building a recursively defined structure with a lazy tail, you use `foldr` corecursively to yield the head and then when demanded the tail. When the operator is strict ((+), (*), ...) you'd use `foldl'`.
My main concern is whether I can use Haskell for a natural-language processing project.
Can't help you there, I do DNS, TLS, cryptography, ... don't know anything about NLP.
In that piece (no, don't read it if you're not interested in linguistics), I remark that the algebraic facet of Haskell practice may not be very useful for natural language parsing and understanding. The truth is: I'd prefer to be proven wrong about that. This area is so hard, we need all the good tools we can get. IF abstract algebra could be useful for this application, I want it.
Haskell is quite well suited to writing parsers, DSLs, compilers, largely painless multi-threaded concurrency (which is my primary use-case), nice database APIs (e.g. Hasql), nice Web APIs (e.g. Servant and Wai)... Perhaps also NLP, but I woudn't know about that. My impression was that NLP is substantially neural-net based these days... -- Viktor.