-
Notifications
You must be signed in to change notification settings - Fork 376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why "No parts of b should be checked" for Applicative? #85
Comments
That's because you're All values should not be null by there very nature when interacting with fantasy-land specification. If you do want to have a null checking part of your
|
@SimonRichardson Is the first implementation of Maybe also wrong? Perhaps I don't understand the use case for |
Wrapping puts the value in a context so that you know which |
To answer your question, is the first implementation wrong also, that depends if it fulfils the laws or not. And by laws, I mean the following Applicative and Functor ( The point of |
@TheLudd it's useful for abstraction. With function map(a, f) {
return a.constructor.of(f).ap(a);
} Note that this works with anything that has an Applicative. Maybe is just one example! |
A more useful example is probably: function liftA2(f, a, b) {
return a.constructor.of(f).ap(a).ap(b);
} We can do something like: liftA2(function(a) { return function(b) { return a * b; }; }, Maybe.Nothing, Maybe.Nothing);
// Maybe.Nothing
liftA2(function(a) { return function(b) { return a * b; }; }, Maybe.Nothing, Maybe.Just(10));
// Maybe.Nothing
liftA2(function(a) { return function(b) { return a * b; }; }, Maybe.Just(30), Maybe.Just(10));
// Maybe.Just(300) But again, this works for much more than just Maybe: liftA2(function(a) { return function(b) { return a * b; }; }, Either.Left("Bad"), Either.Left("Error message"));
// Either.Left("Bad")
liftA2(function(a) { return function(b) { return a * b; }; }, Either.Left("Bad"), Either.Right(10));
// Either.Left("Bad")
liftA2(function(a) { return function(b) { return a * b; }; }, Either.Right(30), Either.Right(10));
// Either.Right(300) |
unless i'm mistaken then, a |
@buzzdecafe yep, that's right! |
@puffnfresh @buzzdecafe Is this correct? |
I'd think it is wrong for the traditional semantics of It could still be a valid data type, or these abstractions could still apply. It just has different meaning from the traditional |
I don't believe that's true. Look at |
@TheLudd to implement An easy example of this quantification is Maybe's Maybe.of : forall a. a => Maybe<a> The Please let me know if you know a way we can more easily talk about this. |
i'm also very interested in this discussion -- do you have, or have any interest in, a gitter chatroom for fantasy-land? see e.g. https://gitter.im/ramda/ramda |
I'm wondering if I"m missing something basic here. The first implementation in the OP, was, I believe the one from Ramda's (still early) attempt at implementing the FantasyLand spec. In this implementation, there didn't seem to be any good reason to introduce In other words, this implementation would not allow you to do any non-trivial algebra on |
Yes, The Fantasy Land spec is an attempt to take the idea of parametricity seriously. Some tooling to help ensure parametricity would be awesome. |
No, it's not just an implementation detail. The semantics of the data type are encoded in those two values. You might also think of |
In idiomatic Javascript, if I were to try to encapsulate a a computation that might fail, my usual return value from the failure case would be I guess my question would be how one would expect
But would not
or would you expect to define some new value for that ( That it seems to me to act exactly as |
@CrossEye |
@michaelficarra: Yes, unless you buy my argument that |
I don't. 😏 |
Before we go too deep down this metaphor, we should realize that this is not the only interpretation. It is merely another way of looking at the semantics of the data type. Also, there's no real attempt to define a rigorous type system here. We're specifying an api with laws for it. That said, suggesting there is no difference here is less composable than realizing these are separate values. What happens when you have a computation that might fail returning another computation that might fail? The type of something like this would be
If you assume Suggesting that
|
I think I'm just having a hard time giving up on how I wanted to use these algebraic types with Ramda. (For those who haven't seen it, Ramda is a JS utility library with pre-curried functions, sane parameter order, easy composability, and no mutation of user data.) Although its functions are more strongly-typed than much of JS (no variadic functions, single types for most parameters) we don't go as far as trying to introduce such constructs as the FantasyLand algebraic types. But we would like to offer them as an adjunct for users who want to use them. I'm starting to think that perhaps this simply cannot be done.
(Actually it's This is our failure case. We can't get the head of an empty list. we signal that issue with That's a real bummer. |
Well, as mentioned before, it might not fit the semantics of the conventional That's the whole point of fantasy land. You're not restricted by which data type you want or can use. If you make something and suspect it's a |
@puffnfresh Regarding this rule:
Is it ok to return a Also: I realize that there are relations between the different types in the specification and that some functions can be derived as combinations of others. Is there an example where the expectations break if |
|
The only thing I will miss is:
... since I won't be able to call
which is a little less sweet. |
That's looks really nice to me! |
@buzzdecafe If I understand what's being said here though, since |
if: Maybe.from = function(x) {
return x == null ? Nothing() : Just(x);
}; then: R.compose(map(inc), Maybe.from, head)([1,10,100]) //=> Just(2)
R.compose(map(inc), Maybe.from, head)([]) //=> Nothing
R.compose(map(inc), Maybe.from, head)([undefined]) //=> Nothing that seems reasonable to me. |
I realize that it won't satisfy the laws since this question is about an implementation that does go against the laws. What I did't understand was the reason for this particular law and why it needs to be in place for all the types to be consistent. I was hoping for an example (a certain value put into a Maybe) that would break this consistency if |
@TheLudd Maybe.of(null).map(function(a) { return 1; })
Maybe.of(null).chain(function(a) { return Maybe.of(1); }) Both expressions should result in |
It doesn't lawfully implement You can think of the specification as being another law. So
So it holds for the
EDIT: Nope, this data type is not even a // `Apply` composition
a.map(function(f) { return function(g) { return function(x) { return f(g(x))}; }; }).ap(u).ap(v) is equivalent to a.ap(u.ap(v))
// Left side
Maybe({value: null}).map(function(f) { return function(g) { return function(x) { return f(g(x))}; }; }).ap(u).ap(v)
= Maybe({value: null}).ap(u).ap(v)
= Maybe({value: null}).ap(v)
= Maybe({value: null})
// Right side
Maybe({value: null}).ap(u.ap(v))
= Maybe({value: null})
// `Functor` identity
a.map(function(x) {return x;}) == a
Maybe({value: null}).map(function(x) {return x;})
== Maybe({value: null})
// `Functor` composition
a.map(f).map(g) == a.map(function (x) {return g(f(x));})
// Left side
Maybe({value: null}).map(f).map(g)
== Maybe({value: null}).map(g)
== Maybe({value: null})
// Right side
Maybe({value: null}).map(function (x) {return g(f(x));})
== Maybe({value: null}) The semantics of the data type are what cause it to fail to hold for algebras stronger than |
@joneshf functor composition is absolutely broken. function f(a) {
return null;
}
function g(b) {
return 10;
}
Maybe.of(1).map(f).map(g)
Maybe.of(1).map(function(a) { return g(f(a)); }) The first one will give you |
These examples look funky because of the semantics of the data type and because it's not an To construct the intended values you'd have to already have the nested Maybe({value: Maybe({value: null})}).map(function(a) { return 1; })
== Maybe({value: (function(a) { return 1; })(Maybe({value: null}))})
== Maybe({value: 1}) and Maybe({value: Maybe({value: null})})).chain(function(a) { return Maybe({value: 1}); })
== (function(a) { return Maybe({value: 1}); })(Maybe({value: null}))
== Maybe({value: 1}) |
Oh, right. I was looking at too simple of a case. Yeah, I guess this whole data type is not even a |
Thanks everyone for your input, closing this now. I feel a little bit wiser even though I don't grasp this fully. I understand that these laws works together and need to be consistent no matter what type implements them. I think that my confusion is due to the fact that I don't understand what a |
There probably isn't a good reason for a It is never going to be 100% clean when you bring something that doesn't have the concept of null to a something that does. To be honest you end up working around it using things like |
Really good discussion as well, thanks |
It's unlikely to be good for much at all. You've highlighted the disconnect in this thread: some of us are thinking of use cases while others are thinking of the underlying (mathematical?) laws. The underlying laws know nothing of usefulness, so there may be times when a law disallows behaviour programmers find useful. As several in the thread have said, that's fine, write useful code. The laws simply provide a way to answer questions such as Is this a functor? |
This statement seems misleading. These laws allow useful abstraction and refactoring. For example, it's extremely useful to know that we can always rewrite Always being able to do refactorings and derive these functions requires |
Well put, @puffnfresh. It's more accurate to say that the laws know nothing of the usefulness of any particular value. @TheLudd and @CrossEye were asking themselves when one would ever want |
It's useful because you're not stopping the flow of information. If you build a library that someone else uses as one step in a chain of things to do, you don't want to mess up their flow of information. If you feel that A concrete example might help here. Let's say you were writing a simple repl for js. You have separate functions for each part of it, looking at just read :: String -> Maybe Program
evaluate :: Program -> Maybe a where you might imagine that function Program() {}
function Null() {}
Null.prototype = new Program()
function String(s) { this.value = s; }
String.prototype = new Program()
... What semantics can What semantics can You might compose these together with read(line).chain(evaluate) What's the behavior of this if the line you're reading is "null"? "null" should be a valid program, so it should be read fine and evaluated fine: read("null").chain(evaluate)
== Just(Null).chain(evaluate)
== evaluate(Null)
== Just(null) At the next step ( However, if you view Of course, this example is contrived. You'd generally want a more complex data type so you'd know how and when the repl failed. The point is, when you as the library writer arbitrarily decide that some input has different meaning than everything else in the language, you are making things less composable than they could be. It might be a good abstraction for certain situations, but you limit library users to the mindset you were in when you made the library, as @davidchambers and @puffnfresh mentioned. And for composability and reuse, this limitation is generally a bad thing. It's jquery-esque: jQuery.map([null, null, null], function(x) { return [x]; })
== [null, null, null] Libraries become much more reusable and composable if you don't care what data people store in your data types. |
It may be worth noting that a close analogue of |
If you'd like to review the pull request above, please do so. 😀 |
fyi: scalaz has a similarly to |
Note that |
Good spot! |
Which basically means that you can replace what's on the left by any of the terms that are on the right (as long as
What doesn't let you implement Maybe.of like that are the laws surrounding these algebras. For example, the Identity law for applicatives states:
So, if you take the implementation of Maybe.of above, that law is most likely broken:
|
Why is it important for the Applicative
of
function to not check it's input?My question arises because I have seen two different types of implementations for
Maybe
, one that pretty much looks like this where the check to see if the value is null is done in the map function:and another one which has 2 sub types
Just
andNothing
.My point here is that the second implementation does a check in the constructor, and because of this it would break the fantasy land specification if it implemented
of = function(val) return Maybe(val)
but this is not true for the first implementation since it does the checking in the map method.In the second case,
Maybe.of
can never return something that will not apply the input function in map.This leads to the situation that because of internal design decisions
Maybe.of
may be forced to be inconsistent with other implementations in order to fulfill this specification. I find this strange and wonder if I have misunderstood something.The text was updated successfully, but these errors were encountered: