Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to abstract over a "back and forth" transformation?

Tags:

haskell

Consider this example (from https://codereview.stackexchange.com/questions/23456/crtitique-my-haskell-function-capitalize):

import Data.Char

capWord [] = []
capWord (h:t) = toUpper h : map toLower t

capitalize = unwords . map capWord . words

Is there a good way to abstract over the "back and forth" transformation, e.g. unwords . f . words? The best I could come up was

class Lift a b | a -> b where
  up :: a -> b
  down :: b -> a

instance Lift String [String] where
  up = words
  down = unwords

lifted :: (Lift a b) => (b -> b) -> a -> a
lifted f = down . f . up

capitalize = lifted (map capWord)

but it feels not very flexible, and needs MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances and FlexibleInstances - which might be an indicator that it goes slightly over the top.

like image 217
Landei Avatar asked Mar 05 '13 10:03

Landei


2 Answers

Your lifted is actually the same as dimap from Data.Profunctor:

onWords = dimap words unwords
capitalize = onWords (map capWord)

That might not be the direction of generalization you thought about. But look at the type of the equivalent function in Control.Functor from category-extras:

dimap :: Bifunctor f (Dual k) k k => k b a -> k c d -> k (f a c) (f b d)

This version generalizes it over everything which is both a QFunctor and a co-PFunctor. Not that useful in everyday scenarios, but interesting.

like image 139
phipsgabler Avatar answered Nov 01 '22 15:11

phipsgabler


I'd say the best answer is "no, because abstracting over that doesn't buy you anything". In fact your solution is far less flexible: there can be only one instance of Lift String [String] in scope and there are more ways to split string into a list of strings than just words/unwords (which means you'll start throwing newtypes or even more arcane extensions into the mix). Keep it simple — the original capitalize is just fine the way it is.

Or, if you really insist:

lifted :: (a -> b, b -> a) -> (b -> b) -> a -> a
lifted (up, down) f = down . f . up

onWords = lifted (words, unwords)
onLines = lifted (lines, unlines)

capitalize = onWords $ map capWord

Conceptually the same thing as your typeclass, except without abusing typeclass machinery so much.

like image 23
Cat Plus Plus Avatar answered Nov 01 '22 14:11

Cat Plus Plus