Let some type be given for many classes. What is the right way to selectively select certain types of behavior?
One way to express it can be to construct a by operator, then
data Person ... sort personList -- default Ord instance (sort `by` age) personList -- `age` modify `Ord` instance
here sort can be any function (e.g. minimum ) with any arity (e.g. insert ).
If we have a function like
reportPersons :: [Person] -> Report
and it uses Ord (to sort the list), Show (to format entries), ... or other special instances; using the funcBy template, we must write
reportPersonsBy :: (Person -> Person -> Ordering) -> (Person -> String) -> ... -> [Person] -> Report
but we can use by to wrap each behavior with the original reportPersons without refactoring reportPersonsBy (explained example and not fixed related problems at the end).
My toy (and not completely satisfactory) solution (full code at the end):
Class for transferring types to types for overriding instances
class Wrappable m where wrap :: forall a . a -> ma unwrap :: forall a . ma -> a
and by function to wrap functions
-- wrap functions: fa -> ga by :: (Functor f, Functor g, Wrappable m) => (f (ma) -> g (ma)) -> ma -> fa -> ga by f _ = fmap unwrap . f . fmap wrap -- wrap functions: a -> fa -> ga by_ fma = f (wrap a) `by` m
Now we can write (in the bottom full example)
-- fa -> fa mapM_ print $ sort personList mapM_ print $ (sort `by` age) personList -- fa -> ga print $ minimum personList print $ (minimum `by` age) personList -- a -> fa -> fa print $ insert jane personList print $ (insert `by_` age) jane personList
Ok, by , by_ , ... works, but what is the correct way ? how to write full polymorphic by ?
I tried but did not work
class Wrappable m => By mxfio where by :: fmx -> mx -> imx -> omx
to be able to write function instances as
instance (Wrappable m, Functor f, Functor g) => By ma (f (ma) -> g (ma)) (fa) (ga) where by :: (f (ma) -> g (ma)) -> ma -> fa -> ga by f _ = fmap unwrap . f . fmap wrap
Thanks!
Report Example
Suppose there is one report function for faces ( face wrap )
reportPersons :: (Wrappable m, Show (m Person), Ord (m Person)) => [m Person] -> Maybe String reportPersons = Just . unlines . map show . sort
with portable behavior for each instance ( Ord and Show ).
Let (not polymorphic by :()
by' :: (Functor f, Functor g, Wrappable m) => (f (ma) -> gb) -> ma -> fa -> gb by' f _ = f . fmap wrap
and a new instance of Wrappable for Person s
newtype Format1 a = Format1 a deriving (Eq, Ord) instance Show (Format1 Person) where show (Format1 (Person na)) = "Name := " ++ n ++ " (" ++ show a ++ " years old)" format1 :: Format1 Person; format1 = undefined instance Wrappable Format1 where wrap = Format1 unwrap (Format1 p) = p
we can now tell people who overlap selective behavior
putStrLn $ fromJust $ (reportPersons `by'` age) personList putStrLn $ fromJust $ (reportPersons `by'` format1) personList
with exit
ByAge (Person {personName = "John", personAge = 16}) ByAge (Person {personName = "Anne", personAge = 24}) ByAge (Person {personName = "Zorn", personAge = 37}) ByAge (Person {personName = "Peter", personAge = 42}) Name := Anne (24 years old) Name := John (16 years old) Name := Peter (42 years old) Name := Zorn (37 years old)
using TypeFamilies or perhaps another function, we can chain Wrappables , etc. (this is a toy !!! and I donβt know how to do it in a good way)
(full sandbox code)
{-