ClassyPrelude has two map functions, namely:
map
omap
map
works on any Functor
. However, things like Text
are not functors as they aren't generic containers. That is, they can't contain any type, unlike a list. As can be seen in the following code:
module Main where
import Prelude ()
import ClassyPrelude
import qualified Data.Text as T
import Data.Char as C
main = do
let l = [1,2,3] :: [Int]
let t = (T.pack "Hello")
let m = Just 5
print $ map (*2) l
print $ map (*2) m
print $ omap C.toUpper t
return ()
Notice one has to use omap
to deal with the Text
. As map
requires the type to be a Functor
, map f text
fails. The thing is, I found it trivially easy to redefine map
to work for both calls. Here's the code:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}
module Main where
import Prelude hiding (map)
import qualified Data.Text as T
import Data.Char as C
import Control.Monad (Functor)
import qualified Data.Vector.Unboxed as U
class CanMap a b where
type Element a :: *
type Container a b :: *
map :: (Element a -> b) -> a -> Container a b
instance (Functor f) => CanMap (f a) b where
type Element (f a) = a
type Container (f a) b = f b
map = fmap
instance CanMap T.Text Char where
type Element T.Text = Char
type Container T.Text Char = T.Text
map = T.map
instance (U.Unbox a, U.Unbox b) => CanMap (U.Vector a) b where
type Element (U.Vector a) = a
type Container (U.Vector a) b = U.Vector b
map = U.map
main = do
let l = [1,2,3] :: [Int]
let m = Just 5
let t = (T.pack "Hello")
let u = U.generate 3 id
print $ map (*2) l
print $ map (*2) m
print $ map C.toUpper t
print $ map (*2) u
return ()
All that's required is to add instances to CanMap for any monomorphic containers. ClassyPrelude already does this anyway with "omap" in the Data.MonoTraversable module. I suspect however there's a good reason I'm missing about why there should be two separate map functions to deal with these alternate situations, but I'm wondering what that is.
I think the problem is with types such as unboxed Vector
s, where the type looks almost like it should have a Functor
instance but you actually need a restriction on the element types to do the mapping:
instance U.Unbox a => MonoFunctor (U.Vector a)
When you try to include such types in your system, with the way GHC only looks at the heads and not the contexts of instances when looking them up, you will necessarily end up with overlapping instance problems.
It can also be useful, in the case of map
, to know that you really can convert to any "element" type you want. With only one multiparameter typeclass you cannot express that the instance sometimes doesn't depend on the element.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With