
I wrote:
OK, so then how about f .! g = ((.) $! f) $! g
Ulf Norell wrote:
That should probably do the trick.
OK, thanks! Time to re-write the "Note" paragraph yet again. Here goes a first shot at it: : <code>id . f = f . id = f</code> '''''Note:''''' It turns out that the usual identity function <code>id</code> and composition function <code>(.)</code> in Haskell are actually not exactly what we need for the '''Hask''' category. The function <code>id</code> in Haskell is ''polymorphic'' - it can take many different types for its domain and range, or, in category-speak, can have many different source and target objects. But morphisms in category theory are by definition ''monomorphic'' - each morphism has one specific source object and one specific target object. A polymorphic Haskell function can be made monomorphic by specifying its type (''instantiating'' with a monomorphic type), so it would be more precise if we said that the identity morphism from '''Hask''' on a type <code>A</code> is <code>(id :: A -> A)</code>. With this in mind, the above law would be rewritten as: : <code>(id :: B -> B) . f = f . (id :: A -> A)</code> The Haskell composition function <code>(.)</code> is lazy, so the required identity <code>id . f = f</code> is not true when we take <code>f = undefined</code>: we have <code>(id . undefined) `seq` "foo" = "foo"</code>, but <code>undefined `seq` "foo"</code> is equivalent to <code>undefined</code>. To fix this problem, we can define a strict composition function: : <code>f .! g = ((.) $! f) $! g Now the above law looks like this: : <code>(id :: B -> B) .! f = f .! (id :: A -> A)</code> In this form, the law is correct, and ''Hask'' is a category. However, for simplicity, we will ignore these distinctions when the meaning is clear. -Yitz