# Picnic: put containers into a backpack

*by Dmitrii Kovanikov*

HASKELL.SG: 6 Sep 2018

## About me

### What I do

Haskell Adept at Holmusk *(present)*

### Contacts

Haskell Software Developer at Serokell *(2016-2018)*

Haskell Lecturer at the ITMO University *(2015-2018)*

Cofounder, Mentor, Developer at Kowainik *(free time)*

@chshersh

## What is Backpack?

**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!

## Interface for containers

## Why we need it?

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
```

## Challenges

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.

## Typeclass-based solution (1 / 2)

```
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]
```

## Typeclass-based solution (2 / 2)

```
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
```

### Problems with typeclass-based solutions?

## Backpack solution (1 / 4)

```
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
```

### Map.hsig

```
cabal-version: 2.0
name: containers-sig
library
signatures: Map
build-depends: base
```

### containers-sig.cabal

## Backpack solution (2 / 4)

```
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 ... >
```

### Map/Contrib/Group.hs

```
cabal-version: 2.0
name: containers-contrib
library
exposed-modules: Map.Contrib.Group
build-depends: base, containers-sig
```

### containers-contrib.cabal

## Backpack solution (3 / 4)

```
{-# 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
```

### Map/Ord.hs

```
{-# 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
```

### containers-ordered-strict.cabal

## Backpack solution (4 / 4)

```
module Main where
import Map.Contrib.Group (groupBy)
main :: IO ()
main = do
putStrLn "### Map ###"
print $ groupBy (`mod` 2) ([1..10] :: [Int])
```

### Main.hs

```
cabal-version: 2.0
name: containers-example
executable map-exe
main-is: Main.hs
build-depends: base
, containers-ordered-strict
, containers-contrib
```

### containers-example.cabal

## Backpack HashMap

```
{-# 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]
```

## Using mixins

```
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)
```

### containers-example.cabal

```
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])
```

### Main.hs

## Backpack IntMap

```
{-# 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
```

### Map/Int.hs

## Problems with IntMap

```
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)]
```

### Map/Int.hs

### Map.hsig

```
signature Map where
...
toList :: Key k => Map k v -> [(k, v)]
```

## Read-only containers

`signature ROMap (Map, Key, lookup, ...)`

### What doesn't work

Maps and Sets from **primitive-containers** package don't have modification operations. This means we need to split our signatures across two packages.

### What works

```
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
```

## Backpack in the wild

* 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

## How to spend free time

## Questions?

#### Picnic: put containers into a Backpack

By Dmitrii Kovanikov

### Picnic: put containers into a Backpack

Slides for talk about Backpack interface for containers.

- 329