Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing common haskell piping boilerplate

Tags:

haskell

I have some pretty common Haskell boilerplate that shows up in a lot of places. It looks something like this (when instantiating classes):

a <= b = (modify a) <= (modify b)

like this (with normal functions):

fn x y z = fn (foo x) (foo y) (foo z)

and sometimes even with tuples, as in:

mod (x, y) = (alt x, alt y)

It seems like there should be an easy way to reduce all of this boilerplate and not have to repeat myself quite so much. (These are simple examples, but it does get annoying). I imagine that there are abstractions created for removing such boilerplate, but I'm not sure what they're called nor where to look. Can any haskellites point me in the right direction?

like image 774
So8res Avatar asked Dec 16 '11 02:12

So8res


2 Answers

For the (<=) case, consider defining compare instead; you can then use Data.Ord.comparing, like so:

instance Ord Foo where
  compare = comparing modify

Note that comparing can simply be defined as comparing f = compare `on` f, using Data.Function.on.

For your fn example, it's not clear. There's no way to simplify this type of definition in general. However, I don't think the boilerplate is too bad in this instance.

For mod:

mod = alt *** alt

using Control.Arrow.(***) — read a b c as b -> c in the type signature; arrows are just a general abstraction (like functors or monads) of which functions are an instance. You might like to define both = join (***) (which is itself shorthand for both f = f *** f); I know at least one other person who uses this alias, and I think it should be in Control.Arrow proper.

So, in general, the answer is: combinators, combinators, combinators! This ties directly in with point-free style. It can be overdone, but when the combinators exist for your situation, such code can not only be cleaner and shorter, it can be easier to read: you only have to learn an abstraction once, and can then apply it everywhere when reading code.

I suggest using Hoogle to find these combinators; when you think you see a general pattern underlying a definition, try abstracting out what you think the common parts are, taking the type of the result, and searching for it on Hoogle. You might find a combinator that does just what you want.

So, for instance, for your mod case, you could abstract out alt, yielding \f (a,b) -> (f a, f b), then search for its type, (a -> b) -> (a, a) -> (b, b) — there's an exact match, but it's in the fgl graph library, which you don't want to depend on. Still, you can see how the ability to search by type can be very valuable indeed!

There's also a command-line version of Hoogle with GHCi integration; see its HaskellWiki page for more information.

(There's also Hayoo, which searches the entirety of Hackage, but is slightly less clever with types; which one you use is up to personal preference.)

like image 92
ehird Avatar answered Oct 20 '22 10:10

ehird


For some of the boilerplate, Data.Function.on can be helpful, although in these examples, it doesn't gain much

instance Ord Foo where
    (<=) = (<=) `on` modify   -- maybe

mod = uncurry ((,) `on` alt)  -- Not really
like image 4
Daniel Fischer Avatar answered Oct 20 '22 11:10

Daniel Fischer