Skip to content
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

Add Ord spec #235

Merged
merged 1 commit into from
Apr 4, 2017
Merged

Add Ord spec #235

merged 1 commit into from
Apr 4, 2017

Conversation

gabejohnson
Copy link
Member

Closes #233

I intentionally left the definition of Ordering implicit (as is that of Boolean under Setoid) to spur discussion.

I'm looking for any suggestions on a better way to state the laws in both the README and the tests.

README.md Outdated
A value that implements the Ord specification must also implement
the [Setoid](#setoid) specification.

1. `a.compare(a) <= 0` (reflexivity)
Copy link
Member

Choose a reason for hiding this comment

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

Why <= rather than ===?

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 was simply copying the laws from PureScript. No other reason. === works just as well and is probably clearer.

README.md Outdated
1. If `b` is not the same Ord, behaviour of `compare` is
unspecified (returning NaN is recommended).

2. `compare` must return a number (-1, 0 or 1).
Copy link
Member

Choose a reason for hiding this comment

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

I suggest backticks around -1, 0, and 1 (and NaN). Also, an Oxford comma. ;)

@joneshf
Copy link
Member

joneshf commented Mar 26, 2017

I'm not enamored with the idea of using -1, 0 and 1. There are other formulations for orderings, posets and comparisons we can use that don't require this C-style obfuscation.

@gabejohnson
Copy link
Member Author

@joneshf what would you suggest? Strings? Symbols?

@gabejohnson
Copy link
Member Author

Okay here's another formulation:

  1. a.compare(a).equals([a, a]) (reflexivity)
  2. If a.compare(b).equals([b, a]) and b.compare(a).equals([a, b]), then a.equals(b) === true (antisymmetry)
  3. If a.compare(b).equals([b, a]) and b.compare(c).equals([c, b]), then a.compare(c).equals([c, a]) (transitivity)

@safareli
Copy link
Member

dependencies.png should be regenerated

@gabejohnson
Copy link
Member Author

gabejohnson commented Mar 27, 2017

How about:

  1. a.covers(a) === false (irreflexivity)
  2. a.covers(b) !== b.covers(a) (asymmetry)
  3. If a.covers(b) and b.covers(c), then a.covers(c) (transitivity)
// defined like operators
const lt = (x, y) => x.covers(y);
const gte = (x, y) => !x.covers(y);
const lte = (x, y) => x.covers(y) || x.equals(y);
const gt = (x, y) => !x.covers(y) && !x.equals(y);
const min = (x, y) => x.covers(y) ? x : y;
const max = (x, y) => x.covers(y) ? y : x;

const nativeCompare = (x, y) => x.covers(y) ? -1 : x.equals(y) ? 0 : 1;

Edit: covers is the wrong name. c.covers(a) if there's no b such that a < b < c.

@gabejohnson
Copy link
Member Author

gabejohnson commented Mar 27, 2017

Back to using <=:

  1. a.precedes(a) === true (reflexivity);
  2. If a.precedes(b) and b.precedes(a), then a.equals(b) (antisymmetry)
  3. If a.precedes(b) and b.precedes(c), then a.precedes(c) (transitivity)
// defined like operators
const equals = (x, y) = x.precedes(y) && y.precedes(x);
const lte = (x, y) => x.precedes(y);
const gt = (x, y) => !x.precedes(y);
const lt = (x, y) => x.precedes(y) && !y.precedes(x);
const gte = (x, y) => !x.precedes(y) || y.precedes(x);
const min = (x, y) => x.precedes(y) ? x : y;
const max = (x, y) => x.precedes(y) ? y : x;

const nativeCompare = (x, y) => !x.precedes(y) ? 1 : y.precedes(x) ? 0 : -1;

@gabejohnson gabejohnson force-pushed the add-ord branch 2 times, most recently from 0821ddd to 86c97e5 Compare March 27, 2017 18:40
@gabejohnson
Copy link
Member Author

I just changed the formulation to use precedes instead of compare. Added a derivation for equals and regenerated dependencies.png.

README.md Outdated
@@ -605,6 +632,11 @@ The `profunctor` method takes two arguments:
When creating data types which satisfy multiple algebras, authors may choose
to implement certain methods then derive the remaining methods. Derivations:

- [`equals`][] may be derived from [`precedes`][]:
```js
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Empty line before ```js, please.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@davidchambers
Copy link
Member

I'll defer to @joneshf, @safareli, and others who are actually familiar with this stuff, but I will say that in its current state this pull request seems extremely elegant!

laws/ord.js Outdated
return eq(a, d) && eq(b, d) && eq(c, d);
};

const transitivity = t => eq => x => {
Copy link
Member

@safareli safareli Mar 28, 2017

Choose a reason for hiding this comment

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

Instead of passing t-s I think we should pass values on which precedes will be called.
Like just pass f, g, h instead of t and x.
Same in other laws.

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 defined precedes on Id which may have been wrong as it should just delegate to value's precedes. Should I define it in internal/func.js instead? I could implement it for String and Number and delegate otherwise.

Copy link
Member

Choose a reason for hiding this comment

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

I think yes

@safareli
Copy link
Member

Small issue with laws otherwise LGTM.
/cc @SimonRichardson @puffnfresh

@gabejohnson
Copy link
Member Author

Updated with changes to tests.

@davidchambers
Copy link
Member

Are you happy with this pull request in its current state, @joneshf? If so, let's merge it and release it as v3.2.0.

@sadasant
Copy link

sadasant commented Apr 2, 2017

:shipit: ❤️

@scott-christopher
Copy link
Contributor

I would have assumed precedes meant < rather than <=. e.g. (42).precedes(42) === true doesn't sound right to me. How about lte as an alternative?

Besides naming, I welcome this addition.

@davidchambers
Copy link
Member

I like @scott-christopher's suggestion. Let's use lte rather than precedes.

@joneshf
Copy link
Member

joneshf commented Apr 3, 2017

I have a different qualm about naming. The name Ord comes from Haskell98. That typeclass is for total orderings. But here we're not requiring a total ordering. Although we don't really follow the typeclasses for other things (Setoid is a thing, Monad doesn't have methods, etc), I wonder how confusing this will be for someone coming from Haskell. So, I guess we should mention whether we want a partial ordering or a total ordering:

  1. Rename to something like Poset, and mention that this is for partial orderings. Since we're actually specifying a poset here, might as well name it appropriately.
  2. Keep the current nameOrd, change the first law from reflexivity to totality, and mention that this is for total orderings.

I'm fine with either of those options. However, I think we should seriously reconsider it being unspecified currently.

@davidchambers
Copy link
Member

Thanks for the feedback, Hardy. :)

However, I think we should seriously reconsider it being unspecified currently.

I don't understand this sentence. What is it that's unspecified?

@joneshf
Copy link
Member

joneshf commented Apr 3, 2017

I guess nothing, thinking about it. You can infer that it's for partial orderings and not total orderings based on the laws.

I think what I should have said was that we should be explicit, rather than saying that something was unspecified 😊.

@davidchambers
Copy link
Member

I'm in favour of Hardy's second option as I believe the intention was to define the type class of totally ordered sets. Do you agree, @gabejohnson?

@gabejohnson
Copy link
Member Author

@davidchambers @joneshf that was an oversight on my part. I'll make it a total ordering unless you can think of a reason we wouldn't want to add that constraint.

@gabejohnson
Copy link
Member Author

gabejohnson commented Apr 3, 2017

@scott-christopher @davidchambers I was looking for a name that didn't suggest quantity and precedes is already used as a synonym for lte in some of the work I read, albeit inconsistently. I also thought it sounded nice paired with equals.
That said, since a total order can have a "least element" I suppose "less than" doesn't have to suggest quantity. I'll change it to lte unless anyone can think of a better name.

@gabejohnson
Copy link
Member Author

I've implemented all of the requested changes. Everything look good?

index.js Outdated
@@ -22,7 +22,8 @@
extend: 'fantasy-land/extend',
extract: 'fantasy-land/extract',
bimap: 'fantasy-land/bimap',
promap: 'fantasy-land/promap'
promap: 'fantasy-land/promap',
lte: 'fantasy-land/lte'
Copy link
Member

Choose a reason for hiding this comment

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

Let's include this after equals.

@gabejohnson
Copy link
Member Author

Done.

README.md Outdated
- [`equals`][] may be derived from [`lte`][]:

```js
function (b) { return this.lte(b) && b.lte(this); }
Copy link
Member

Choose a reason for hiding this comment

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

For consistency with the other derivations, let's drop the space after function.

I'd like to see the parameter named other rather than b to suggest that the method expects an argument of the same type.

README.md Outdated
@@ -700,6 +733,7 @@ be equivalent to that of the derivation (or derivations).
[`extract`]: #extract-method
[`map`]: #map-method
[`of`]: #of-method
[`lte`]: #lte-method
Copy link
Member

Choose a reason for hiding this comment

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

🔤

const b = g[lte](f);
const c = true;
return eq(a || b, c);
};
Copy link
Member

Choose a reason for hiding this comment

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

Taking eq strikes me as unnecessary. I'd find this clearer:

const totality = a => b => a[lte](b) || b[lte](a);

Copy link
Member

Choose a reason for hiding this comment

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

I think all of the laws are like this—equals included. I can't remember why, but there was a reason we switched to this way of writing the laws.

Copy link
Member

Choose a reason for hiding this comment

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

I.e., it seems fine to me to have these laws work similarly, and revisit whether that's appropriate in another PR. But, if you feel strongly, I won't fight it.

Copy link
Member

Choose a reason for hiding this comment

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

Given that I'm working on fantasyland/fantasy-laws#1, I'd very much like to know the reason. If it occurs to you, let me know. :)

Let's leave this as it is, Gabe. These functions will be removed from this repository in due course.

Copy link
Member

Choose a reason for hiding this comment

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

Nice! I didn't know that existed.

@gabejohnson
Copy link
Member Author

I'll squash as soon as you sign off.

@davidchambers
Copy link
Member

Let's update the height of the <img> for dependencies.png. The image's natural dimensions are now 1047 × 347. The width is fixed at 888; the height should be (888 / 1047) * 347 ≈ 294. ;)

@gabejohnson
Copy link
Member Author

Done

Copy link
Member

@davidchambers davidchambers left a comment

Choose a reason for hiding this comment

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

I'm very excited about this addition! 🌳

@gabejohnson
Copy link
Member Author

@davidchambers do you want me to squash it?

@davidchambers
Copy link
Member

Yes, it would be nice to squash the commits. :)

* Use precedes instead of compare to define Ord

* Replace reflexivity with totality so we can have a toset

* Change 'precedes' to 'lte'
@gabejohnson
Copy link
Member Author

Squashed

@davidchambers davidchambers merged commit 3af8d23 into fantasyland:master Apr 4, 2017
@gabejohnson
Copy link
Member Author

Looks like I forgot to put the link to the new spec in the list here

@davidchambers
Copy link
Member

No worries. Would you mind opening another pull request? I'll then merge it and release v3.2.0.

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.

6 participants