by Dmitrii Kovanikov
HASKELL.SG: 6 Sep 2018
Haskell Adept at Holmusk (present)
Haskell Software Developer at Serokell (2016-2018)
Haskell Lecturer at the ITMO University (2015-2018)
Cofounder, Mentor, Developer at Kowainik (free time)
@chshersh
Backpack — mixin package system.
Mixin libraries can have signatures which permit implementations of values and types to be deferred, while allowing a library with missing implementations to still be type-checked.
New fields in .cabal files: signatures, reexported-modules, mixins
New file extension: .hsig
Supported currently only by cabal-install (stack issue)
New compiler errors!
1. Polymorphic functions.
groupBy :: (Foldable f, Ord k) => (a -> k) -> f a -> Map k (NonEmpty a)
ghci> groupBy even [1..10]
fromList [ (False, 9 :| [7,5,3,1])
, (True, 10 :| [8,6,4,2])
]
2. Benchmarks.
3. Property-based tests for laws (and unit tests as well)
* lookup k (insert k v m) ≡ Just v
* insert k b . insert k a ≡ insert k b
* member k (delete k m) ≡ False
1. Data types are different across libraries (like Map and HashMap).
2. Every library has its own constraints for the keys (Map requires Ord constraint and HashMap requires Eq and Hashable constraints).
3. Types might have different kinds (consider Map and IntMap).
4. Some maps don’t have efficient modification operations since they are based on arrays (Map from primitive-containers package).
5. Different libraries implement the same functions with different constraints.
class ( Monoid set
, MonoFoldable set
, Eq (ContainerKey set)
, GrowingAppend set
)
=> SetContainer set where
type ContainerKey set
member :: ContainerKey set -> set -> Bool
notMember :: ContainerKey set -> set -> Bool
union :: set -> set -> set
difference :: set -> set -> set
intersection :: set -> set -> set
keys :: set -> [ContainerKey set]
class StaticMap t where
type Key t :: Type
type Val t :: Type
size :: t -> Int
lookup :: Key t -> t -> Maybe (Val t)
member :: Key t -> t -> Bool
class StaticMap t => DynamicMap t where
insert :: Key t -> Val t -> t -> t
insertWith :: (Val t -> Val t -> Val t) -> Key t -> Val t -> t -> t
delete :: Key t -> t -> t
alter :: (Maybe (Val t) -> Maybe (Val t)) -> Key t -> t -> t
signature Map (Map, Key, empty, alter) where
data Map k v
class Key k
instance (Show k, Show v) => Show (Map k v)
empty :: Map k v
alter :: Key k => (Maybe v -> Maybe v) -> k -> Map k v -> Map k v
cabal-version: 2.0
name: containers-sig
library
signatures: Map
build-depends: base
module Map.Contrib.Group (groupBy) where
import Map (Key, Map)
groupBy :: (Foldable f, Key k) => (a -> k) -> f a -> Map k (NonEmpty a)
groupBy = < ... some implementation ... >
cabal-version: 2.0
name: containers-contrib
library
exposed-modules: Map.Contrib.Group
build-depends: base, containers-sig
{-# LANGUAGE ConstraintKinds #-}
module Map.Ord (Map, Key, empty, alter) where
import qualified Data.Map.Strict as M
type Map = M.Map
type Key = Ord
empty :: Map k v
empty = M.empty
alter :: Key k => (Maybe v -> Maybe v) -> k -> Map k v -> Map k v
alter = M.alter
{-# LANGUAGE ConstraintKinds #-}
module Map.Ord (Map, Key, module Map) where
import Data.Map.Strict as Map
type Key = Ord
cabal-version: 2.0
name: containers-ordered-strict
library
exposed-modules: Map.Ord
reexported-modules: Map.Ord as Map
build-depends: base, containers
module Main where
import Map.Contrib.Group (groupBy)
main :: IO ()
main = do
putStrLn "### Map ###"
print $ groupBy (`mod` 2) ([1..10] :: [Int])
cabal-version: 2.0
name: containers-example
executable map-exe
main-is: Main.hs
build-depends: base
, containers-ordered-strict
, containers-contrib
{-# LANGUAGE ConstraintKinds #-}
module Map.Hash where
import Data.Hashable (Hashable)
import Data.HashMap.Strict as HM
type Map = HM.HashMap
type Key a = (Eq a, Hashable a)
{-# LANGUAGE ConstraintKinds #-}
module Map.Ord where
import Data.Map.Strict as M
type Map = M.Map
type Key = Ord
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE UndecidableInstances #-}
module Map.Hash where
import Data.Hashable (Hashable)
import Data.HashMap.Strict as HM
type Map = HM.HashMap
class (Eq k, Hashable k) => Key k
instance (Eq k, Hashable k) => Key k
• Type constructor ‘Key’ has conflicting definitions in the module
and its hsig file
Main module: type Key a =
(ghc-prim-0.5.2.0:GHC.Classes.Eq a,
hashable-1.2.7.0:Data.Hashable.Class.Hashable a)
:: Constraint
Hsig file: class Key k
Illegal parameterized type synonym in implementation of abstract data.
(Try eta reducing your type synonym so that it is nullary.)
• while checking that containers-unordered-strict-0.0.0:Map.Hash
implements signature Map in
containers-contrib-0.0.0[Map=containers-unordered-strict-0.0.0:Map.Hash]
cabal-version: 2.0
name: containers-example
executable map-exe
main-is: Main.hs
build-depends: base
, containers-ordered-strict
, containers-unordered-strict
, containers-contrib
mixins: containers-contrib (Map.Contrib.Group as Map.Contrib.Group.Ord)
requires (Map as Map.Ord)
, containers-contrib (Map.Contrib.Group as Map.Contrib.Group.Hash)
requires (Map as Map.Hash)
import qualified Map.Contrib.Group.Hash as HM (groupBy)
import qualified Map.Contrib.Group.Ord as M (groupBy)
main :: IO ()
main = do
putStrLn "### Map ###"
print $ M.groupBy (`mod` 2) ([1..10] :: [Int])
putStrLn "### HashMap ###"
print $ HM.groupBy (`mod` 2) ([1..10] :: [Int])
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}
module Map.Int
( Map
, Key
, empty
, alter
) where
import qualified Data.IntMap.Strict as M
newtype Map k v = IM { unIM :: M.IntMap v }
deriving newtype (Show)
type Key = (~) Int
empty :: Map k v
empty = IM M.empty
alter :: Key k => (Maybe v -> Maybe v) -> k -> Map k v -> Map k v
alter f k = IM . M.alter f k . unIM
newtype Map k v = IM { unIM :: M.IntMap v }
deriving newtype (Show)
type Key = (~) Int
-- Doesn't compile without 'Key' constraint!
toList :: Map k v -> [(k, v)]
signature Map where
...
toList :: Key k => Map k v -> [(k, v)]
signature ROMap (Map, Key, lookup, ...)
Maps and Sets from primitive-containers package don't have modification operations. This means we need to split our signatures across two packages.
signature Map (Map, Key, lookup, ...)
data Map k v
class Key k
lookup :: Key k
=> k
-> Map k v
-> Maybe v
signature Map (insert, delete, ...)
import ROMap (Map, Key)
containers-sig-readonly:Map.hsig
containers-sig:Map.hsig
signature Map (Map, Key, delete, ...)
data Map k v
class Key k
delete :: Key k
=> k
-> Map k v
-> Map k v
* haskell-backpack/backpack-str: String signatures
* unpacked-containers: ordered containers with unpacked keys
* ezyang/backpack-regex-example: Learning examples
* ezyang/reflex-backpack: Reflex specialized to Spider
* kowainik/containers-backpack: Backpack interface for containers
* hasktorch/hasktorch: Haskell Backpack bindings for PyTorch