[Haskell-cafe] Haskell's "historical futurism" needs better writing, not better tools

Viktor Dukhovni ietf-dane at dukhovni.org
Fri Sep 17 05:57:58 UTC 2021

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...


More information about the Haskell-Cafe mailing list