[Haskell-cafe] Linguistic hair-splitting
wren ng thornton
wren at freegeek.org
Sun Feb 14 19:38:47 EST 2010
Alexander Solla wrote:
> On Jan 27, 2010, at 4:57 PM, Conor McBride wrote:
>
>> Yes, the separation is not clear in Haskell. (I consider this
>> unfortunate.) I was thinking of Paul Levy's call-by-push-value
>> calculus, where the distinction is clear, but perhaps not as fluid as
>> one might like.
>
> What, exactly, is the supposed difference between a value and a
> computation? Please remember that computations can and very often do
> "return" computations as results. Please remember that in order for a
> function to be computed for a value, binding and computation must
> occur. And that for every value computed, a computation must occur,
> even if it is "just" under identity the identity function.
The difference is ontological. To pull in some category theory ---only
because it helps make the distinction explicit--- there's a big
difference between a morphism |A->B| and an exponential object |B^A|.
What's the difference? Well, one is a morphism and the other is an
object; they're simply different sorts of things, and it's a syntactic
error to put one in the place of the other.
In Cartesian Closed Categories (and similar) it so happens that the
category "has exponentials", i.e. for every morphism |f:A->B| there
exists an exponential object |o:B^A|. Because of the has-exponentials
law, we know that in a CCC for every |f| there's an |o| and for every
(exponential) |o| there's an |f|; but that does not mean that |f| and
|o| are the *same* thing, it merely means we can conceptually convert
between them.
So what has any of this to do with Haskell? For the non category
theorists in the audience: morphisms capture the notion of functions as
procedures; that is, morphisms *do* things (or: are actions).
Exponential objects capture the notion of functions as values/arguments;
that is, objects *are* things. Down in the compiler we make the same
exact distinction when distinguishing code or code pointers (morphisms)
from closures (objects).
In functional languages there are invisible coercions between
functions-as-procedures and functions-as-values. The problem, as it
were, is that the coercion is invisible. Why is this a problem? Most of
the time it isn't; it's part of what makes functional programming so
clean and elegant. Consider the identity monad. It's a simple monad,
it's exactly the same thing as using direct values except with a newtype
wrapper. Since it's exactly the same, why not try writing a program
using |Id| everywhere instead of using the direct style. If you try this
you'll see just how much invisible coercion is going on. That's part of
the reason why it's sugared away.
But some of the times you want or need to be explicit about the
coercions (or conversely, want to change which coercions are implicit).
Consider for instance the problem of having distinct functions |map| vs
|mapM| or any other |foo| vs |fooM|. The reason for this proliferation
is that, syntactically, we must write different code depending on
whether we're using the invisible coercions of direct values, vs whether
we're making the coercions explicit by using some monad (or applicative,
or whatever).
Haskell and similar languages choose a particular set of coercions to
make invisible and implicit. Currying, uncurrying, application, etc. But
there's nothing sacred about choosing to make those particular coercions
invisible instead of choosing a different set of coercions to sugar away.
--
Live well,
~wren
More information about the Haskell-Cafe
mailing list