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

Does empty need to be a function? #37

Open
polytypic opened this issue Nov 30, 2016 · 6 comments
Open

Does empty need to be a function? #37

polytypic opened this issue Nov 30, 2016 · 6 comments

Comments

@polytypic
Copy link

Currently the empty of Monoid is specified as a nullary function rather than a plain value. Is there a plausible use case that benefits from empty being a function or requires it to be a function?

At the moment, I'm tempted to specify empty as a plain value in a use case of monoid.

@rpominov
Copy link
Member

rpominov commented Nov 30, 2016

There are some minor reasons for it to be a function:

  • It's consistent with fantasy-land, where it's also a function. Although there are plans to make it value there too.
  • It's a bit simpler for the spec when all we have are functions. Just less kinds of things to think about, not sure how to better say it...
  • @gcanti mentioned in Better term for what we currently call Type or Type Dictionary #32 (comment) that there will be some issue with Flow if it will be just a value.

And there's no much harm in it being a function. I mean I don't see there a big conceptual issue.

Is there a plausible use case that benefits from empty being a function or requires it to be a function?

I'd say I'm not sure there isn't, although I can't present any.

All that said maybe we make that change in the future along with other breaking changes if there will be any.

@polytypic
Copy link
Author

polytypic commented Nov 30, 2016

Ok. I'll just write down here a couple arguments in favour of using a plain value.

One downside of empty being a function is that it can (and does) have a negative effect on performance. How large the effect is depends on the case. In some cases you can mitigate that by e.g. manually caching the results of calling empty().

In a simple benchmark of foldMapOf with Sum I saw approximately 5% (manually optimized to cache empty()) to 15% (naïve switch from values to nullary functions) performance hit. IOW, I didn't yet find a way to reduce the performance hit to 0 (with Node 6.9.1). empty of Sum returns 0, which is an easy case for a compiler to optimize. In some cases, empty, unless manually optimized, can imply allocating a new object, which is more difficult for a compiler to eliminate. Having to manually optimize is arguably a nuisance. So, use of a function for empty in the spec seems to lead to inefficiency and complexity in every implementation.

Regarding simplicity, a dictionary is a mapping of names to values. In the current Static Land spec the values are just (artificially) limited to functions. I don't see how allowing non-functions would complicate the spec. On the contrary, one could give an arguably simpler specification for Monoid:

Constants

  1. empty :: Monoid m => Type m ~> m

Laws

  1. Right identity: M.concat(a, M.empty) ≡ a
  2. Left identity: M.concat(M.empty, a) ≡ a

(I just roughly deleted characters from the current Monoid spec.)

Regarding Flow Static Land, related experimental libraries in various ML style languages (e.g. exhibit A and exhibit B) don't require empty to be a function. I'm not intimately familiar with Flow. Perhaps @gcanti could elaborate. What would be lost if the parentheses here would be removed? Couldn't this line just be changed to empty: empty()?

@rpominov
Copy link
Member

rpominov commented Dec 1, 2016

Regarding performance, I think in most cases implementation of a monoid should look like this:

const empty = []

const List = {
  empty() {return empty},
  concat(a, b) {return a.concat(b)},
}

But yea, this is a bit of complication and probably still causes a perf hit, so argument stands.

@gcanti
Copy link

gcanti commented Dec 6, 2016

Hi @polytypic, sorry for the delay. Besides my comment here (which is admittedly a contrived example) I can't think of critical downsides. If I find some cons I'll make sure to inform you and write here

@masaeedu
Copy link

masaeedu commented May 10, 2018

I've been using plain values for empty in this semi-static land compatible library and it works out pretty well, especially when destructuring monoid instances for use in other things.

@adit-hotstar
Copy link

I'm in favor of using plain values too. Note that you could always use lazy getters if you need a nullary function.

const List = {
    get empty() {
        return Object.defineProperty(List, "empty", { value: [] }).empty;
    },
    concat: (a, b) => a.concat(b)
};

This is a contrived example, but it shows that nullary functions are equivalent to plain values when used as lazy getters. Hence, you don't sacrifice anything by using plain values.

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