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

Add Map.applyWithDefaults to satisfy Applicative laws #8

wants to merge 2 commits into from

Conversation

LiamGoodacre
Copy link
Member

@LiamGoodacre LiamGoodacre commented Aug 26, 2018

Inspired by @joneshf's comments here: purescript-contrib/purescript-these#21

This gives us a way of modelling an applicative map, for example something like:

data DefaultMap k v = DefaultMap (M.Map k v) (Maybe v)

default :: forall k v . Maybe v -> DefaultMap k v
default = DefaultMap M.empty

empty :: forall k v . DefaultMap k v
empty = default Nothing

lookup :: forall k v . Ord k => k -> DefaultMap k v -> Maybe v
lookup k (DefaultMap m d) = M.lookup k m <|> d

derive instance functorDefaultMap :: Functor (DefaultMap k)

instance applyDefaultMap :: Ord k => Apply (DefaultMap k) where
  apply (DefaultMap lm ld) (DefaultMap rm rd) =
    DefaultMap (M.applyWithDefault lm ld rm rd) (ld <*> rd)

instance applicativeDefaultMap :: Ord k => Applicative (DefaultMap k) where
  pure = default <<< Just

Whether or not the above should live in this library is a different question (thoughts?), but the function is useful nonetheless.

Copy link
Member

@joneshf joneshf left a comment

Choose a reason for hiding this comment

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

This looks great!

I'm a little confused on the associative law. Other than that, I say add it!

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

Choose a reason for hiding this comment

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

Maybe this Nothing should be any Maybe Int? It looks like it's important to the property that it's Nothing, but it isn't. I had to write down the equations to see that it made no difference. So, maybe it'll prevent someone else going through the same thing?

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, thanks!

left2 = M.applyWithDefault left1 (fd <*> gd) h hd
right0 = M.applyWithDefault f fd g gd
right1 = M.applyWithDefault right0 (fd <*> gd) h hd
in left2 == right1
Copy link
Member

Choose a reason for hiding this comment

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

Is this checking associativity? I haven't tried to look if it's an equivalent formulation, but it looks like the values are grouped the same way. Is this the associative grouping?

let left0 = M.applyWithDefault M.empty (Just compose) f fd
    left1 = M.applyWithDefault left0 (Just compose <*> fd) g gd
    left2 = M.applyWithDefault left1 (Just compose <*> fd <*> gd) h hd
    right0 = M.applyWithDefault g gd h hd
    right1 = M.applyWithDefault f fd right0 (gd <*> hd)
in left2 == right1

I haven't checked that either.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, I completely messed this up 😅

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.

@JordanMartinez
Copy link
Contributor

Should this PR be closed? It's been open for quite some time.

@JordanMartinez
Copy link
Contributor

We merged the Align class in purescript-contrib/purescript-these#29. I'm not sure how that affects this PR, but something to keep in mind.

@LiamGoodacre
Copy link
Member Author

Closing for inactivity, I'd also forgotten it existed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants