Hacker News new | ask | show | jobs
by tel 4090 days ago
It... doesn't.

We appear to be talking past one another. How do you interpret

    (>>) :: Monad m => m a -> m b -> m b
as combining two maps of any kind without either (a) specializing `m` to a function type or (b) considering it syntax sugar for

    a >> b ===> (const a >=> const b) ()
1 comments

> It... doesn't.

It does! :)

> How do you interpret (>>) :: Monad m => m a -> m b -> m b as combining two maps of any kind

The answer is in your question. Is -> not an arrow? Isn't there one of those arrows pointing into and out of every m x in the signature above? (for the first and last m x, there could be arrows pointing in and out, respectively). That means they are maps.

A morphism is any value that has arrows pointing to it (from other values) and out of it (to other values). Read my answer above as well.

I am not debating your arguments. I know you know what you are talking about and you are right in what you're saying otherwise. What I am saying is in Haskell everything except Bottom is a categorical morphism, and you seem to be ignoring this concept, which is helpful for those who are trying to make sense of all this.

I'm not sure I've ever heard of this concept, tbh. Categorically, you have arrows. Haskell is often modeled by the category Hask of types and arrows between types. If we denote these categorical arrows in notation as fat arrows then things like

    Int, 
    (), 
    Maybe (Maybe (Maybe String))
    forall a . a -> a
are objects and things like

    Int => (),
    () => Int
    Maybe (Maybe (Maybe String)) => Maybe (Maybe (Maybe String))
are arrows. Arrows are often called morphisms so you might talk about the morphisms as the elements of a Hom set like Hom((), Int).

Notably, the Haskell arrows, the type of functions, happen to internalize the categorical arrows. This makes Hask cartesian closed. Thus, we have a correspondence between the arrows

    a => b
and the objects

    a -> b
This is why we often talk about function types as being categorical arrows.

---

All that said, the type `m a` is not a function type let alone an arrow unless we happen to specialize `m` to be one. More specifically, the type `forall m . m a` is never a function type no matter the choice of `a`.

The notation `(-> m a)` or `(m a ->)` is... not actually a type. It's a type constructor at best (though it's illegal Haskell notation) which fits into a different kind of categorical construction if you want to go there.

The type `(a -> b)` "is" a valid arrow via the correspondence I mentioned earlier. The thing that's being referred to is the fully applied type constructor. If we have

   type Arr a b = a -> b
then `Arr` is a type constructor of kind `(* -> * -> )` (e.g. a "type constructor constructor") and `Arr a` a type constructor of kind `( -> )`.

The type* `Arr a b` is in correspondence with the morphisms of Hask. A combination of arrows would be a morphism from pairs of internal arrows to... something else

    (a -> b, c -> d) => r
we can represent that with an internal arrow

    (a -> b, c -> d) -> r
and then use the currying natural isomorphism

    (a -> b) -> (c -> d) -> r
which is the (internal) type of "arrow combining morphisms".

That's the clearest I can explain what I'm on about. Where do you fit your notion of categorical morphism into there?

I'm not even talking about it at that level, because I don't fully understand it. :)

The reason I said that is because I visualize it like this:

  enum maybeValue { NOTHING, JUST };
  typedef enum maybeValue MaybeValue;
  
  struct maybeInt { MaybeValue maybe_value; int integer };
  typedef struct maybeInt MaybeInt;
  
  MaybeInt just (int x) { ...; m.maybe_value = JUST; m.integer = x; return m; }
  MaybeInt nothing (void) { ...; m.maybe_value = NOTHING; return m; }
  
  MaybeInt chevron (maybeInt a, maybeInt b)
  {
    if (a.maybe_value == JUST)
      if (b.maybe_value == JUST)
        just(a.integer + b.integer);
      else
        nothing();
    else
      nothing();
  }
  
  /* Just 5 >> Nothing >> Just 6 */
  chevron(chevron(just(5), nothing()), just(6));
Which is why I said that (>>) ("chevron" above) combines two functions, just() and nothing().
I was a bit sleepy when I wrote the comment above. Please consider the following function as an example of what I'm talking about, instead of the confused "chevron" function above. This is make-believe C:

  MaybeInt bind (MaybeInt a, (function : int -> MaybeInt a) f)
  {
    if (a.maybe_value == JUST)
      f(a.integer);
    else
      nothing();
  }

  MaybeInt chevron (MaybeInt a, MaybeInt b)
  {
    bind(a, lambda(x){ b });
  }
/* Just 5 >>= (\x -> Just $ x + 1) */ bind( just(5), lambda(x){ just(x+1); } );