Skip to content

Instantly share code, notes, and snippets.

@jml
Last active August 25, 2018 12:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jml/53dfdf608537fd501bad3e4f0e473b60 to your computer and use it in GitHub Desktop.
Save jml/53dfdf608537fd501bad3e4f0e473b60 to your computer and use it in GitHub Desktop.
Things I want in Protolude

Stephen Diehl's protolude library is excellent. I highly recommend it as a default prelude for Haskell.

It's so good and so close to what I want that whenever I do come across something missing it's as obvious as a glitch in good music.

Here's what I'd like:

<<$>>

(<<$>>) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
(<<$>>) = fmap . fmap

I do this enough that I'd like it to hand. Pi Delport suggested the syntax & that it should be in a standard library somewhere.

for / forA

I want for to be flip fmap and forA to be what's currently for (protolude/protolude#19).

ppShow

From the pretty-show library.

A very simple pretty printer, great for debugging data structures. Would have to be adjusted for better string types for protolude.

tracePpShow

Like traceShow, but pretty. Doesn't actually exist yet.

guarded

guarded :: Alternative f => (a -> Bool) -> a -> f a
guarded p x = bool empty (pure x) (p x)

Another great idea from Pi Delport.

@theunixman
Copy link

Another thing I'd love would be UnicodeSyntax and associated modules. I have sort of a weird collection of .hs files I drag around with me from project to project...

And now that I'm working with singletons that's just going to grow...

@PiDelport
Copy link

Some addendums, for completeness!

Working with multiple levels of Applicative

Two levels:

purer :: (Applicative f, Applicative g) => a -> f (g a)
purer = pure . pure

(<<$>>) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
(<<$>>) = (<$>) . (<$>)

(<<*>>) :: (Applicative f, Applicative g)  => f (g (a -> b)) -> f (g a) -> f (g b)
(<<*>>) = liftA2 (<*>)

liftAA2 :: (Applicative f, Applicative g) => (a -> b -> c) -> f (g a) -> f (g b) -> f (g c)
liftAA2 = liftA2 . liftA2

Three levels:

purerer :: (Applicative f, Applicative g, Applicative h) => a -> f (g (h a))
purerer = pure . pure . pure

(<<<$>>>) :: (Functor f, Functor g, Functor h=> (a -> b) -> f (g (h a)) -> f (g (h b))
(<<<$>>>) = (<$>) . (<$>) . (<$>)

(<<<*>>>) :: (Applicative f, Applicative g, Applicative h) => f (g (h (a -> b))) -> f (g (h a)) -> f (g (h b))
(<<<*>>>) = liftA2 (liftA2 (<*>))

liftAAA2 :: (Applicative f, Applicative g, Applicative h) => (a -> b -> c) -> f (g (h a)) -> f (g (h b)) -> f (g (h c))
liftAAA2 = liftA2 . liftA2 . liftA2

And so on, following the same pattern.

(Yes, purer and purerer are a bit silly. Suggestions for better names welcome. :)

guardedA, to go with guarded

Definition:

guarded :: Alternative t => (a -> Bool) -> a -> t a
guarded p x = bool empty (pure x) $ p x

guardedA :: (Functor f, Alternative t) => (a -> f Bool) -> a -> f (t a)
guardedA p x = bool empty (pure x) <$> p x

These are useful whenever you want the presence or absence of some value to be predicated on some function of it.

For example, here's a definition of filter as a guarded identity fold, in both pure and Applicative versions:

filter :: (Foldable t, Alternative t') => (a -> Bool) -> t a -> t' a
filter p = foldr ((<|>) . guarded p) empty

filterA :: (Applicative f, Foldable t, Alternative t') => (a -> f Bool) -> t a -> f (t' a)
filterA p = foldr (liftA2 (<|>) . guardedA p) (pure empty)

-- Example usage:
ghci> filter even [1..9] :: [Int]
[2,4,6,8]
ghci> filterA doesFileExist ["one", "nonexistent","two"] :: IO [FilePath]
["one", "two"]

Applicative function composition: <.> to go with <$>

For the sake of intuition and symmetry, it really makes sense to have a <.> operator that is to <$> what . is to $.

infixr <.>
(<.>) :: Functor f => (b -> c) -> (a -> f b) -> a -> f c
(f <.> g) x = f <$> g x

-- Compare definition of (.):
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f $ g x

This allows you to do the same kinds of code restructuring as you're accustomed to with $ and .:

  • foo x = f $ g xfoo = f . g
  • foo x = f <$> g xfoo = f <.> g

Example:

fileSizes <- traverse (length <.> readFile) paths

-- Compare the noisy <$> version:
fileSizes <- traverse (\f -> length <$> readFile f) paths

-- and the less intuitive (.) + fmap plumbing version:
fileSizes <- traverse (fmap length . readFile) paths

@seagreen
Copy link

I think <$$> is a slightly better bikeshed color for (fmap.fmap) than <<$>>, especially once you start getting into names like <$$$> and <$$$$>.

@theunixman
Copy link

@PiDelport
Copy link

@seagreen: The problem with that is that <**> is already taken to mean flipped application.

@Profpatsch
Copy link

Hm, once you reach three levels of Functors/Applicatives, you should really think about using monad transformers or the Monad* classes.

@PiDelport
Copy link

True: I've only really found myself using the two-level combinators, not the three-level ones.

@jml
Copy link
Author

jml commented Aug 23, 2016

Another silly one:

pass :: Applicative f => f ()
pass = pure ()

@sdiehl
Copy link

sdiehl commented Jan 2, 2017

All of these functions should be added as of 0.1.10. Any issues please report on the issue tracker. Thanks!

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