I propose to change the spec so it would require more unique method names. For example, instead of ap method, a @@fantasy-land/ap. It can be called as foo['@@fantasy-land/ap']().
This would serve two purposes:
This also will allow to create a polyfill that adds fantasy-land support to native JS data structures. It relatively ok to add @@fantasy-land/ap method to Array.prototype, but less ok to add ap.
Also libraries like Bacon, for example, will be able to add fantasy-land support, as for now it problematic with map method, for instance, as it not strictly compatible with the spec, but they could add @@fantasy-land/map that is.
I think this change could significantly speed up the adoption of fantasy-land specification.
The idea inspired by transducers protocol, seems like it worked out for them pretty well.
See also discussion on Gitter: https://gitter.im/fantasyland/fantasy-land?at=553cb82d20328f114ca36f0f
:+1:
This would make life much easier for Ramda which is a generic functional programming library for JS, but which will work well with types implementing the FantasyLand spec:
R.map(square, [1, 2, 3]); //=> [1, 4, 9]
R.map(square, Just(5)); //=> Just(5).map(square); //=> Just(25)
R.map(square, Nothing); //=> Nothing.map(square); //=> Nothing
R.map(square, Right(7)); //=> Right(7).map(square); //=> Right(49)
// etc.
While we do this sort of dynamic dispatching on a number of our functions, there is a special place in our hearts for the FantasyLand specification, and I would love to see this work more generally than it does now. We could easily code to dispatch to such FL versions first if they're defined and after that to matching named function. This would mean that Ramda might integrate better with Bacon and other libraries that have conflicts with the FL names.
Why not just put the fantasy-land library in a wrapper and expose your new
method names. I just don't feel fantasy-land should do this IMO.
So :-1: from me.
On Sat, 23 May 2015 20:47 Scott Sauyet [email protected] wrote:
[image: :+1:]
This would make life much easier for Ramda
https://github.com/ramda/ramda which is a generic functional
programming library for JS, but which will work well with types
implementing the FantasyLand spec:R.map(square, [1, 2, 3]); //=> [1, 4, 9]
R.map(square, Just(5)); //=> Just(5).map(square); //=> Just(25)
R.map(square, Nothing); //=> Nothing.map(square); //=> Nothing
R.map(square, Right(7)); //=> Right(7).map(square); //=> Right(49)
// etc.While we do this sort of dynamic dispatching on a number of our functions,
there is a special place in our hearts for the FantasyLand specification,
and I would love to see this work more generally than it does now. We could
easily code to dispatch to such FL versions first if they're defined and
after that to matching named function. This would mean that Ramda might
integrate better with Bacon and other libraries that have conflicts with
the FL names.—
Reply to this email directly or view it on GitHub
https://github.com/fantasyland/fantasy-land/issues/92#issuecomment-104939407
.
The problem is that FantasyLand wants to claim ownership of all of these names:
With the exception of ap (and arguably concat), these are very common English words, with many meanings.
If a user has a type that uses one of these words for another meaning -- map for something geographic, reduce for fractions, extend for credit processing, etc. -- then she cannot make her type a Functor, a Foldable, an Extend, etc., according to the FantasyLand specification, without renaming her perfectly-well named functions.
With the suggestion here, she could easily add the FantasyLand types, and any tool that uses the types in a generic way and any algorithm written against these algebraic data types would work as they do today, But we gain a more declarative sense too. Right now, the existence of a chain method on an object certainly does not imply that that object also has a map method, as this specification would like. Because clearly the existence of the chain method could be for some other purpose. But if it had @@fantasy-land/chain, one could certainly infer that compliance with this specification is intended, and then code accordingly. (I would actually prefer something like @@algebraic-data-types/chain or @@alg-types/chain, but that's just bikeshedding.)
On the naming: Array's map should allow it to be a Functor. Patching monkeys to add support is not something I'm in favour of.
@CrossEye something with chain doesn't have to have map:
A value that implements the Chain specification must also implement the Apply specification.
A value which satisfies the specification of an Applicative does not need to implement: (map)
(function (e) {
var fl = require ("fantasy-land-library");
// require all the fantasy land libraries here.
// you could use common js everywhere if you're using the browser
...
e['@@fantasy-land/ap'] = ap;
...
})(whatever);
That way you're not exposing any fantasy-land functions that you don't
want. We could even have a git repo full of these.
On Mon, 25 May 2015 05:20 Brian McKenna [email protected] wrote:
On the naming: Array's map should allow it to be a Functor. Patching
monkeys to add support is not something I'm in favour of.@CrossEye https://github.com/CrossEye something with chain doesn't have
to have map:A value that implements the Chain specification must also implement the
Apply specification.A value which satisfies the specification of an Applicative does not need
to implement: (map)—
Reply to this email directly or view it on GitHub
https://github.com/fantasyland/fantasy-land/issues/92#issuecomment-105120761
.
On the naming: Array's map should allow it to be a Functor.
Right, but we could also add @@fantasy-land/ap, @@fantasy-land/chain, @@fantasy-land/of etc. to Array.prototype. Not sure if it makes much sense in case of Array, but what about native Promise, for instance?
Also the spec can be stricter this way, e.g. it can demand that @@fantasy-land/map(fn) must call fn with a single argument, or in all places where it says that a behavior is undefined, it can say that an exception must be thrown.
Also :+1: on @@alg-types/chain, @@fantasy-land/chain is just something first came to my mind, and we needed to call it somehow :smile:
@SimonRichardson: Perhaps I'm not being very clear. Ramda is not trying to implement the specification at all. (Well, there is a side project, but that's not the point) Instead, Ramda is presenting a more functional API centered around functional composition. Many of its functions work on lists, and some of these overlap with the FantasyLand methods. For these and some others we do a form of dynamic dispatch. Thus R.map(fn, object) will call the object's map method if it exists, passing fn. But currently we also have no guarantee that the map we call has anything to do with the expected Functor behavior. For all we know, it could be geography.
@puffnfresh:
@CrossEye something with chain doesn't have to have
map
Yes, sorry, but the point should still be clear.
I really like the way the transducers protocol has been handled as a low level library that's out of the users' way, and which will not interfere with other design of their tools. I would love to see FL do the same.
I think what would benefit this proposal would be more clarity in what is suggested. It's kind of hard to understand what the point is, who it benefits, what would change, how to mitigate confusion, and what current libraries would need to do.
It sounds like the proposal can help foster adoption, but it's hard to see how.
@CrossEye I might reluctantly give in on this, but I really think that as long as this is an alias to the functions then it could work. I absolutely think that the functions should stay as is and that your namespaced aliases should just be sugar in the spec.
I use fantasy-land by hand and I do know what map does in my code base and definitely don't want to use ['@@fantasy-land/map'] as functions in my code base.
@joneshf
I think what would benefit this proposal would be more clarity in what is suggested.
I'll try to summarize.
what would change
Only names of the methods that the specification require to implement.
what current libraries would need to do
Also only change the names.
who it benefits
- Libraries like Ramda, that want to add methods that operates on types described in FL. Because duck-typing will be more reliable — if an object has
@@fantasy-land/map, it's for sure implements FL Functor.- Libraries that want to implement FL. Because they may already use short names, that FL now requires, for something else, or for same functionality but with slightly different API, that incompatible with the spec.
- We will be able to add FL methods as polyfills to native classes, which automatically allow libs like Ramda to operate on a native Promise as on a Monad, for example.
- The FL spec will be able to become stricter e.g., when it says that a behavior is undefined, it can say that an exception must be thrown. It will improve reliability in all the code that uses FL one way or another. For instance, we won't need to worry about stuff like
['10', '10', '10'].map(parseInt) // [10, NaN, 2].
@SimonRichardson
I use fantasy-land by hand and I do know what map does in my code base and definitely don't want to use ['@@fantasy-land/map'] as functions in my code base.
Good point! But I don't see a problem here, all libs that implement FL, will be able to add both long and shorter name with the same functionality. They even can state in the docs that they "implement FL with both long and shorter names". Also you'll can use libs like Ramda instead of directly call FL methods, if you like, but again no one stops anybody to add methods with short names and use them directly.
@SimonRichardson
I might reluctantly give in on this, but I really think that as long as this is an alias to the functions then it could work. I absolutely think that the functions should stay as is and that your namespaced aliases should just be sugar in the spec.
Unfortunately, this would likely only work if it were the other way around, if users implemented @@alg-types/map. They could feel free to implement map too as an alias if they like, but generic tools, which know only about the Functor specification would be looking for the former. This would be something akin to the iteration spec of ES6: the language can perform, for instance, for-of on anything that has the correct symbol defined. But that symbol is not one that has any significant chance of colliding with other uses.
I'm highly in favor of making this change. The arguments given so far has been splendid but there is another angle as well: Currently fantasy land requires data types to implement a sensible object oriented API. It completely wrong for a specification that is coming from a functional angle.
I've created a library that exposes reactive streams that implements the applicative interface. This means that the streams must have a map method. I've _only_ added the map method to support fantasy land. My users should not use it directly, they should instead use the map function I make available. However since object oriented programming is so common in JavaScript people continue to get it wrong even though I've added a disclaimer in the documentation. This confusion would probably _never_ happen if the method names where prefixed.
It is very annoying that one has to implement a object oriented API that can easily be mistaken for a genuine OO API in an otherwise completely functional library that makes no use of methods.
@puffnfresh
On the naming: Array's map should allow it to be a Functor. Patching monkeys to add support is not something I'm in favour of.
Array.prototype.map is not a proper map function. It invokes the mapping function with three arguments which wreaks havoc on curried functions. Not having a collision with Arrays misbehaving map would be an advantage, not the opposite.
@SimonRichardson
I use fantasy-land by hand and I do know what map does in my code base and definitely don't want to use ['@@fantasy-land/map'] as functions in my code base.
That is of course easily solvable by using a function like this function map(fn, app) { return app[''@@fantasy-land/map'](fn); }. Then you could also curry the function and additionally avoid the troubles with methods in JavaScript (worrying about their this binding).
@paldepind I vote for you to make a wrapper and expose what you need to :)
@SimonRichardson How would a wrapper help expect making it more cumbersome to use my library with consumers of fantasy land adts? That is not a solution.
I am not satisfied that the only counter argument in this discussion has been "I just don't _feel_ fantasy-land should do this IMO." This is not about feelings. This is about making it easier and less problematic for libraries to expose adts _and_ for consumers of adts to be safe and certain that they are dealing with the real thing. And not any of the thousand unlawful map implementation that you will find in the wild west of JavaScript.
@SimonRichardson:
It's clearly understandable why this spec was written the way it was. There is a real desire for this to simply work where possible with existing code. Use map instead of fmap since Arrays are _already_ Functors using map. Use object methods instead of pure functions since they're nearly ubiquitous in JS, and avoid (almost!) any necessity for type dictionaries. Give users familiar names, since they're easier to type and to understand.
But this can break down eventually for reasons already expressed. People really would like to describe their types according to the FantasyLand specification, and use them with tools that are ready to work with Functors, or Applicatives, or whatever. But because of competing concerns in their domain, they can't really use the names reserved by FantasyLand for their Functor or Applicative methods. Or they have preexisting functions with similar behavior but a different API that their users are already far too long accustomed to use. Suddenly FantasyLand's easy solution is not so simple.
Users can get around this only by writing wrappers for any type that has these problems, a horrible solution. Libraries that work with these types cannot get around this at all. But FantasyLand has a simple fix. It's worked well for Transducers and for the Iteration protocol; it really seems like a good solution here too.
I've only added the map method to support fantasy land. My users should not use it directly, they should instead use the map function I make available.
@paldepind why bother at all, if map does what it's supposed to do F[A] -> F[B] then what's the issue. You're exposing a very useful method for people to use. If the implementation doesn't work how your library works, then just don't align to the specification. I don't see the need to align to the specification at all if you don't want your library to expose map.
But FantasyLand has a simple fix. It's worked well for Transducers and for the Iteration protocol;
@CrossEye I would agree, _but_ fantasy-land isn't doing anything magical here. It's not setting up things for mutability nor is it doing anything grossly underhanded. I see no point in it! The methods in the specifications are really simple, there is no ambiguity about what happens and when it happens. If you want to add a _fantasy-land protocol_ then maybe that's the way to move forward. I just don't see the need to make something that is inherently simple, complicated.
Fantasy-sorcery implements a generic map method and I've used this many times in the past without issue, again, maybe it's worth moving forward with this.
https://github.com/fantasyland/fantasy-sorcery/blob/master/index.js#L16-L22
It might actually help if the spec was versioned, then this could be 2.0.0. (or somehow represent a divergency)? Ultimately we could put this to a vote and see what the outcome is, I maybe fighting against the tide here :)
@CrossEye I very much agree with you. But I have to complain about this again:
Use
mapinstead of fmap since Arrays are _already_ Functors using map.
This is a similar claim to what @puffnfresh wrote earlier. But it is still only superficially true.
Consider the following completely normal (albeit contrived) usage of an applicative
var multiply = curry(function(x, y) { return x * y; });
function multWith(functor) {
return functor.map(multiply);
}
Here fn should obviously return a functor of functions. But if you pass an array to it that will not be the result – you will break it. Pretending that Array is a functor is wishful thinking that is only going to cause trouble. Ideally fantasy land should add prefixes _and_ specify that functions passed to map must only be invoked with one single argument. Otherwise fantasy land compatible applicatives can and will be completely unreliably as is the case right now.
@SimonRichardson
why bother at all, if map does what it's supposed to do F[A] -> F[B] then what's the issue. You're exposing a very useful method for people to use.
You can say that. But the point is that it causes confusion for users. They see the map method (which is only there for fantasy land but they don't now that) and think they should use it. Then they go look for other methods but they don't see them (expect for the other fantasy land methods I implement). Then they get confused. Anyway, for my library this it not a _huge_ issue. I mostly broad it up since other hadn't. For other libraries that already have an incompatible map method the problems are much bigger (as @CrossEye and @rpominov have explained). Also the array issue I explained above is more significant as well.
For other libraries that already have an incompatible map method
Then it's not fantasy-land compliant. It's that simple.
@SimonRichardson Just wonder, what is the purpose of fantasy-land in your opinion? Please, don't get me wrong, it's a serious question, not a troll. It's just seems like we see the purpose of this spec differently, which is the source of all the confusion about this proposal.
Then it's not fantasy-land compliant. It's that simple.
Of course? Don't you see that the problem arises as soon as someone wants that library to _become_ compliant?
@rpominov I didn't take it as such :)
It's just seems like we see the purpose of this spec differently, which is the source of all the confusion about this proposal.
I :100: agree with you about this, it seems like libraries are wanting to align to the specification, which is completely understandable. I just don't see the reason why you would want to hide various methods because it might confuse people. If you think your library might confuse people by how map is implemented when aligned to the specifications then either don't align to it or educate people on how it's implemented and why.
From my point of view, I would like to code to the specification without additional complexity, like I can already do now. I'm all for progressive changes to the specification, but I don't see the need for the namespaced complexity as map, chain, extend, extract, etc are so easy to implement in their current guise.
@paldepind
My users should not use it directly, they should instead use the map function I make available.
And not any of the thousand unlawful map implementation that you will find in the wild west of JavaScript.
Adding another map to the _wild west_ isn't helping either, as you're doing the thing which is confusing, as you're saying you're compliant with fantasy-land and then you're not at the same time. Consistency is key, when implementing a library for end users?
But I have to complain about this again:
Use
mapinstead offmapsince Arrays are _already_ Functors usingmap.This is a similar claim to what @puffnfresh wrote earlier. But it is still only superficially true.
I agree. I was speaking of the reasons the specifcation was written is what is on the surface the easiest manner, but which is in the end more complex than is necessary.
Note, though, that your example depends on a style of curry than many in the FP community might not recognize nor accept. By many definitions, and in many implementations, a curried function _is_ a function of one parameter.
For other libraries that already have an incompatible map method
Then it's not fantasy-land compliant. It's that simple.
And because FantasyLand has chosen to define itself in terms of common words such as 'map', 'empty', 'reduce', 'chain', etc., such users can't make their libraries tantasy-land compatible. Maybe others feel this is fine, and only those that can throw aside whatever other constraints they have upon them are worthy of implementing this spec. But I feel it's a shame. I would rather these ideas were easier for users to implement.
But I feel it's a shame. I would rather these ideas were easier for users to implement.
@CrossEye
Ultimately we could put this to a vote and see what the outcome is, I maybe fighting against the tide here :)
If people vote that the spec should be namespaced, then I think semver should be pushed through at the same time, then at least we can live together.
I just don't see the reason why you would want to hide various methods because it might confuse people. If you think your library might confuse people by how map is implemented when aligned to the specifications then either don't align to it or educate people on how it's implemented and why.
And
Adding another map to the wild west isn't helping either, as you're doing the thing which is confusing, as you're saying you're compliant with fantasy-land and then you're not at the same time. Consistency is key, when implementing a library for end users?
I think there is a misunderstanding here. My library exports a proper map _function_ with a signature like this (a -> b) -> Stream a -> Stream b. It also exports a lot of other functions. My library is a functional library thus my users should ideally _never_ user _any_ methods when they are using it. I do however have to implement a small amount of methods _only_ to support fantasy land. Users see these methods and thus they think they can use my library in an OO style which is not the case. I am not doing anything that is not compliant with fantasy land and I am also not "adding another map to the wild west".
Note, though, that your example depends on a style of curry than many in the FP community might not recognize nor accept. By many definitions, and in many implementations, a curried function is a function of one parameter.
It is only a significant problem in that case, but it could also be a problem with variadic functions. In any case it is still wrong to pass more than one argument to a function passed to a map. map should always treat the function passed to it as a unary function.
From my point of view, I would like to code to the specification without additional complexity, like I can already do now
Please explain the complexity of adding a string prefix to the method names? Creating functions that invoke the methods are really simple and many people are already doing it to avoid annoyances with methods in JS.
The end goals for me is. 1/ Exporting fantasy land adts should be as easy and smooth as possible – then more people will implement it. 2/ Consuming fantasy land adts should be as safe as possible – safety means being certain that one is actually operating on a adt and not on something with colliding method names. Prefixing achieves both of these goals.
Please explain the complexity of adding a string prefix to the method names?
Now I'm having to create methods/functions that needlessly wrap and call ['@@fantasyland/map'] instead of map. You've just made my code base more complex when in fact it need not be. I'm not making a library, we're not writing mutable constructs, again, these are easy and simple methods (functions if you use sorcery or other point free library) that are easy to understand. It's not like we're making compile to assembly here, we just calling methods on an instance.
@paledepind: I'm on your side here, but let's not downplay the ugliness of both implementing and using @@alg-types/map compared to implementing and using map. This is not necessarily "easy and smooth". For most users, map will be easier. That does _not_ mean it's simpler. Or that it's better.
The advantages have to do with the ability for _anyone_ to adopt this for compliant implementations. Right now, FL is limiting the pool of potential implementations for the shallowest of reasons. But those are actual reasons, not just figments of someone's imagination.
I don't know whether I get a vote, but if I do I vote in favour of namespacing.
Everyone should get a vote imo, this just needs to be put to bed, one way or another :+1:
i'm also a proponent of namespacing. however, i am also from the ramda bloc. Here is why I see it as useful: we are trying to present a point-free interface for using FL. But we don't have any way of distinguishing a compliant map from WildWest.map. While namespacing does not exaclty _solve_ this, it makes the intent--to be FL compliant--explicit.
Now I'm having to create methods/functions that needlessly wrap and call ['@@fantasyland/map]instead of map`. You've just made my code base more complex when in fact it need not be. I'm not making a library, we're not writing mutable constructs, again, these are easy and simple methods (functions if you use sorcery or other point free library) that are easy to understand. It's not like we're making compile to assembly here, we just calling methods on an instance.
But we agree that if you're using sorcery or another point free library then the prefixes will be handled by the library so such users would never experience it? So you can still have your easy and simple functions just not your easy and simple methods. Since methods are so annoying anyway it seems like a small trade of to me considering the advatages.
Since methods are so annoying anyway
One _point_ of view :)
:-1: from me; I think part of the benefit of fantasy-land is short, simple function names that have obvious meaning. If library authors want to go the namespaced route, I think it's easy enough to just mention something akin to:
This library follows the
fantasy-landspec, but prefixes its function names with@@fantasy-land/to resolve naming conflicts.
Short, canonical names are a major plus to fantasy-land for me, so I have to vote no.
This library follows the
fantasy-landspec, but prefixes its function names with@@fantasy-land/to resolve naming conflicts.
This is backwards, no? Alice builds a library which provides some types: Either, Maybe, and Tuple, say. These types satisfy the requirements of the applicable sections of the Fantasy Land specification. Bob builds a library which provides a number of functions, some of which dispatch to methods endorsed by the Fantasy Land specification.
We're not discussing the API Bob's library exposes to its users; we're discussing the contract by which Bob is able to safely consume the types provided by Alice's library (and other such libraries).
For example:
Alice.Nothing.prototype['@@fantasy-land/map'] = f => Alice.Nothing();
Alice.Just.prototype['@@fantasy-land/map'] = f => Alice.Just(f(this.value));
Bob.map = curry((f, x) => x['@@fantasy-land/map'](f));
Bob's library could export Bob['@@fantasy-land/map'], but it would still need to dispatch to map to integrate with FL-compliant types. Bob has no way of knowing whether x.map is even _intended_ to be used in this manner. With x['@@fantasy-land/map'] there's no guarantee that the function is lawful, but we can at least be sure that the creator of x _intends_ x to satisfy the requirements of Functor.
If library authors want to go the namespaced route, I think it's easy enough to just mention something akin to:
This library follows the
fantasy-landspec, but prefixes its function names with@@fantasy-land/to resolve naming conflicts.
So, imagine I am not the author of an implementation of fantasy-land types. Instead, I am writing tools that should work well with any conforming implementation. Let's say I'm writing a binary Tree structure that accepts for its nodes the members of any particular conforming Monoid.
I've got a function in my library that accepts a Tree and an initial value and combines them into a single value, based only on the specification.
But the poor implementer, who couldn't follow the specification exactly because her procat, mehcat, and concat methods somehow conflicted with FL, follows your advice and simply implements @@alg-types/concat instead. She documents it well, and her users have the very best combined cat pictures experience around. (:smile:)
But when she finds she needs to store these in trees, she hears about my awesome Binary Tree for Monoids and decides to use it for her project. BOOM. It doesn't work!
In this scenario, my tool is fine, as it does exactly what it should for conforming Monoids, And the author's library is fine, since she's documented that she's just prefixing her names for internal compatibility. What is it that's failed? I think it's a specification that is squatting on too many good names.
This issue reminds me of https://github.com/promises-aplus/promises-spec/issues/94 without all the name calling, denigration and attitudes.
Our industry as a whole lost out on that one because we couldn't communicate the ideas in a way that worked for everyone. We lost because there's now this separate spec that not everyone takes seriously. We lost because we've got a whole league of people implementing/learning/writing the promises-aplus api and not recognizing that it's more powerful than it states. We lost because people are reimplementing the exact same api time and time again.
What does it look like when we lose? It looks like this. Each of the first three are a more specific version of the last one. Functions are monadic, lenses are monadic, promises are monadic. Yet the implementations do not exploit this. This example is not a dig on ramda or its maintainers in specific. It is not immediately obvious what commonality these functions all share. This example is a dig on all of us. We all allowed this to happen in our own ways. My part in it was not being educated enough to understand everything that was talked about or to convey my thoughts on the subject (and I've still got a long way to go). These sorts of things will get better in time. It's a learning process for everyone involved, but this is our current state.
I say that the industry lost out because we do not live in a bubble. Even though we might be talking about one specific language, no person in this industry uses only one language in their career. These ideas carry over to other parts of our jobs or projects. And we missed the opportunity to expose more people to these ideas.
As far as the issue at hand, I'm in the camp where I do not see the benefit of changing the names of the methods. From my perspective (as someone who mixes both methods and functions where appropriate) it doesn't seem to make sense to me. To me it just seems like the target has moved, but all the problems are the same. I cannot see the purpose of renaming the methods. And that goes both ways. If we started with the namespaced names, I wouldn't see a reason for converting them to the non-namespaced names. When @rpominov was explaining it some time ago on gitter, it made some sense, but I think I've lost whatever insights I had at the time.
We don't need a divergent spec. We don't need a competing fork. We don't need people to go off and build their own smaller community. We don't need to make it harder for others to understand the benefits of another idea.
We need to communicate better.
When a bunch of smart people come to a spec and suggest an improvement, there's generally a reason for it. I think this idea has value, but many of us just can't see the value. It seems like a bit issue is just the renaming. Many of us can't see the point in a rename. The reason I think it has value is that there doesn't seem to be an immediately recognizable reason against it. The advantages might be blatantly obvious to some, but for me, I cannot see them.
I hesitate to suggest this (as it might cause a rift), but maybe we need some example code in order to understand what is truly being proposed. And by example code I mean a full library or some such. The transducer protocol has been touched on many times, but I still can't see the advantage of that over not namespacing. Perhaps instead of building a new example someone could show two transducer examples: one with namespacing, one without namespacing. This way we can compare and contrast between the two examples. It seems like there's some nugget of goodness hidden within this proposal that would make everything clearer.
One last note. These sorts of things don't have to be all or nothing. Sometimes the best way forward is little by little. Maybe we could namespace some things. Maybe a new method could get namespaced, and experimented with. Maybe we could do both ways at once as an experiment, and decide which way works better.
There are many possibilities that don't require one side winning and the other losing.
Perhaps instead of building a new example someone could show two transducer examples: one with namespacing, one without namespacing. This way we can compare and contrast between the two examples.
Good idea. If I understood transducers I would volunteer! ;)
@joneshf :+1: very well put.
I second the :thumbsup:. Let's continue to be respectful and constructive. We all want to arrive at a good solution.
@joneshf: Very well thought out, and very well spoken, as usual. :+1:
Many of us can't see the point in a rename. The reason I think it has value is that there doesn't seem to be an immediately recognizable reason against it. The advantages might be blatantly obvious to some, but for me, I cannot see them.
I can't really build a library to demonstrate this, but I think I can explain with existing ones. Bacon is a functional reactive programming library that's been around a few years. It's fairly popular, getting around 20k NPM downloads per month, and probably many more through bower, since it's mostly a front-end technology, and presumably many others too. Let's take a wild guess that it's on the order of 75k downloads per month altogether. To my mind, that's a substantial user base.
Bacon has, probably from the very beginning had a map method on its central type, Observable. While this method is related to the Functor map/fmap function, it is not a precise fit. It has some additional options, and even when used like a standard map, it may not be lawful.
Let's imagine now that the maintainers of Bacon have come to appreciate algebraic data types and wanted to implement fantasy-land's Functor on their type. They're immediately stuck. With 75k downloads per month, they have far too large a user-base to simply tell them to lump it and start over with a brand new map. But their own map cannot be made compliant without breaking many existing users' applications.
Because fantasy-land is squatting on a common method name, there's a substantial chance of a conflict. If however, fantasy-land were reserving just @@alg-types/map, Bacon could simply define a new, compliant version of this, making their library slightly bigger in the process, but now able to interact with any Functor-based tools.
Now lets look at Ramda. Ramda is a general purpose FP utility library in Javascript, built around making it easy to create applications through functional composition. Many of its methods work on dense arrays, as the closest analog the language has to lists. But it will also often dispatch its functions to the appropriately-named method of user objects.
Thus R.lift(add)(Mabye(3), Maybe(5)), will actually result in Maybe(8), if Maybe matches the fantasy-land monadic specification, since lift is implemented in terms of ap and map.
There are all sorts of ways in which Bacon and Ramda are a logical fit. One of the expectations people would have from Ramda is that it could so turn various OO signatures from Bacon into simple pure-functional versions. But when Ramda does that with Bacon's map, it's doing something very wrong. It's trying to treat a non-functor as a functor. And Ramda can't really help this, because everything its been told has been pointing this way.
Had the specification been a little more specific, and required that the user have to implement @@alg-type/map, then there would have been no doubt that the function was at least intended to match this specification. Bacon could have implemented this later without disturbing its users. Ramda could call the correct function without bleeding over into Bacon's preexisting API.
This is the sort of scenario I think many of us are imagining.
Thanks. I think I understand that example. And I think I see the value in this again. Someone that tries to do right either has to break their API, or give up trying.
If I'm understanding it correctly, the current prototypal version becomes just one specific implementation of the more general specification. This does seem like a good idea. I'm for it!
An option to help mitigate breaking already established apis could be to provide a prototype that handled the conversion automatically:
One for providing the prototype functions from the namespaced functions:
function FunctorPrototype(data) {
data.map = data['@@fantasy-land/map'];
}
function ApplyPrototype(data) {
FunctorPrototype(data);
data.ap = data['@@fantasy-land/ap'];
}
// More things ...
function MonadPrototype(data) {
ApplicativePrototype(data);
ChainPrototype(data);
}
And one for providing the namespaced functions from the prototype functions:
function FunctorNamespace(data) {
data['@@fantasy-land/map'] = data.map;
}
function ApplyNamespace(data) {
FunctorNamespace(data);
data['@@fantasy-land/ap'] = data.ap;
}
// More things ...
function MonadNamespace(data) {
ApplicativeNamespace(data);
ChainNamespace(data);
}
Then you'd only have to add two lines to stay compliant with the changes, and nothing breaks your current API.
// Add this import:
var namespace = require('fantasy-namespaced');
// This is the old code:
function Id(x) {
this.val = x;
}
// Bunch more code all using the current prototype based way.
// And add this line to inject the namespaced code.
namespace.Monad(Id.prototype);
Unfortunately, that pretty much means that Array will no longer meet the spec. I'm fine with this, since I usually end up getting bit by expecting sane behavior from these functions anyway. I don't think the benefit of having Array sort of implement the Functor, Semigroup, and Foldable spec outweighs the benefits of allowing more people to implement the spec. But I'm not sure how many other people are okay with that.
@joneshf:
An option to help mitigate breaking already established apis could be to provide a prototype that handled the conversion automatically.
I think this would be a great help. It might make this much more palatable to some, and is reasonably non-intrusive.
Not that I'm changing my vote or anything, but are we thinking about something like this?
https://github.com/SimonRichardson/fantasy-cauldron
Feel free to make suggestions, PR's are most welcome!
I'm glad we slowly move forward :)
I have an idea on names itself: what if we use not fantasy-land nor alg-type as the first part, but the name of a type e.g., @@functor/map, @@monoid/concat etc. Or perhaps we can even go ahead and simply use names from Haskell prefixed by @@ e.g., @@fmap, @@mappend etc. I like the latter also because it doesn't use /, which, I guess, may cause some problems in the future.
The issue with @@fmap is the same issue with chain is it not? I think we should namespace to something unique, also I wouldn't worry about / because js is backwards compatible, if they removed that, then I'm pretty sure a lot of stuff would break.
@@fantasy-land/ or @@alg-typ/ is a preferable option.
I think @@fmap may be unique enough for our purposes, compared to chain or map it has two prefixes — @@ and f (short for Functor).
What I meant when mentioning possible problems with / is that may happen that in the future we will use not strings but symbols, so it will look like foo[Symbol.fmap] instead of foo['@@fmap'] (see Iteration protocols and Well-Known Symbols list)
I think @@fmap may be unique enough for our purposes,
I disagree. Much better to fully qualify these and avoid squatting on common names. @@alg-type/... is much more explicit.
Can't a symbol value be evaluated to @@alg-type/... anyway, have read zero up about the details, just what they're used for.
Also I'm really a fan of keeping the @@fantasy-land/..., so not to block competing forks, or specifications. _If_ we went with this of course :)
Also I'm really a fan of keeping the
@@fantasy-land/...so not to block competing forks, or specifications.
sounds good to me.
If we went with this of course :)
naturally. :smile:
Can't a symbol name be evaluated to @@alg-type/... anyway?
As I understand it will not work like this. Symbol name won't evaluate to a string, it's just symbol will be used as the method identifier in the object (symbols is a new thing in ES6, that can be used as identifiers instead of strings). I don't want to dive into details because I don't understand them well myself, but in a nutshell people use strings "@@something" as method names in specs for new protocols, and in the future it's planned to use Symbol.something instead.
With my limited understanding, I think it would be wise to avoid / and - in names now, if we can.
Also, why I like @@fmap, is because with names like this Fantasy Land will look like a good starting point for a proposal to ES spec! Which sounds a bit crazy at this point, but I don't think it's impossible that FL will became part of ES, and many internal classes (Array, Promise, Observable ...) will implement FL methods. If it ever happen, I don't think we will use @@alg-type/map then, but more likely @@fmap.
Anyway, looking at Well-Known Symbols list, I think we should at least avoid / and -, and use camel case to separate words e.g. @@algTypesMap.
@SimonRichardson: I think that's a fine implementation of half the idea that @joneshf mentioned. The other half, to turn a map/chain/of/etc api into a @@alg-types/map/etc one is at least as important. I think that half is what might make it easy for existing implementations to live with a change to the specification names.
I definitely agree that just @@map or @@fmap is not enough. I don't really care much between @@alg-types/map and @@algTypesMap. The former feels cleaner to me, but not be enough to make a difference. As to @@fantasyLand vs @@alg-types, I would specifically prefer the latter precisely to start a standardization effort that might be hindered by some of the bad blood surrounding promises-aplus/promises-spec#94 and the creation of this specification under the name "FantasyLand". But this is still a much more minor issue than the basic one of switching to these more focused names.
ES6 symbols would be a perfect fit. Notice that @@ prefixes only the names that are used in the spec, not the description of the string. We can use whatever key we want - I would recommend no prefix, dots as separators, hierarchical structure. (Though I don't want it to look too closely like Java module names, org.fantasyland.algtypes.functor.map :-))
var getSymbol = Symbol && Symbol.for || function(desc) { return "@@"+desc; }; // looks like symbol reference, is a string
var equals = getSymbol("AlgebraicTypes.equals"),
map = getSymbol("AlgebraicTypes.map"),
concat = getSymbol("AlgebraicTypes.concat"),
empty = getSymbol("AlgebraicTypes.empty"),
map = getSymbol("AlgebraicTypes.map"),
ap = getSymbol("AlgebraicTypes.ap"),
of = getSymbol("AlgebraicTypes.of"),
reduce = getSymbol("AlgebraicTypes.reduce"),
sequence = getSymbol("AlgebraicTypes.sequence"),
chain = getSymbol("AlgebraicTypes.chain"),
extend = getSymbol("AlgebraicTypes.extend"),
extract = getSymbol("AlgebraicTypes.extract");
to be used like
Array.prototype[map] = function map(f) { return this.map(R.unary(f)); };
Array[of] = function of(x) { return this.of(x); };
Promise.prototype[chain] = function chain(f) { return this.then(f); };
Promise[of] = Promise.resolve;
…
The above would automatically fall back to strings in non-ES6 environment, and would request a symbol from the global registry otherwise.
@CrossEye so essentially the reverse?
If we go the namespaced route, is there any problem with simply: fantasyland.map et al?
I'm a bit confused as to why we're using @@ at all; is it just a separation mechanism? It was also mentioned that we should avoid / so . seems like a nice alternative.
I'm a bit confused as to why we're using
@@at all;
Probably because the transformer protocol is using it as well.
is it just a separation mechanism?
Yes, it's meant to signify property names that should have been a Symbol actually, inspired by the ES6 spec, "_Within [which] well-known symbols [are] referred to by using a notation of the form @@name_".
The symbols themselves don't have @@ in their name, we won't use it with them either.
@SimonRichardson. Exactly.
@bergus: the 'alg' at least in my mind was meant to stand for '_algebraic_'. But I guess if there's confusion, it should be spelled out.
@bergus Thanks for clearing that up :+1:
@CrossEye Of course, thanks for pointing it out so that I could replace the "algorithmic". I really should know better. I've just broken my habit of calling ADTs "_abstract_" data types, and now this :-/
@bergus: I like this approach, _except_, there's too much of an algorithm spelled out for a specification. I don't know if there could be some generic description that read something like, "In this specification, in environments that don't support Symbols, any reference to Symbol.typeName for any value of typeName can be replaced with the string '@@' + typeName." And then the specification could just read Symbol.for('AlgebraicTypes.map')
@CrossEye Yeah, I didn't mean this code to go into the spec, but rather in the example implementation(s). Your formulation looks good to me.
Maybe we should also cross-check with what current transpilers do about symbols.
I usually don't really care about names, but I can't get behind @@alg-types/map.
fantasy land, but It seems to hint that this spec is called alg-types, algebraic types, or something along those lines. If the purpose of this proposal is to namespace properly, this naming scheme feels like it will add more confusion than clarity.Is there anything wrong with @@fantasy-land/map and friends?
@joneshf If we do this, I vote for that naming scheme :+1:
Same with me :+1:
On Fri, 26 Jun 2015 16:42 Benjamin Kovach [email protected] wrote:
@joneshf https://github.com/joneshf If we do this, I vote for that
naming scheme [image: :+1:]—
Reply to this email directly or view it on GitHub
https://github.com/fantasyland/fantasy-land/issues/92#issuecomment-115735820
.
I agree. Fantasy land is the name of the specification. But maybe @@alg/map can be justified since the readme begins like this:
Fantasy Land Specification _aka "Algebraic JavaScript Specification"_.
Emphasis is mine.
@joneshf:
(Sorry for the slow response. Just back from a vacation.)
Is there anything wrong with
@@fantasy-land/mapand friends?
Yes and no.
If that was what was settled on, I would live with it, happy with the progress made. But I think something else would be better. Anything else, honestly.
I'm not arguing for my original alternative suggestion of @@alg-types/map. But something that spells out that this function is part of the Category Theoretic definition of a Functor would be very useful. I'd love to hear other possible names.
My objection to "FantasyLand" is pretty simple, and it's possible it might all be based on a misunderstanding. The name of the project, and of this specification has always seemed petulant. I've always assumed that it was named in direct response to Domenic Denicola's characterization of @puffnfresh's suggestion in the infamous Promises thread as fantasy:
Yeah this is really not happening. It totally ignores reality in favor of typed-language fantasy land.
(Later in that thread, of course, was the first public announcement of FantasyLand.)
While naming it this ways is quite funny, it also comes across as a bit childish; it feels like someone trying to gloat over another's inability to understand.
As I said, this may all be a misunderstanding. The name may have come from a book @puffnfresh read as a child, or some other random place. But I doubt that I'm the only one with this impression.
I'm hoping that with this change, especially with the changes @bergus mentions, this could eventually become a widespread standard, and I feel a name with that baggage might hinder it.
That's why I'd rather see a different name.
Just to confirm, the name is absolutely a parody of that quote. I'll never make fun of anyone not understanding something - it was the _willful_ ignorance shown in that issue which motivated Fantasy Land, since I'm always excited to teach anything I know anything about.
@puffnfresh: Thank you for the confirmation.
I laughed out loud when I first saw the name and the logo for the new specification. Slowly, though, the name has not only lost its lustre for me, but turned into something much less appealing. I didn't really think you were gloating in naming it such. But somehow as the name has stayed around, for me it's served as a constant reminder of that thread and the incredible amount of talking past one another that was involved. Because the name is tied more with the origins in an argument than with what the specification actually stands for, the specification itself is always somewhat tainted in my mind as backward-looking. I don't know if at affects others the same way. I doubt that a change would be reasonable at this date, but my point was that at least I would not want to carry this forward into function names.
I don't know if at affects others the same way
No, no it doesn't, it only serves to show that the js-land can be a better place and you're always encounter resistance about anything (react libraries atm).
I'd love to see a mature and more descriptive name as well. But I'm fine with fantasy-land as well, just because everyone knows it under that name. Changing the name of the repo, or even the organisation, might be a bit too late indeed.
fantasy-land is fine with me, too. that it has an edge is a feature IMO.
Since this seems to have arrived at a stand still I will re-fuel this discussion with an additional example and an alternative naming proposal.
Bluebirds promises has a map method. But it does something quite different from a functor map (it's based on the misunderstanding that map is something only for lists/arrays). Today Ramdas R.map will think a Bluebird promise is a functor. Users might be fooled too (I was) and they will only realize their mistake upon receiving run-time errors and closer reading of the Bluebird documentation. These types of errors would completely be eliminated if fantasy land used uncommon names (and don't we all like to eliminate potential errors?).
Now, consider the iterable protocol. It declares that something is an iterable if it has an Symbol.iterator method. The reasons why they didn't just choose iterable as the method name are very similar to the reasons why fantasy land IMO should not be choosing common names: to avoid name collision with non-compatible ideas. I propose that all fantasy land methods use symbols as well. Like Symbol.map, Symbol.chain, etc. For older environments it falls back to @@ prefixed names like @@map, @@chain, etc. This adds a slight extra burden. But I think the iterable protocol has established precedence for this approach and that fantasy land should follow it as well if it wants to be a serious specification.
I'm currently using seamless-fantasy as a personal tool for treating built ins as if they implemented their respective type classes. If fantasy land used un-common names I could replace that with monkey patching. It would also at least be possible to include fantasy land in ECMAScript. Currently that is impossible since it collides with several existing methods (Array.prototype.map for instance). The last argument is quite significant I think.
I think to truly solve this, just make a new PR with the whole specification updated. Then we can at least see if it's viable. :dancer:
I wrote a very lengthy comment on this issue, but my laptop decided to restart before I had a chance of sending it. I'll post my thoughts on the issue once I'm less mad at computers.
(I agree that unique symbols are a good idea. But there are some issues that need to be addressed before we go for that)
This is really long and I'm sorry. But I think all of the points I wanted to make are there :x
I can work on a specification PR to further discuss these issues later this week.
A bit late, but here are my thoughts on this issue:
map, extend or extract can still be fantasy-landfold method, and tomorrow I add a FoldableBy deciding to use Symbols, we need to make sure that every library has access
to the same set of symbols. This is tricky, of course, because symbols are
first-class objects. We get in the same problem Scala has with its encoding of
type classes using implicit calculus, but probably slightly worse.
There are a few ways of solving this problem, none of which I consider entirely
satisfactory:
We can put the known symbols in some global object, like Symbols.FantasyLand
or global.FantasyLandInterface. A library would then have to implement each
algebra by using one of the names exposed by that interface:
Symbols.FantasyLand = {
map: Symbol('fantasy land Functor´s map'),
( ... )
}
Array.prototype[Symbols.FantasyLand.map] = function(f) {
return this.map(x => f(x))
}
Pros:
Cons:
If we provide a module that exposes the interface we expect to be implemented as
a set of known symbols, then every library has only to load that module:
// fantasy-land-interface
module.exports = {
map: Symbol('fantasy land Functor´s map'),
( ... )
}
// fantasy-native-array
var fantasyLand = require('fantasy-land-interface')
Array.prototype[fantasyLand.map] = function(f) {
return this.map(x => f(x))
}
Pros:
Cons:
fantasy-land-interface moduleInstead of having the library itself require the interface, we could have the
library take it as an argument:
module.exports = function(fantasyLand) {
Array.prototype[fantasyLand.map] = function(f) {
return this.map(x => f(x))
}
}
Pros:
Cons:
fantasy-land-interface moduleECMAScript 2015 finally specifies
Realms, which
have always existed, but weren't described in the specification. A "Realm" is
the context in which code is evaluated. So, for example, in a Browser, iframeA
and top window are two different Realms.
Objects from different Realms are fundamentally incompatible, because they are
different objects, inheriting from different objects, etc. Anything that relies
on identity equality will fail when objects from two different Realms
interact with each other.
While
well-known symbols
are usually shared by every Code Realm, Symbols themselves are not. More so,
it's not possible to write an implementation of Symbols in user-land that
guarantees the same unique string in two different Realms. This means that:
I think it's very important to be able to define an interface whose
implementation is guaranteed to be a correct implementation of that
interface. Having common names as the operations in this interface means that we
risk conflicting with every other code, and all of this makes it harder to use
different libraries together.
If we have interfaces whose names are guaranteed to be unique, then any
implementation of those names must be an implementation of our interface. This
works in a very similar way to multi-methods dispatched on the first argument,
and close to Protocols/TypeClasses (with the difference that we can't enforce a
grouping of operations in the language). It also means that we can safely write
runtime contracts that check for the interface, which could be useful for
development.
Going forward, it's probably a better idea to use Symbols for this. Mostly
because it's more semantically correct. This does mean that every library gets
the burden of using something that isn't available in most environments, and
that we need to figure out the best way of sharing these known symbols (I'm
particularly in favour of providing a fantasy-land-interface module that is
required by every fantasy-land implementation). However, the issues with Symbols
and Code Realms has to be considered. "unique enough" strings, using a known
prefix, are a less costly solution, even though it could lead to name
collisions. If we choose the strings route, I'd be in favour of prefixing it
with the name of the specification, so @@fantasy-land/map, or something.
We don't have a test suite,
https://github.com/fantasyland/fantasy-check
On Sun, 13 Sep 2015 19:42 robotlolita [email protected] wrote:
This is really long and I'm sorry. But I think all of the points I wanted
to make are there :x
I can work on a specification PR to further discuss these issues later
this week.A bit late, but here are my thoughts on this issue:
Overview1.
Using unique names is a good idea, because we guarantee that anything
implementing the interface is really trying to implement the Fantasy
Land
interface.
1.*Any object can implement the interface*, since the names are guaranteed to not exist anywhere by chance. So objects that already have an incompatible map, extend or extract can still be fantasy-land compatible, without breaking their user's code. 2. *We don't break existing code by adding new algebras*, since the new names are guaranteed to not exist anywhere by chance. This means that if I implement an object with a fold method, and tomorrow I add a Foldable algebra, my object will not be in conflict with this future specification. The specification can change without breaking existing libraries' code. 3. But *we don't get implementations for free by chance*, which is okay for me, but also means we need to modify existing objects for them to implement the algebras. This makes instances explicit, rather than implicit, and somewhat closer to type class instances in Haskell/etc. 2.If we use Symbols, we're guaranteed to have unique names, because
they're
unique values, rather than a flat namespaced-thingie with structural
equality, like Strings, but:
1.*We need to guarantee a single instance of each symbol*, which is tricky given JS's semantics (for the same reason type classes encoded in Scala's implicit calculus have issues — globally unique classes/instances isn't something that's enforced). Since Symbols are first-class values compared by identity, we need to enforce that only one instance of each symbol is used in the entire program. (More clarifications on why this is a hard problem below). 2. *Every library would have to use ES6*, this means that we burden the writers of the library, and the users of the library, with adding the required replacements. For symbols, this means generating a string with a GUID attached, which is not actually guaranteed to be globally unique, but has little chance of a name collision (still possible in objects used as hashtables). 3. *Something has to provide the known symbols to every library*, meaning we need to place symbol objects somewhere every library would have access to. This has some technical implications which are explained below. 3.Changing the specification in this way is not backwards compatible,
thus:
1.*All libraries need to be updated to the new spec*, in the mean time we have two incompatible versions of the specification. I don't think this is a huge problem because our userbase is not as big as the entire JS userbase, but it's a cost. 2. *We don't have a versioning scheme for the specification*, it would be interesting to decide on one. 3. *We don't have a test suite*, it would be interesting to have one, with laws expressed as property-based tests. This would make it easier for people who want to implement the specification to check their implementation.Technical issues
By deciding to use Symbols, we need to make sure that every library has
access
_to the same set of symbols_. This is tricky, of course, because symbols
are
first-class objects. We get in the same problem Scala has with its
encoding of
type classes using implicit calculus, but probably slightly worse.There are a few ways of solving this problem, none of which I consider
entirely
satisfactory:
GlobalsWe can put the known symbols in some global object, like
Symbols.FantasyLand
or global.FantasyLandInterface. A library would then have to implement
each
algebra by using one of the names exposed by that interface:Symbols.FantasyLand = {
map: Symbol('fantasy land Functor´s map'),
( ... )
}
Array.prototype[Symbols.FantasyLand.map] = function(f) {
return this.map(x => f(x))
}Pros:
- It's easier for every library to implement the algebras.
Cons:
- We'd be hoping no collisions happen with the global names;
- Every library now has an _implicit_ dependency on both the names and
on time/order of evaluation, that's not really controlled by the library;- We require the user to load the proper globals before loading any
library that uses Fantasy Land;- Realms will break things.
Require libraries to load the interface module
If we provide a module that exposes the interface we expect to be
implemented as
a set of known symbols, then every library has only to load that module:// fantasy-land-interfacemodule.exports = {
map: Symbol('fantasy land Functor´s map'),
( ... )
}
// fantasy-native-arrayvar fantasyLand = require('fantasy-land-interface')
Array.prototype[fantasyLand.map] = function(f) {
return this.map(x => f(x))
}Pros:
- Dependencies are explicit;
- No problems with time/order-of-execution;
- The module can be easily incorporated in most workflows by using
something like Browserify.Cons:
- Someone still needs to guarantee that only one fantasy-land-interface
module gets loaded for the entire program. npm doesn't help with this;- Realms still break things.
Require libraries to be parametric modules
Instead of having the library itself require the interface, we could have
the
library take it as an argument:module.exports = function(fantasyLand) {
Array.prototype[fantasyLand.map] = function(f) {
return this.map(x => f(x))
}
}Pros:
- Dependencies are explicit;
- No problems with time/order-of-execution;
- npm can't suddenly break things in case of version incompatibilities.
Cons:
- The user now has to guarantee that the proper fantasy-land-interface
module gets correctly passed to every parametric module. This is similar to
the problem with SML's generative modules, but without the help of the type
system to tell you where you did it wrong.- Realms _still_ break things.
About Realms
ECMAScript 2015 finally specifies
Realms http://www.ecma-international.org/ecma-262/6.0/#sec-code-realms,
which
have always existed, but weren't described in the specification. A "Realm"
is
the context in which code is evaluated. So, for example, in a Browser,
iframeA
and top window are two different Realms.Objects from different Realms are fundamentally incompatible, because they
are
different objects, inheriting from different objects, etc. Anything that
relies
on _identity equality_ will fail when objects from two different Realms
interact with each other.While
well-known symbols
http://www.ecma-international.org/ecma-262/6.0/#sec-well-known-symbols
are usually shared by every Code Realm, Symbols themselves _are not_.
More so,
it's not possible to write an implementation of Symbols in user-land that
guarantees the same unique string in two different Realms. This means that:1.
If we can figure out how to share our well-known Symbols between
Realms,
then code executing in native ES6 VMs will work, code not executing in
native ES6 VMs will not. This is a major problem, as we get random
breaks
whenever Realms are involved.
2.In the general case, objects from different Realms are considered to
implement _different fantasy-land_ interfaces, and so they are not
compatible with each other. Mixing these objects is going to lead to
random
breakages.My own subjective position in this issue
I think it's very important to be able to define an interface whose
implementation is guaranteed to be a correct implementation of that
interface. Having common names as the operations in this interface means
that we
risk conflicting with every other code, and all of this makes it harder to
use
different libraries together.If we have interfaces whose names are guaranteed to be unique, then any
implementation of those names must be an implementation of our interface.
This
works in a very similar way to multi-methods dispatched on the first
argument,
and close to Protocols/TypeClasses (with the difference that we can't
enforce a
grouping of operations in the language). It also means that we can safely
write
runtime contracts that check for the interface, which could be useful for
development.Going forward, it's probably a better idea to use Symbols for this. Mostly
because it's more semantically correct. This does mean that every library
gets
the burden of using something that isn't available in most environments,
and
that we need to figure out the best way of sharing these known symbols (I'm
particularly in favour of providing a fantasy-land-interface module that
is
required by every fantasy-land implementation). However, the issues with
Symbols
and Code Realms has to be considered. "unique enough" strings, using a
known
prefix, are a less costly solution, even though it could lead to name
collisions. If we choose the strings route, I'd be in favour of prefixing
it
with the name of the specification, so @@fantasy-land/map, or something.—
Reply to this email directly or view it on GitHub
https://github.com/fantasyland/fantasy-land/issues/92#issuecomment-139907109
.
Ah, nice. I thought fantasy-check was just a QuickCheck port
Symbols have a built-in solution for global shared symbols — Symbol.for("bar") === Symbol.for("bar") https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for
We can use this, but it won't be too different from just using prefixed strings. Probably the only difference is that symbol-properties are not enumerable by default (with native implementation of symbols). Related twitter thread.
Maybe to simply use prefixed strings is not such a bad idea looking at all issues with symbols.
Ah, nice. I thought fantasy-check was just a QuickCheck port
It was, but I added some laws to it. Ideally it would be nice to put them in it's own repo so people would be much more aware of it/them.
Thanks for your thoughts @robotlolita, much appreciated! The Realms problem can be solved with symbols when we use Symbol.for (as I proposed above), but you're very right, that'll only work nicely in a native ES6 environment. How to produce unique but common strings in pre-ES6 environments (and especially libraries without ES6 tooling) remains an unsolved problem. We _could_ go for our own fantasy-land specific thing, but I think we should talk to transpiler/polyfill guys about this. I would hope that we can make libs written in pure ES6 with Symbol.for that are compiled to ES5 compatible with libs written in pure ES5, and need to have neither of them jump through hoops.
What if we publish a fantasy-land package to npm. The package will expose constants:
const fl = require('fantasy-land')
fl.map === 'fantasy-land/map' // we may use Symbols with this approach as well, I think
So the libraries that use FL in any way will add that package as a Peer Dependency with as wide versions range as possible. And use constants that the package exposes as method names in any place they use FL spec.
For example we may have a library that provides map() that may be used with arrays and any FL Functors:
// my-map-lib/index.js
const fl = require('fantasy-land')
exports.map = (fn, target) => {
if (Array.isArray(target)) {
return target.map(fn)
}
if (target && target[fl.map]) {
return target[fl.map](fn)
}
}
// my-map-lib/package.json
{
"peerDependencies": {
"fantasy-land": "*"
}
}
Then we may have a library that provides some type that implements Functor:
// my-id-lib/index.js
const fl = require('fantasy-land')
exports.Id = function(x) {
this._x = x
}
exports.Id.prototype[fl.map] = function(fn) {
return new exports.Id(fn(this._x))
}
// my-id-lib/package.json
{
"peerDependencies": {
"fantasy-land": "*"
}
}
And finally we can use both of libs in our app code:
// my-app.js
const {map} = require('my-map-lib')
const {Id} = require('my-id-lib')
const foo = new Id(1)
const bar = map(x => x + 1, foo)
bar._x // 2
// package.json
{
"dependencies": {
"my-id-lib": "1.0.0",
"my-map-lib": "1.0.0",
"fantasy-land": "1.0.0"
}
}
What is good about this approach is that we can publish first version of fantasy-land that exposes unprefixed names, so all current libraries will be automatically compatible with that first version.
So if I want to access FL methods directly in my code, I can install [email protected] plus some lib that haven't updated yet and use them like this:
// my-app.js
const {map} = require('fantasy-land')
const {Id} = require('old-lib')
const foo = new Id(1)
const bar = foo[map](x => x + 1)
bar._x // 2
// package.json
{
"dependencies": {
"old-lib": "1.0.0",
"fantasy-land": "1.0.0"
}
}
This will work just fine, although won't save us from name clashes. So then we can publish [email protected] that exposes prefixed names. And consumers will have a choice to use 1.0.0 and get compatibility with all libs, or use 2.0.0 and get compatibility only with libs that migrated to new approach + name clashes safety.
Actually @robotlolita has suggested exactly that. Sorry I didn't re-read comments before writing this.
So the Cons was:
I think peer dependencies solve the first, and we shouldn't have any problems with Realms if we use strings and not Symbols (don't know why we should use Symbols anyway).
Create a PR and then we can see what it feels like to use :)
On Sun, 17 Jan 2016, 17:58 Roman Pominov [email protected] wrote:
Actually @robotlolita https://github.com/robotlolita have suggested
exactly that. Sorry I did't re-read comments before writing this :)So the Cons was:
- Someone still needs to guarantee that only one
fantasy-land-interface module gets loaded for the entire program. npm
doesn't help with this;- Realms still break things.
I think peer dependencies solve the first, and we shouldn't have any
problems with Realms if we use strings and not Symbols (don't know why we
should use Symbols anyway).—
Reply to this email directly or view it on GitHub
https://github.com/fantasyland/fantasy-land/issues/92#issuecomment-172359109
.
I've had a play with it and it works for me. I'm happy to merge this one in - @puffnfresh you happy with this?
This is now merged :+1: Shall we close and open a new issue discussing namespaced prefixes in the future?
That's great! :tada: :confetti_ball:
Yeah, a new issue would be nice, this one a bit too long and hard to read.
One point were made here by me and others is that with unique prefixed names we can tell for sure if an object has a compliant FL method. But it doesn't mean it has an algebra (doesn't mean that the method obeys certain laws).
For example if an object have chain method it does't mean it has Monad algebra. Strictly speaking we can't even say that it has Chain algebra, because spec requires methods to be defined in order for type to support algebras but not vise versa.
I realized it just recently and thought it worth mentioning here.
One point were made here by me and others is that with unique prefixed names we can tell for sure if an object has a compliant FL method. But it doesn't mean it has an algebra (doesn't mean that the method obeys certain laws).
I think there's no way to tell that in general. This is not even a Javascript issue; it must be fairly fundamental and related to issues like the halting problem and Rice's Theorem.
The advantage of prefixed names is that with them it becomes abundantly clear that someone is _meaning to_ create types that adhere to the laws for the given algebra. Code that tries to do something generically against any sort of Monoid can comfortably use your type if you have a prefixed empty and a prefixed concat. It really can't make that assumption just because you happen to have those (unprefixed) methods.
Yeah, I wouldn't worry too much about Monoid case, but chain for instance is more problematic, we can't really tell if a type has Monad or just Chain.
The problem seems solvable though, at least in JavaScript. We could require a property on each value with the list of algebras it implements. E.g. obj[fl.algebras] = ['Functor', 'Chain', 'Monad'].
Ok, I thought you were worried about a different problem.
Oh, yes I'm sure it's possible to have something be both a Chain and and Applicative without satisfying, or even attempting to satisfy, the identity laws. (Does anyone have a good example of this?)
I'm still in favor of a TypeRegistry version instead. But I don't know when, or at this point even if, I'll get back to my attempt at this.
@CrossEye ap should be able to be derived by:
function ap(f, ma) {
return f.chain(function(x) {
return ma.map(function(a) {
return x(a);
});
});
}
I don't have a proof, but I have a feeling that Monad identity laws can be shown to follow from the Applicative and Bind laws given the above.
@CrossEye
apshould be able to be derived...
Yes, that's in the spec.
I don't have a proof, but I have a feeling that
Monadidentity laws can be shown to follow from theApplicativeandBindlaws given the above.
I'd be very very interested to see such a proof if anyone knows one.
Most helpful comment
A bit late, but here are my thoughts on this issue:
Overview
implementing the interface is really trying to implement the Fantasy Land
interface.
guaranteed to not exist anywhere by chance. So objects that already have an
incompatible
map,extendorextractcan still be fantasy-landcompatible, without breaking their user's code.
names are guaranteed to not exist anywhere by chance. This means that if I
implement an object with a
foldmethod, and tomorrow I add aFoldablealgebra, my object will not be in conflict with this future
specification. The specification can change without breaking existing
libraries' code.
for me, but also means we need to modify existing objects for them to
implement the algebras. This makes instances explicit, rather than
implicit, and somewhat closer to type class instances in Haskell/etc.
unique values, rather than a flat namespaced-thingie with structural
equality, like Strings, but:
given JS's semantics (for the same reason type classes encoded in Scala's
implicit calculus have issues — globally unique classes/instances isn't
something that's enforced). Since Symbols are first-class values compared by
identity, we need to enforce that only one instance of each symbol is used
in the entire program. (More clarifications on why this is a hard problem
below).
writers of the library, and the users of the library, with adding the
required replacements. For symbols, this means generating a string with a
GUID attached, which is not actually guaranteed to be globally unique, but
has little chance of a name collision (still possible in objects used as
hashtables).
we need to place symbol objects somewhere every library would have access
to. This has some technical implications which are explained below.
have two incompatible versions of the specification. I don't think this is a
huge problem because our userbase is not as big as the entire JS userbase,
but it's a cost.
interesting to decide on one.
laws expressed as property-based tests. This would make it easier for
people who want to implement the specification to check their
implementation.
Technical issues
By deciding to use Symbols, we need to make sure that every library has access
to the same set of symbols. This is tricky, of course, because symbols are
first-class objects. We get in the same problem Scala has with its encoding of
type classes using implicit calculus, but probably slightly worse.
There are a few ways of solving this problem, none of which I consider entirely
satisfactory:
Globals
We can put the known symbols in some global object, like
Symbols.FantasyLandor
global.FantasyLandInterface. A library would then have to implement eachalgebra by using one of the names exposed by that interface:
Pros:
Cons:
time/order of evaluation, that's not really controlled by the library;
uses Fantasy Land;
Require libraries to load the interface module
If we provide a module that exposes the interface we expect to be implemented as
a set of known symbols, then every library has only to load that module:
Pros:
like Browserify.
Cons:
fantasy-land-interfacemodulegets loaded for the entire program. npm doesn't help with this;
Require libraries to be parametric modules
Instead of having the library itself require the interface, we could have the
library take it as an argument:
Pros:
Cons:
fantasy-land-interfacemodulegets correctly passed to every parametric module. This is similar to the problem
with SML's generative modules, but without the help of the type system to tell
you where you did it wrong.
About Realms
ECMAScript 2015 finally specifies
Realms, which
have always existed, but weren't described in the specification. A "Realm" is
the context in which code is evaluated. So, for example, in a Browser,
iframeAand
top windoware two different Realms.Objects from different Realms are fundamentally incompatible, because they are
different objects, inheriting from different objects, etc. Anything that relies
on identity equality will fail when objects from two different Realms
interact with each other.
While
well-known symbols
are usually shared by every Code Realm, Symbols themselves are not. More so,
it's not possible to write an implementation of Symbols in user-land that
guarantees the same unique string in two different Realms. This means that:
then code executing in native ES6 VMs will work, code not executing in
native ES6 VMs will not. This is a major problem, as we get random breaks
whenever Realms are involved.
implement different fantasy-land interfaces, and so they are not
compatible with each other. Mixing these objects is going to lead to random
breakages.
My own subjective position in this issue
I think it's very important to be able to define an interface whose
implementation is guaranteed to be a correct implementation of that
interface. Having common names as the operations in this interface means that we
risk conflicting with every other code, and all of this makes it harder to use
different libraries together.
If we have interfaces whose names are guaranteed to be unique, then any
implementation of those names must be an implementation of our interface. This
works in a very similar way to multi-methods dispatched on the first argument,
and close to Protocols/TypeClasses (with the difference that we can't enforce a
grouping of operations in the language). It also means that we can safely write
runtime contracts that check for the interface, which could be useful for
development.
Going forward, it's probably a better idea to use Symbols for this. Mostly
because it's more semantically correct. This does mean that every library gets
the burden of using something that isn't available in most environments, and
that we need to figure out the best way of sharing these known symbols (I'm
particularly in favour of providing a
fantasy-land-interfacemodule that isrequired by every fantasy-land implementation). However, the issues with Symbols
and Code Realms has to be considered. "unique enough" strings, using a known
prefix, are a less costly solution, even though it could lead to name
collisions. If we choose the strings route, I'd be in favour of prefixing it
with the name of the specification, so
@@fantasy-land/map, or something.