0

I'm not sure how to write the semigroup instance for this map newtype:

import Data.Map (Map)
import qualified Data.Map as Map

newtype mymap = MyMap (Map Int String)

instance Semigroup (MyMap k v) where
    MyMap a <> MyMap b = Map.compose a b 

error:

    • Expected kind ‘k -> k1 -> *’, but ‘MyMap’ has kind ‘*’
    • In the first argument of ‘Semigroup’, namely ‘(MyMap k v)’
      In the instance declaration for ‘Semigroup (MyMap k v)’
  |
6 | instance Semigroup (MyMap k v) where
  |                     ^^^^^^^^^

I thought that MyMap (Map Int String) would have kind * -> * -> * as it takes two types (Int and String) and returns a mymap type

Thanks!

k-shar
  • 79
  • 1
  • 8
  • Something of kind `* -> * -> *` is something that can be applied to two types. So if `MyMap (Map Int String)` had that kind we could get a type by writing `MyMap (Map Int String) SomeType1 SomeType2`. This is not the case. Actually `MyMap` is already a type, so it has kind `*`, and `MyMap (Map Int String)` is a kind error on its own (we can't apply anything to a type). When in doubt, comment out the last part of the code (so that it compiles) and use `:k MyMap` in GHCi to see its kind. – chi Mar 10 '23 at 12:52

1 Answers1

4

You should write your datatype capitalized

newtype MyMap = MkMyMap (Map Int String)

Type MyMap has no arguments, notice that I renamed the constructor MkMyMap to disambiguate between them:

instance Semigroup MyMap where
  MkMyMap a <> MkMyMap b = MkMyMap (Map.compose a b)

Map.compose will not work as a semigroup operation unless the map has type Map A A:

compose :: Ord b => Map b c -> Map a b -> Map a c

Let's see what happens when the first argument is Map Int String:

compose :: Map Int String -> Map a Int -> Map a String

If we are returning Map Int String as well, it forces the second argument to be Map Int Int

compose :: Map Int String -> Map Int Int -> Map Int String

There is a template for how maps can be defined, using a Monoid to accumulate conflicts: MonoidalMap key a.

newtype MyMap = MkMyMap (Map Int String)
  deriving
  newtype IsList

  deriving
  stock Show

  -- >> :set -XOverloadedLists
  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"twotveir")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int String -- (<>) = unionWith (++)

This unions the two Maps using the Semigroup String for the value of every duplicate key. If we used a different Semigroup instance Semigroup.First String then the first key is chosen:

  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"two")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Semigroup.First String) -- (<>) = union
                                               --      = unionWith const
                                               --      = unionWith \a _ -> a

and the last key is chosen if we use Semigroup.Last String.

  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"tveir")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Semigroup.Last String) -- (<>) = unionWith \_ b -> b

There are also other combinations, some of which are rather strange. For example using Applicative lifting (Ap):

  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"tttttwwwwwooooo")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Ap [] (Semigroup.First Char)) -- (<>) = unionWith (liftA2 \a _ -> a)
                                                     --      = unionWith (<*)

  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"tveirtveirtveir")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Ap [] (Semigroup.Last Char)) -- (<>) = unionWith (liftA2 \_ b -> b)
                                                    --      = unionWith (*>)

  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"two")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Ap ZipList (Semigroup.First Char)) -- (<>) = unionWith (zipWith \a _ -> a)

  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"tve")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Ap ZipList (Semigroup.Last Char)) -- (<>) = unionWith (zipList \_ b -> b)

  -- >> [(2, "two"), (0, "zero")] <> [(1, "einn"), (2, "tveir")] :: MyMap
  -- MkMyMap (fromList [(0,"zero"),(1,"einn"),(2,"two")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Max String) -- (<>) = unionWith max

  -- >> [(4, "four")] <> [(4, "fjórir")] :: MyMap
  -- MkMyMap (fromList [(4,"foór")])
  deriving (Semigroup, Monoid)
  via MonoidalMap Int (Ap ZipList (Max Char)) -- (<>) = unionWith (zipWith max)
Iceland_jack
  • 6,848
  • 7
  • 37
  • 46