Skip to content
This repository was archived by the owner on Oct 4, 2020. It is now read-only.

Add *WithIndex instances for StrMap #116

Merged
merged 1 commit into from
Jul 31, 2017
Merged

Add *WithIndex instances for StrMap #116

merged 1 commit into from
Jul 31, 2017

Conversation

Rufflewind
Copy link
Contributor

Add FunctorWithIndex, FoldableWithIndex, and TraversableWithIndex instances for StrMap.

instance foldableWithIndex :: FoldableWithIndex String StrMap where
foldlWithIndex f = fold (flip f)
foldrWithIndex f z m = foldr (uncurry f) z (toArrayWithKey Tuple m)
foldMapWithIndex = foldMap
Copy link
Contributor

Choose a reason for hiding this comment

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

How does this one work?

Copy link
Contributor Author

@Rufflewind Rufflewind Jul 27, 2017

Choose a reason for hiding this comment

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

  • foldlWithIndex: nearly equivalent to fold. The flip converts k -> acc -> v -> acc to k -> v -> acc -> acc.
  • foldrWithIndex: first unpack into an array of Tuple k v, then use the standard foldr for arrays. The uncurry converts k -> v -> acc -> acc to Tuple k v -> acc -> acc suitable for foldr.
  • foldMapWithIndex: same as foldMap.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant foldMap specifically. I wasn't seeing how the types lined up. Could you please add a test?

instance traversableStrMap :: Traversable StrMap where
traverse f ms = fold (\acc k v -> insert k <$> f v <*> acc) (pure empty) ms
sequence = traverse id

instance traversableWithIndexStrMap :: TraversableWithIndex String StrMap where
traverseWithIndex f ms = fold (\acc k v -> insert k <$> f k v <*> acc)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be defined in terms of sequence and mapWithKey? It doesn't have to be, just a thought.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently yes! :P

@Rufflewind
Copy link
Contributor Author

Um, so I tried adding a test that relates foldMapWithIndex f to traverseWithIndex (\k v -> tell (f k v)), but traverseWithIndex kept giving me the reverse of what I expect.

I think traverse is broken:

> runWriter (traverse_ tell ["1", "2", "3"])
(Tuple unit "123")
> runWriter (traverse_ tell (StrMap.fromFoldable [Tuple "a" "1", Tuple "b" "2", Tuple "c" "3"]))
(Tuple unit "321")
> fold (StrMap.fromFoldable [Tuple "a" "1", Tuple "b" "2", Tuple "c" "3"])
"123"

This seems to fix it:

-  traverse f ms = fold (\acc k v -> insert k <$> f v <*> acc) (pure empty) ms
+  traverse f ms = fold (\acc k v -> flip (insert k) <$> acc <*> f v) (pure empty) ms

I'm also a bit wary of sequence <<< mapWithKey as well. It seems to work, but the object is being recreated and JavaScript doesn’t necessarily guarantee the object is insertion-ordered, so this could break on some browsers. It might be better to just define traverse in terms of traverseWithIndex instead.

@paf31
Copy link
Contributor

paf31 commented Jul 30, 2017

It might be better to just define traverse in terms of traverseWithIndex instead.

That seems fine to me.

Anyone else have comments here?

Copy link
Contributor

@hdgarrood hdgarrood left a comment

Choose a reason for hiding this comment

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

Since JavaScript doesn't define what order a for-in loop goes over the elements of an object, I'd prefer to also leave the order that StrMap instances iterate over their properties undefined. That is, I wouldn't describe the current Traversable instance as "broken"; if the order is important, people should be using Array (Tuple String a) anyway. So I'd suggest we not treat this as a breaking change.

Apart from that minor nitpick, this looks good to me 👍

@@ -112,10 +118,19 @@ instance foldableStrMap :: Foldable StrMap where
foldr f z m = foldr f z (values m)
foldMap f = foldMap (const f)

instance foldableWithIndex :: FoldableWithIndex String StrMap where
Copy link
Contributor

Choose a reason for hiding this comment

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

foldableWithIndexStrMap?

@paf31
Copy link
Contributor

paf31 commented Jul 30, 2017

I don't know if it's broken, but a Traversable container should be ordered, so we need to pick some ordering, even if it's just alphabetical by key.

@hdgarrood
Copy link
Contributor

At the moment I think it's whatever order the JS engine decides to use in a for-in loop, which is usually insertion order, not by key - but the order used in a for-in loop is not actually defined in the language spec AFAIK.

For example:

> entries = [Tuple "a" 1, Tuple "c" 3, Tuple "b" 2]
> m = SM.fromFoldable entries
> traverse_ logShow m
1
3
2

@paf31
Copy link
Contributor

paf31 commented Jul 30, 2017

Seems like we could fix that easily enough by iterating over the sorted keys, right?

@hdgarrood
Copy link
Contributor

Perhaps, but it's potentially quite a large performance cost. I don't see that there's a problem with the current Traversable behaviour or the behaviour in this PR, personally.

@hdgarrood
Copy link
Contributor

If you want fast lookup, insert, & delete, and a traversal with a guaranteed order, you should be using Map String a, I think.

@natefaubion
Copy link
Contributor

Maybe we should deprecate StrMap, and move it to a JavaScript specific module, with the caveat that it matches JS iteration semantics.

@paf31
Copy link
Contributor

paf31 commented Jul 30, 2017

Obviously StrMap is unordered. But Foldable says it is ordered. Does that mean we can use fromFoldable :: _ ~> Array to observe the insertion order? That sounds like a bug to me.

@hdgarrood
Copy link
Contributor

I'm not sure I'd agree StrMap is unordered; in my mind it's for JS objects which are being used like maps, and in particular where every property is of a fixed type. So a key part of the API in my mind is that a StrMap has this runtime representation.

JS objects necessarily have an ordering, in that a for-in loop has to iterate over them in some order, even if that order is unspecified and therefore unpredictable. It's maybe not ideal, but given that this is how they work, I think it would be strange for StrMap to do anything other than exhibit the same behaviour.

For the same reason, I'm not sure we should go as far as deprecating StrMap; it can be useful for interop, in particular. (Having said that, it would make me happier to see more Map String a usage where it is more appropriate than StrMap, which imo is most of the time.) It might make sense to move StrMap to a separate package but I'm not sure it would be worth the breakage.

@natefaubion
Copy link
Contributor

I mean deprecating it as a part of purescript-maps. It's clearly useful, but it isn't a general purpose data structure. It only exists as a hack for JS interop. if you wrote FFI bindings for a different backend, would you expect to keep all the same JS semantics?

@natefaubion
Copy link
Contributor

Also, it's frustrating that the API for it and Map are not kept in sync. People use it for very different use cases.

@hdgarrood
Copy link
Contributor

Yes, that's a good point. If we were going to do this we should take the opportunity to rename it too, perhaps JSMap or something.

@hdgarrood
Copy link
Contributor

Anyway we should probably move this to a separate issue at this point.

@Rufflewind
Copy link
Contributor Author

foldableWithIndexStrMap?

Oops, should be fixed now.

@hdgarrood
Copy link
Contributor

Great, thanks. So should we merge this, make a minor release, and open a new issue to decide what to do about StrMap?

@paf31
Copy link
Contributor

paf31 commented Jul 31, 2017

@Rufflewind
Copy link
Contributor Author

Here is a demonstration of the bug

Maybe it's better to not think of StrMap as an ordinary unordered map at all, since insertion is not commutative. Rather it behaves more like an array in that regard. (Though technically the standards don't prescribe any ordering.)

@hdgarrood hdgarrood merged commit 02e2e86 into purescript-deprecated:master Jul 31, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants