Skip to content
This repository has been archived by the owner on Oct 9, 2019. It is now read-only.

How to compose custom equality checking functions? #235

Open
jwoudenberg opened this issue Apr 16, 2018 · 6 comments
Open

How to compose custom equality checking functions? #235

jwoudenberg opened this issue Apr 16, 2018 · 6 comments

Comments

@jwoudenberg
Copy link
Collaborator

Sometimes I'd like to include a custom equality check for a type with a library. I'm talking about a function with the signature a -> a -> Expectation. One might do so when Elm's default == implementation will result in the wrong result for a type, for instance when the type's internal representation allows the same value to be expressed in multiple ways or when that internal implementation contains a function.

Some examples of instances where I wanted to write such a custom equality function include:

In fact elm-test bundles some of its own custom equality checks, for lists, dicts, and sets, although I'm not sure if those exist purely for nicer looking error messages, or also because regular Expect.equal would give the wrong answer.

Regardless, I've ran into trouble when composing types with a custom equality function into larger types, and then attempting to write a custom equality function for the larger type. To explain what I mean, let's assume that for some reason I want to build a library around a pair of sets:

type alias SetPair comparable = (Set comparable, Set comparable)

equalSets : SetPair comparable -> SetPair comparable -> Expectation
equalSets (pair1first, pair1second) (pair2first, pair2second) =
    -- ? what to do here ?

My naive approach would be to use equalSets to check the firsts are the same, use equalSets to compare the seconds are the same too, and then combine those two expectations into one. elm-test doesn't come with a function for that last part, something like Expectation -> Expectation -> Expectation or List Expectation -> Expectation, because it could easily be abused to write combined tests for things that should really be tested as separate units.

I've hacked around Expect.all in the past to wrangle it into a List Expectation -> Expectation function, but I wonder if there's a better approach. Or whether I'm perhaps going at this all wrong. Any insights would be appreciated!

@drathier
Copy link
Collaborator

drathier commented Apr 16, 2018

My first instinct is to compare them manually (using a -> a -> Bool), compose the Bool results, and then use Expect.true.

@mgold
Copy link
Member

mgold commented Apr 17, 2018

I agree with @drathier that the library should expose an equals function (example) that returns a bool. (Order is another option if your things can be ordered.)

It's weird for a library to include it's own testing functions, since they don't ship in the compiled bundle. If you really want to provide test helpers for your library, make it a separate package that can be a test dependency.

I've hacked around Expect.all in the past

Don't. Use Expect.pass and Expect.fail. These primitives are exposed for exactly this reason. As for what to pass to Expect.fail to make it look nice, we may need to open up an interface so you can provide diffs similar to the built-ins.

@jwoudenberg
Copy link
Collaborator Author

jwoudenberg commented Apr 17, 2018

An a -> a -> Bool function covers the happy path: if the compared values are equal we're all good. If they're not equal then I don't really see how I can generate an informative error message showing why the comparison failed. Given that, hacking Expect.all currently seems the option that gives the best user experience, in that is produces error messages that are somewhat helpful.

It's weird for a library to include it's own testing functions, since they don't ship in the compiled bundle. If you really want to provide test helpers for your library, make it a separate package that can be a test dependency.

My logic is that if you're using a type from a package, you probably also want to be able to write tests for code using that type. I keep test helpers in a separate module so that those helpers and their dependencies don't get compiled into apps. Typically such helpers include an equality function and a fuzzer for the type.

Another disadvantage I see to splitting out test helpers into a separate package, is that these helpers might make use of the internal implementation of a type. If they're defined in a separate package, that would mean the main package would need to expose these internals.

we may need to open up an interface so you can provide diffs similar to the built-ins

That would help a lot! I'd go one further and say that maybe the diff-generating functions for the built-ins need to be exposed, so that for the pair-of-sets example I wrote above we might generate an error message like:

Sets of pairs are not the same:

(a, b)
 ^
 the first value in both pairs did not line up:

<Insert set-diff generated by elm-test function here>

@drathier
Copy link
Collaborator

Possibly related: #214

@mgold
Copy link
Member

mgold commented Apr 17, 2018

I keep test helpers in a separate module so that those helpers and their dependencies don't get compiled into apps.

I'm not sure if a separate module works for this or not. I wrote my previous comment assuming it was not. If it does work, it's clearly preferable.

The builtins use this format:

    2
    ╷
    │ Expect.equal
    ╵
    3

We could expose this as Expect.failBecause : {expected : String, wasNot : String, actual : String }. Would that be helpful?

@jwoudenberg
Copy link
Collaborator Author

I'm not sure if a separate module works for this or not. I wrote my previous comment assuming it was not. If it does work, it's clearly preferable.

👍 I think it does work, based on a quick experiment. I only see elm-test related code show up in a generated js bundle if I import the module that in turn imports from elm-test.

We could expose this as Expect.failBecause : {expected : String, wasNot : String, actual : String }. Would that be helpful?

Sounds promising! I'm going to play around a bit with that and report back on what using that might look like for the libraries linked above!

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

No branches or pull requests

3 participants