The right way to wrap sample instances of a class (or to "raise" functions like `sortBy`,` minimumBy`, ... automatically)

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)

 {-# LANGUAGE RankNTypes, FlexibleInstances #-} import Data.Maybe import Prelude hiding (minimum) import Data.List hiding (minimum) import System.Random {- safe minimum -} minimum [] = Nothing; minimum xs = listToMaybe $ sort xs data Person = Person { personName :: String, personAge :: Int } deriving (Eq, Show, Ord) personList = [Person "Anne" 24, Person "John" 16, Person "Peter" 42, Person "Zorn" 37] jane = Person "Jane" 26 class Wrappable m where wrap :: forall a . a -> ma unwrap :: forall a . ma -> a -- 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 newtype ByAge a = ByAge a deriving (Eq, Show) instance Ord (ByAge Person) where (ByAge (Person _ a)) `compare` (ByAge (Person _ b)) = a `compare` b age :: ByAge Person; age = undefined instance Wrappable ByAge where wrap = ByAge unwrap (ByAge p) = p main = do -- 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 
+5
source share
2 answers

I found one valid way.

Let our data type

 data Person = Person { name :: String , age :: Int } deriving (Show, Eq, Ord) 

to allow overloading of selectively (and interdependently) instances, we only need

 class CPerson p where get :: p -> Person -- grant access to real data instance CPerson Person where get = id 

Now let any function without explicitly indicate behavior (for example, sortBy vs. sort )

 reportPersons :: (Ord p, Show p, CPerson p) => [p] -> IO () reportPersons = mapM_ print . sort 

with this we can only report the order by name and show the default Show data instance

 Person {name = "Alice", age = 24} Person {name = "Anne", age = 16} Person {name = "Peter", age = 16} Person {name = "Pluto", age = 24} 

And posteriori we need to sort this report using any other field (for example, age ), without changing (we will not, we cannot, ...) reportPersons function we can write

 -- Wrap our inner `CPerson` newtype OrdByAge p = OrdByAge p deriving Eq -- Eq is invariant over wrapping -- Grant access to other wrappers to final data instance CPerson p => CPerson (OrdByAge p) where get (OrdByAge q) = get q -- `Show` maybe or not invariant over wrapping, then bypass instance (Show p, CPerson p) => Show (OrdByAge p) where show = show . get -- `Ord` instance modification to sort by age instance (Eq p, CPerson p) => Ord (OrdByAge p) where (OrdByAge a) `compare` (OrdByAge b) = (age $ get a) `compare` (age $ get b) 

we can now use unmodified reportPersons ordering on age

 reportPersons $ personList `as` OrdByAge 

with the result

 Person {name = "Peter", age = 16} Person {name = "Anne", age = 16} Person {name = "Alice", age = 24} Person {name = "Pluto", age = 24} 

now we won sort age , then name , again without changing reportPersons

 newtype ThenByName p = ThenByName p deriving (Show, Eq) instance CPerson p => CPerson (ThenByName p) where get (ThenByName q) = get q -- Using the chained instance to compare first by inner then by our instance instance (Eq q, Ord q, CPerson q) => Ord (ThenByName q) where (ThenByName a) `compare` (ThenByName b) = case a `compare` b of EQ -> (name $ get a) `compare` (name $ get b) x -> x 

we can write many different versions of instances, for example. two different instances of the format

 -- Short format: newtype ShortFormat p = ShortFormat p deriving (Eq, Ord) instance CPerson p => CPerson (ShortFormat p) where get (ShortFormat q) = get q instance CPerson p => Show (ShortFormat p) where show p = (name $ get p) ++ " (" ++ (show $ age $ get p) ++ " years old)" -- Long format: newtype LongFormat p = LongFormat p deriving (Eq, Ord) instance CPerson p => CPerson (LongFormat p) where get (LongFormat q) = get q instance CPerson p => Show (LongFormat p) where show p = "Person:\n\tName: " ++ (name $ get p) ++ "\n\tAge: " ++ (show $ age $ get p) 

and mix instances, as we prefer, for example. we can get a new modifiedReport as

 let modifiedReport = reportPersons ~> OrdByAge .> ThenByName .> LongFormat modifiedReport personList 

with the result

 Person: Name: Anne Age: 16 Person: Name: Peter Age: 16 Person: Name: Alice Age: 24 Person: Name: Pluto Age: 24 

(Full sandbox code)

 import Data.List data Person = Person { name :: String , age :: Int } deriving (Show, Eq, Ord) class CPerson p where get :: p -> Person instance CPerson Person where get = id reportPersons :: (Ord p, Show p, CPerson p) => [p] -> IO () reportPersons = mapM_ print . sort personList = [ Person "Peter" 16 , Person "Alice" 24 , Person "Pluto" 24 , Person "Anne" 16 ] newtype OrdByAge p = OrdByAge p deriving Eq instance CPerson p => CPerson (OrdByAge p) where get (OrdByAge q) = get q instance (Eq p, CPerson p) => Ord (OrdByAge p) where (OrdByAge a) `compare` (OrdByAge b) = (age $ get a) `compare` (age $ get b) instance (Show p, CPerson p) => Show (OrdByAge p) where show = show . get newtype ShortFormat p = ShortFormat p deriving (Eq, Ord) instance CPerson p => CPerson (ShortFormat p) where get (ShortFormat q) = get q instance CPerson p => Show (ShortFormat p) where show p = (name $ get p) ++ " (" ++ (show $ age $ get p) ++ " years old)" newtype ThenByName p = ThenByName p deriving (Show, Eq) instance CPerson p => CPerson (ThenByName p) where get (ThenByName q) = get q instance (Eq q, Ord q, CPerson q) => Ord (ThenByName q) where (ThenByName a) `compare` (ThenByName b) = case a `compare` b of EQ -> (name $ get a) `compare` (name $ get b) x -> x newtype LongFormat p = LongFormat p deriving (Eq, Ord) instance CPerson p => CPerson (LongFormat p) where get (LongFormat q) = get q instance CPerson p => Show (LongFormat p) where show p = "Person:\n\tName: " ++ (name $ get p) ++ "\n\tAge: " ++ (show $ age $ get p) xs `as` q = fmap q xs f ~> q = f . fmap q ; infixr 7 ~> p .> q = q . p ; infixr 8 .> main = do reportPersons personList reportPersons $ personList `as` OrdByAge reportPersons $ personList `as` OrdByAge `as` ThenByName `as` ShortFormat let modifiedReport = reportPersons ~> OrdByAge .> ThenByName .> LongFormat modifiedReport personList 
0
source

Let some type be given for many classes. What is the correct way to replace, selectively, the behavior of certain instances?

The correct way is to use the plain old functions and use sortBy , maximumBy , groupBy , etc. groupBy .

I think this is class abuse. Keeping it just stupid! Yes, it is opinion based, let the stackoverflow based voting system sort (By).

+2
source

Source: https://habr.com/ru/post/1209531/


All Articles