Closed rpominov closed 8 years ago
:+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 notifications@github.com 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 notifications@github.com 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 Array
s 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
map
instead 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
map
instead offmap
since 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-land
spec, 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-land
spec, 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-land
spec, 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 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 asfoo['@@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 toArray.prototype
, but less ok to addap
.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