Skip to content

Add Map.applyWithDefaults to satisfy Applicative laws #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Data/Map.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Data.Map

import Prelude

import Data.Map.Internal (Map, alter, checkValid, delete, empty, filter, filterKeys, filterWithKey, findMax, findMin, foldSubmap, fromFoldable, fromFoldableWith, insert, isEmpty, isSubmap, lookup, lookupGE, lookupGT, lookupLE, lookupLT, member, pop, showTree, singleton, size, submap, toUnfoldable, toUnfoldableUnordered, union, unionWith, unions, update, values)
import Data.Map.Internal (Map, alter, applyWithDefault, checkValid, delete, empty, filter, filterKeys, filterWithKey, findMax, findMin, foldSubmap, fromFoldable, fromFoldableWith, insert, isEmpty, isSubmap, lookup, lookupGE, lookupGT, lookupLE, lookupLT, member, pop, showTree, singleton, size, submap, toUnfoldable, toUnfoldableUnordered, union, unionWith, unions, update, values)
import Data.Set (Set)
import Unsafe.Coerce (unsafeCoerce)

Expand Down
18 changes: 17 additions & 1 deletion src/Data/Map/Internal.purs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,17 @@ module Data.Map.Internal
, filterWithKey
, filterKeys
, filter
, applyWithDefault
) where

import Prelude

import Control.Alt ((<|>))
import Data.Eq (class Eq1)
import Data.Foldable (foldl, foldMap, foldr, class Foldable)
import Data.FoldableWithIndex (class FoldableWithIndex)
import Data.FunctorWithIndex (class FunctorWithIndex, mapWithIndex)
import Data.List (List(..), (:), length, nub)
import Data.List (List(..), (:), length, nub, mapMaybe)
import Data.List.Lazy as LL
import Data.Maybe (Maybe(..), maybe, isJust, fromMaybe)
import Data.Ord (class Ord1)
Expand Down Expand Up @@ -638,3 +640,17 @@ filterKeys predicate = filterWithKey $ const <<< predicate
-- | on the value fails to hold.
filter :: forall k v. Ord k => (v -> Boolean) -> Map k v -> Map k v
filter predicate = filterWithKey $ const predicate

-- | Apply a map of functions to a map of inputs.
-- | If a key only exists on one side, then a default is used.
applyWithDefault ::
forall k i o . Ord k =>
Map k (i -> o) -> Maybe (i -> o) ->
Map k i -> Maybe i ->
Map k o
applyWithDefault fns defFn vals defVal =
let build k = Tuple k <$> ((lookup k fns <|> defFn) <*>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be possible to use union here, like the implementation in Data.Align? That way if we get a faster hedge union, this will improve for free too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thought: why not add this in the form of newtype MapWithDefault k a = MapWithDefault a (Map k a) with an Applicative instance? I think Conal has a Haskell library which does that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have a think about the union, thanks.

Found the library you mentioned: https://hackage.haskell.org/package/total-map-0.0.6/docs/Data-TotalMap.html
I think both the type you propose and mine are useful for difference purposes.
With the one I proposed you get to model merges where one/both/neither have a default.

(lookup k vals <|> defVal)) in
let allKeys = keys (void fns <> void vals) in
fromFoldable (mapMaybe build allKeys)

38 changes: 38 additions & 0 deletions test/Test/Data/Map.purs
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,41 @@ mapTests = do
quickCheck \(TestMap m :: TestMap Int Int) ->
let outList = foldrWithIndex (\i a b -> (Tuple i a) : b) Nil m
in outList == sort outList

-- Identity: (pure identity) <*> v = v
log "applyWithDefault abides applicative laws: Identity"
quickCheck \(TestMap x :: TestMap Int Int) (d :: Maybe Int) ->
let out = M.applyWithDefault M.empty (Just identity) x d
in out == x

-- Composition: pure (<<<) <*> f <*> g <*> h = f <*> (g <*> h)
log "applyWithDefault abides applicative laws: Composition"
quickCheck \(TestMap f :: TestMap Boolean (Int -> String))
fd
(TestMap g :: TestMap Boolean (Boolean -> Int))
gd
(TestMap h :: TestMap Boolean Boolean)
hd ->
let left0 = M.applyWithDefault M.empty (Just (<<<)) f fd
left1 = M.applyWithDefault left0 (Just (<<<) <*> fd) g gd
left2 = M.applyWithDefault left1 (Just (<<<) <*> fd <*> gd) h hd
right0 = M.applyWithDefault g gd h hd
right1 = M.applyWithDefault f fd right0 (gd <*> hd)
in left2 == right1

--Homomorphism: (pure f) <*> (pure x) = pure (f x)
log "applyWithDefault abides applicative laws: Homomorphism"
quickCheck \(f :: Boolean -> Int) x ->
-- we already know `Just f <*> Just x = Just (f x)`
let out = M.applyWithDefault M.empty (Just f) M.empty (Just x)
in out == (M.empty :: M.Map Int Int)

--Interchange: u <*> (pure y) = (pure (_ $ y)) <*> u
log "applyWithDefault abides applicative laws: Interchange"
quickCheck \(TestMap u :: TestMap Int (Boolean -> Int))
ud
y ->
let lhs = M.applyWithDefault u ud M.empty (Just y)
rhs = M.applyWithDefault M.empty (Just (_ $ y)) u ud
in lhs == rhs