1. 9

  2. 6

    This is absolutely composition in the Maybe monad, or really the Maybe Kleisli category. The author probably knows this, but I’ll make it clear here.

    Each of the “accessor functions” has a signature a bit like this: a -> Maybe b since we may fail to find the inner target due to it being undefined. The Kleisli category is a category structure which is induced by arrows into monads:

    newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
    instance Monad m => Category (Kleisli m) where
      id = Kleisli return
      Kleisli f . Kleisli g = Kleisli (\a -> g a >>= f)

    Now, we can build chains of Kleisli Maybe arrows

    type a ~> b = Kleisli Maybe a b
    get_msg :: Delivery ~> Msg
    get_content :: Msg ~> Content
    get_props :: Content ~> Props
    get_headers :: Props ~> Headers
    get_all :: Delivery ~> Headers
    get_all = get_headers . get_props . get_content . get_msg

    Now the Erlang example uses a list of these “accessors” but that will fail for us because basic lists cannot store heterogenous types. It’d be okay if each of the get_* methods were “Kleisli endomorphisms”, e.g. had the same type a ~> a for some a, but they don’t.

    We can make a list structure which is able to store “paths” of arbitrary Kleisli arrows, but it turns out that if all you’re interested in is the final, composed result then there isn’t a whole lot of value in doing this. You end up writing the same thing each way: what’s the difference between foldl (.) id [a, b, c, d] and a . b . c . d, really?

    1. 3

      (Oh, it should also be stated that lenses are really closely associated with this idea. In this case, we’re talking about a generalization of lenses sometimes known as prisms, though.)

      1. 1

        I know about the Maybe monad, that’s why I have penultimate paragraph comment :)

        I haven’t read much about Kleisli

        I think your composition comment at the end is stated in “A tutorial on the universality and expressiveness of fold”, but sadly Erlang won’t allow this kind of syntax

        1. 3

          Yep, I hope I didn’t insinuate you weren’t aware of it at all. Just didn’t want to assume too much.

          The idea of replacing Cons a (Cons b (Cons c Nil) with f a (f b (f c zero) absolutely falls out of the universality of foldr for lists. In this case you can’t quite do that since Cons get_msg (Cons get_content Nil) is not well-typed. We can do something like this though with a “free category” construction

          data FCat f a b where
            Nil :: FCat a a
            Cons :: f a x -> FCat x b -> FCat a b

          and now Cons get_msg (Cons get_content (Cons get_props (Cons get_headers Nil))) is well-typed. We also have a fold principle

          fold :: (forall a x . f a x -> r x b -> r a b) -> r b b -> (FCat f a b -> r a b)
          fold cons nil = \case
            Nil -> nil
            Cons arr cat -> cons arr (fold cons nil cat)

          and this one has similar universal properties in that fold f z transforms

          Cons q (Cons r (Cons s Nil)) ===> f q (f r (f s z))
          1. 3

            I just want to say that I appreciate not assuming and the nice description. I just wanted to say that I had remembered an elixir article posted about railway programming and there was a chain in the comments about why he didn’t talk about it as a maybe monad. It then turned into, “because monad is a scary word,” when in a response to a comment on the article the author said he wasn’t familiar with monads.

            Just want to say that I liked the way you introduced it then gave an explanation to. Make it clear to those who didn’t know exactly what you meant (like me).

            1. 2

              I’m really glad you appreciated it. I’ll definitely try to continue this pattern in the future! :)

      2. 1

        If I were to do this I would not use fold directly but make a wrapper around it called something like get_path.

        1. 1

          You mean like abstracting the whole fold thing into your get_path function, or?

          1. 2

            Yes, so the header3 function would be something like:

            header3(Deliver) ->
                get_path(Deliver, [fun get_msg/1, fun get_content/1, fun get_props/1, fun get_headers/1]

            Unfortunately Erlang makes doing these things of things rather painful because its function syntax is so heavy.

            1. 1

              I agree, I mean, this was mostly an example