-
Notifications
You must be signed in to change notification settings - Fork 374
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
require static methods to be defined on type representatives #180
require static methods to be defined on type representatives #180
Conversation
#### `empty` method | ||
<div id="empty-method"></div> | ||
|
||
#### `empty` value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The empty div
allows existing links to this section to continue to work.
The term "type representative" was suggested by @tel in sanctuary-js/sanctuary#64 (comment). Type representatives are also described in the Sanctuary documentation. |
Other behaviours do not require a member. Thus certain algebras require a | ||
type to provide a value-level representative (with certain properties). The | ||
Identity type, for example, could provide `Id` as its type representative: | ||
`Id :: TypeRep Identity`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not quite get last sentence, especially Id :: TypeRep Identity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Haskell types are "real" in the sense that one actually references types in one's programs:
inc :: Int -> Int
inc x = x + 1
In JavaScript types are not referenced (except in comments):
// inc :: Number -> Number
const inc = x => x + 1;
In JavaScript the idea of types exists—as evidenced by the use of type signatures in comments—but there's no "type level"; only a "value level". Since we can't reference types at the type level, we must reference them at the value level. So, for each type we'd like a value which represents it. We call such values type representatives.
Let's consider some examples:
Number
is the representative of the Number type.Number
is the sole inhabitant of theTypeRep Number
type.Id
is the representative of the Identity type.Id
is the sole representative of theTypeRep Identity
type.
The formatting above is very much intentional. The Number type exists only as an idea, so should not be formatted as code. Number
, on the other hand, is a JavaScript value which represents the Number type (in addition to its role as a function from Any
to Number
).
It took me a while to come to terms with the idea of type representatives. This sort of thinking is not natural in a dynamically typed language. I suggest reading sanctuary-js/sanctuary#64 if you have not already done so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The part which confused me was Id :: TypeRep Identity
(Id
and Identity
are different when Number
and Number
are same) is it because we assume definition of Identity would be something like this type Identity a = Id a
?
So if we have:
type Maybe a = Just a | Nothing
Then is this true:?
Just :: TypeRep (Maybe a)
Nothing :: TypeRep (Maybe a)
will take a look at that issue more carefully
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Haskell at least those last equations are wrong.
- Just constructs values at the Maybe type, it's not a representative of the type. In particular, a type should be represented in the shape of the type, not its values.
- Maybe is an interesting example since it's a type constructor, a type function. If we want to represent this is a value-level type representative then
TypeRep Maybe
andTypeRep X
must be able to be combined intoTypeRep (Maybe X)
. Perhaps the type reps of constructors are functions, or probably better (since it'll be easier to analyze) you could have something that gives you($$) :: TypeRep f -> TypeRep a -> TypeRep (f a)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that $$ is data then you can easily analyze it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The part which confused me was
Id :: TypeRep Identity
(Id
andIdentity
are different whenNumber
andNumber
are same) is it because we assume definition of Identity would be something like thistype Identity a = Id a
?
When we talk about "the Identity type" we're referring to a concept with no manifestation in JavaScript. When we talk about "Id
" we're referring to a real thing: an identifier we could evaluate in a REPL. Id
lives in the world of values to the left of the colons; Identity
lives in the world of types to the right of the colons.
Since the world of values and the world of types are separate, we needn't worry about naming collisions. This is valid:
Identity :: a -> Identity a
Identity
on the left of the ::
is a value, whereas Identity
on the right is a type (a type constructor, actually).
Similarly, we could name our type representative Identity
rather than Id
without conflict:
Identity :: TypeRep Identity
I hope this clarifies things a little. :)
I like a lot this idea. Still wonder maybe we could make this not a breaking change or at least less breaking? The piece that concerns me is transition from |
It makes sense with We have const makeId = M => {
function Identity(value){...}
Identity.empty = Identity(M.empty)
return Identity
} Do you see any way to make |
Could we do it before though? Technically maybe, but I don't think it was very useful Monoid in practice. |
I believe I can provide irrefutable logical reasoning for the First, let's consider the question of breaking versus less breaking. From a SemVer perspective there's no distinction: any breaking change requires a major version increment. Were we to stick with an
Currently it's valid to define Even without the We should also consider the burden changes will place on authors of ADT libraries. I imagine this is what you had in mind when you wrote "less breaking". Aside from the The next question, then, is whether we greatly increase the burden on authors of ADT libraries by changing the type of I believe the answer is no. Simply by moving the method from the members to the type representative we preclude certain types—Identity, for example—from satisfying the requirements of Monoid. For some ADT libraries this will require documentation updates and major version increments of their own. This may require a significant amount of work, but this work would be required whether or not The question then becomes why it was decided that I'd love to merge this pull request and publish |
0cbb443
to
6526d65
Compare
@rpominov yes we could: Id.prototype[fl.empty] = function() {
return new Id(typeof this.value[fl.empty] == 'function' ? this.value[fl.empty]() : this.value.constructor[fl.empty]());
}; looks like it's not that useful don't know, and this technique would not work for those types which don't hold value in moment of calling empty (IO/Task/Function) |
Yea.
But on the second thought, I agree. This should be done eventually, and not to much of a burden in any case. |
@safareli Yea, but seems like it can be done only for Identity type. And even though Identity itself can be useful sometimes (as a noop type), that particular Monoid isn't useful at all. |
This pull request has several predecessors. The first one I submitted was #162, which required
If It certainly seems odd to have to go from a type representative to a member of the type (via |
I tried to make Function a Monoid as in Haskell: instance Monoid b => Monoid (a -> b) where
mempty _ = mempty
mappend f g x = f x `mappend` g x but we can't: // what `empty` should be we dont know?
function.prototype.empty = (x) => empty
function.prototype.concat = function(g) {
return (x) => this(x).concat(g(x))
} And this problem would be with IO/Task as we don't have a value in it.
not sure if it would be correct but, as empty is only usable in concat what if had a special partial empty value and check against it in concat. so we can make Identity, Function a monoid again: const isEmpty = e => e['@functional/empty'] === true
// this value is a empty value for any Monoid
const empty = ({
'@functional/empty': true,
'constructor': { 'empty': empty},
'concat': a => a,
})
Function.empty = _ => empty
Function.prototype.concat = function(g) {
return (x) => {
const b = g(x)
if (isEmpty(b)) return this(x)
else this(x).concat(b)
}
}
const f1 = (a) => [a, a]
const f2 = (a) => [a+1, a-1]
f1.concat(f2)(1)
Function.empty.concat(f2)(1) // same as `f2(1)`
Identity.empty = Identity(empty)
Identity.prototype.concat = function(idB) {
const a = this.value
const b = idB.value
if (typeof a.concat !== 'function' || typeof b.concat !== 'function') {
throw new TypeError('`concat` called on Identity which does not hold Monoid value')
}
if (isEmpty(b)) return Identity(a)
else Identity(a.concat(b))
} This way even Identity could conform to Monoid. This technique could be used for universal const of = (a) => ({
'@functional/of': true,
value: a,
chain: (f) => f(a),
constructor: {'of': of},
ap: (f) => {
if (isOf(f)) return f.value(a)
if (typeof f.constructor.of !== 'function') {
throw new TypeError('`ap` called on value which is not Applicative')
}
return f.constructor.of(a).ap(f)
})
isOf = a => a['@functional/of']
Identity.prototype.ap = (f) => {
if(isOf(f) return this.map(f.value)
else Identity(f.value(this.value))
}
of(10) // [1]
.ap(of(a => a)) // [2]
.chain(a => of(a)) // [3]
.chain(a => Http.get(a)) // [4]
// [1,2,3] - we don't know what in which type we are wrapping value we have just
// default implementation for some interfaces a value could possibly hold
// [4]- now as Http.get returns Task value would be of that type, we don't know
// and don't need to know it type upfront. We could have this p.s. Implementations might not be 100% correct, I have not checked them but it gives a general idea. update: fixed some issues in example implementations and added some comments. (still not tested, will test them in couple hours) |
@rpominov @davidchambers what are your thoughts on this "trick" ? |
@safareli I don't know. It could work but it seems so different from what we've been doing before. Also seems quite complicated. |
I haven't yet wrapped my head around your suggestion, @safareli. It deserves its own thread. :) I'd love to know what @puffnfresh, @SimonRichardson, @joneshf, or one of the other old hands thinks of this pull request. It's a significant change, but so far it hasn't encountered significant opposition. Is losing the ability to define |
With trick, I provided, we don't even loos ability to define monoid instance for Identity. Here I have created a gist on that idea. |
@puffnfresh, @SimonRichardson, @joneshf any thoughts on this? I think this is exciting change and safareli/quasi/examples proves that with this changes we lose nothing 🌮 |
I like it, but want to think about it if you don't mind... Ping me if I On Tue, 11 Oct 2016, 16:43 Irakli Safareli, notifications@github.com
|
Wrote some question regarding |
((:bell:)) @SimonRichardson, have you given this matter some thought? |
Nope 😱 On Wed, 19 Oct 2016, 19:20 David Chambers, notifications@github.com wrote:
|
Sum[of] = (x) => Sum(x); | ||
Sum[empty] = () => Sum(''); | ||
Sum.prototype[of] = Sum[of]; | ||
Sum.prototype[empty] = Sum[empty]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point I thought Sum('').constructor
did not evaluate to Sum
. I was mistaken. I've reverted to the daggy version.
6526d65
to
c8e83c4
Compare
Right, I'm happy with this change, we should bump to v2.0.0 I guess? |
+1 from me. I would rather didn't have |
Fantastic!
Yes, we should.
Could you elaborate, @rpominov? Is Static Land not affected by the other breaking changes in this pull request? |
Yeah, I think so. Not affected by other changes. |
One more minor point in favor of method over a value. In the spec we often need to say something about methods. For example we have a title "Prefixed method names". Strictly speaking we would now have to say something like "methods and values" in all such places which will make it more confusing. Or we could keep saying only "methods" but that also will be confusing because one will think "does this include |
Good point, @rpominov! I believe we should replace the word "method" regardless, as it means different things to different people. In JavaScript I distinguish between "methods" and "functions": the former expect We could continue to use "method" in the Java sense of "instance" methods and "static" methods. Regardless of whether we stick with "method" or choose another term, we should define the term. |
@rpominov, I've pushed a commit to rephrase potentially confusing references to "methods". How does it look? Although I prefer |
"properties" sounds great 👍
Can't say that I feel strongly, but still feel like we'd better don't make this change. This is not only about static-land and static-land compatible libs. Any |
7d386d2
to
9ab0234
Compare
I've updated the pull request: If you're happy with the changes, @rpominov, we'll ask @SimonRichardson to sign off and merge. :) |
Thank you! Looks great 👍 |
Okay. Do you agree, @SimonRichardson? If so, let's merge this and release v2.0.0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some nice consistency changes as well 👍
9ab0234
to
3ac198b
Compare
sanctuary-js/sanctuary-type-classes#6 shows how much simpler this makes interacting with |
* 'master' of github.com:fantasyland/fantasy-land: (29 commits) Version 2.1.0 Add Alt, Plus and Alternative specs (fantasyland#197) Use uppercase letters for Type representatives in laws (fantasyland#196) Fix id_test and argument order in laws (fantasyland#193) Version 2.0.0 Another go at updating dependencies (fantasyland#192) release: integrate xyz (fantasyland#191) test: remove unnecessary lambdas (fantasyland#190) require static methods to be defined on type representatives (fantasyland#180) lint: integrate ESLint (fantasyland#189) Enforce parametricity (fantasyland#184) readme: tweak signatures to indicate that methods are not curried (fantasyland#183) Fix reduce signature to not use currying (fantasyland#182) Link to dependent specifications (fantasyland#178) Add Fluture to the list of implementations (fantasyland#175) laws/functor: fix composition (fantasyland#173) laws/monad: fix leftIdentity (fantasyland#171) Minor version bump bower: add bower.json (fantasyland#159) Fix chainRec signature to not use currying (fantasyland#167) ...
Closes #176
I imagine there are several things I neglected to update. Let me know if you think of one. :)