10

I would like Sanctuary to provide Fantasy Land -compatible Map and Set types with value-based equality semantics. Ideally these values would be immutable, though this is not critical since Sanctuary would provide pure functions for merging and otherwise manipulating these values.

I would love to leverage the good work done by the Immutable.js team; I imagine implementing persistent data structures takes considerable effort!

The API provided by Immutable.js is of little importance since Sanctuary would expose functions for interacting with these values. The equality semantics of these types, though, are crucial.

This is unacceptable for my use case:

> Map([[[1, 2, 3], 'foo'], [[1, 2, 3], 'bar']])
Map { [1,2,3]: "foo", [1,2,3]: "bar" }

[1, 2, 3] is the same value as [1, 2, 3]. It should not be possible to have two map entries with the same key.

The handling of -0 is also problematic:

> Immutable.is(Map([[0, 0]]), Map([[-0, -0]]))
true

I realize it's possible to define the equality semantics of one's own types by defining equals methods, but I wish to redefine the equality semantics of native types such as Array and Number. Is this possible? The relevant file appears to be is.js, but I see no hooks for customization.

It's possible that Sanctuary's Map type could wrap the Immutable.js Map type. This would provide:

  • a way to handle -0;
  • an opportunity to perform value-based equality checks before performing an assoc operation which would normally result in a duplicate key; and
  • a place to define the various fantasy-land/ methods.

Perhaps:

Map k v = { negativeZero :: Maybe v
          , value :: ImmutableMap k v
          , fantasy-land/equals :: Map k v ~> Map k v -> Boolean
          , fantasy-land/map :: Map k v ~> (k -> a) -> Map a v
          , fantasy-land/bimap :: Map k v ~> (k -> a, v -> b) -> Map a b
          , ...
          }

I'd like to be sure there's no other way to attain the desired equality semantics before creating wrappers such as the above. facebook/immutable-js#519 isn't promising.

13
  • 2
    The following is only my personal opinion. Instead of introducing a new abstract data type I'd solve this problem programmatically by defining a separate function/constructor that takes care of the value equality check while adding entries/constructing the Map. I always tend to accept the limitations of JS. Moreover I doubt that objects used as keys in Maps really benefit from persistent data structures, since they ought to be rather small, like value objects. Immutable.js seems to be more suitable for huge data structures like a redux store. Commented Sep 18, 2016 at 13:09
  • Thanks for the feedback, @ftor. You say that the objects used as keys won't benefit from being persistent data structures, but isn't the pertinent question whether the data structure itself—not its keys—benefits from being persistent? Since wrapping appears to be necessary, I now wonder whether we should wrap the native Map type instead and avoid a dependency: sanctuary-js/sanctuary#233. Commented Sep 18, 2016 at 14:02
  • 1
    Maybe try to use newtype-like wrappers around Array and Number? Modifying such a crucial property of builtins could be devastating. Commented Sep 18, 2016 at 16:27
  • 1
    Oops, I misunderstood "redefine the equality semantics of native types such as Array and Number". Good you don't want to make a global modification :-) It sounded like defining Array.prototype.equals and Number.prototype.equals would solve the problem. Commented Sep 18, 2016 at 21:51
  • 1
    What I mean is that you should use a newtype wrapper around Array and Number which defines your custom .equals method, and then to instantiate the wrapper implicitly for all arrays and numbers that are passed into your wrapped Map. Commented Sep 18, 2016 at 21:56

1 Answer 1

1

Although this was asked long ago I don't see a reference to this simple solution:

const {Map, fromJS} = Immutable

// instead of this:
console.log(
  Map([[[1, 2, 3], 'foo'], [[1, 2, 3], 'bar']]).toString(),
)

// do this:
console.log(
  Map(fromJS([[[1, 2, 3], 'foo'], [[1, 2, 3], 'bar']])).toString()
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/4.0.0-rc.12/immutable.min.js"></script>

By deeply converting the objects using fromJS you get the value equality

Sign up to request clarification or add additional context in comments.

1 Comment

This approach doesn't support fantasy-land/equals, but it works well for values of built-in types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.