Skip to content

Instantly share code, notes, and snippets.

@owickstrom
Last active November 28, 2016 17:16
Show Gist options
  • Save owickstrom/257d4f9308be5664a5f75e947e1dc7e5 to your computer and use it in GitHub Desktop.
Save owickstrom/257d4f9308be5664a5f75e947e1dc7e5 to your computer and use it in GitHub Desktop.
Composing Alt Functions

What I want is to "compose" functions taking some argument and returning an Alt value, like a -> m b where Alt b. The resulting value should be a function from a -> m b. The implementation below does that, but I was wondering if there's a better way? I'm thinking that maybe I could create a newtype wrapper around these functions, and an Alt instance for that type, but I haven't gotten that to work, and I'm not sure it's a good approach.

composeAlt :: forall m a b. Alt m => (a -> m b) -> (a -> m b) -> a -> m b
composeAlt f g x = f x <|> g x

infix 4 composeAlt as <||>

To expand a bit on why I need this, I have functions that represent HTTP server routes, and they return MaybeT (Aff e) values, representing if the route matched the request or not. I need to compose these functions in such a way that the chain of functions will be tried in sequence until one returns a Just value. If all fail to match then I can fallback to a 404 response.

myRoutes = home <||> projects <||> contact <||> whatever
@kritzcreek
Copy link

I think you can just use the Alt instance arising through your use of MaybeT. I knew to look for that instance because I had just read this blogpost describing exactly what you are looking for: http://www.parsonsmatt.org/2016/11/18/clean_alternatives_with_maybet.html

https://github.com/purescript/purescript-transformers/blob/ab7ccac64cf709af722961f246a6e426dc5e326d/src/Control/Monad/Maybe/Trans.purs#L64-L69

@owickstrom
Copy link
Author

owickstrom commented Nov 28, 2016

Thanks for checking this out @kritzcreek!

Yes, I am using that right now, but I needed a newtype around the function to provide a custom instance for Alt, which makes the functions take the same argument, and then basically apply alt to the results of applying the functions. I couldn't get it to work with only a function, can't remember if it was due to it being an orphan instance (both Alt and Function are external), or if there was some issue with my multi-parameter types.

A simplified version (not sure this type checks):

newtype MaybeFn m x y = MaybeFn (x -> MaybeT m y)

instance altMaybeFn :: Alt (MaybeFn m x) where
  alt (MaybeFn f) (MaybeFn g) x = f x <|> g x

-- using it:
foo = MaybeFn bar <|> MaybeFn baz

Anyway, in the real project, this is what I have right now:

The instance: https://github.com/owickstrom/hyper/blob/master/src/Hyper/Router.purs#L98-L104
Usage: https://github.com/owickstrom/hyper/blob/master/test/Hyper/DSLSpec.purs#L24

@kritzcreek
Copy link

You could build it with (&&&) from Data.Profunctor.Strong

composeAlt :: forall a b f. (Alt f) => (a -> f b) -> (a -> f b) -> a -> f b
composeAlt f g = uncurry (<|>) <<< (f &&& g)

Not really sure if that is better :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment