Code Monkey home page Code Monkey logo

Comments (52)

puffnfresh avatar puffnfresh commented on May 20, 2024 1

The underlying laws know nothing of usefulness...

This statement seems misleading. These laws allow useful abstraction and refactoring. For example, it's extremely useful to know that we can always rewrite a.map(f).map(g) to a.map(compose(g, f)). The laws also allow us to derive functions, which we can reuse.

Always being able to do refactorings and derive these functions requires Just(null) to be possible. I'd say Just(null) is very useful, through transitivity of the laws.

from fantasy-land.

davidchambers avatar davidchambers commented on May 20, 2024 1

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 Just(null). @joneshf addressed this question directly. But the more important point is the one made by @puffnfresh above which as that for the laws to be useful at all there can be no exceptions, so Just(null) must be a value regardless of whether we deem it useful.

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

That's because you're Maybe.of is wrong. It should only return Just and not a possibility of both. Checking for null isn't covered in the specification, because this is in a fantasy-land and not to mention the million dollar mistake.

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 Maybe may I suggest Maybe.from (or similar named). There for your of can follow the correct specification.

Maybe.from = function(x) {
    return x == null ? new Nothing() : new Just(x);
}

from fantasy-land.

TheLudd avatar TheLudd commented on May 20, 2024

@SimonRichardson Is the first implementation of Maybe also wrong?

Perhaps I don't understand the use case for of. If all it does is wrap the value in a Just why wrap it at all?
I'd be grateful for an explanation of how/when to use of.

from fantasy-land.

michaelficarra avatar michaelficarra commented on May 20, 2024

Wrapping puts the value in a context so that you know which map to apply.

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

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 (of and map respectfully).

The point of of is to give you a pure value. The of function will always evaluate the same result value given the same argument value. Which could or could not be the case with of also return Nothing.

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

@TheLudd it's useful for abstraction. With of, we can derive map:

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!

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

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)

from fantasy-land.

buzzdecafe avatar buzzdecafe commented on May 20, 2024

unless i'm mistaken then, a Maybe constructor doesn't make sense as something that can be called directly. It's always either Just | Nothing, much as an Either is Left | Right -- have I got that right?

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

@buzzdecafe yep, that's right!

from fantasy-land.

TheLudd avatar TheLudd commented on May 20, 2024

@puffnfresh @buzzdecafe
Just to clarify further:
The decision to wrap a value in either a Just or a Nothing must not be made in any of the functions specified in the fantasy land specification. It needs to be in a separate function, unique for Maybe.

Is this correct?

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

Is the first implementation of Maybe also wrong?

I'd think it is wrong for the traditional semantics of Maybe. How do you represent a value: Just(null)? From the implementation, I'm not sure if you can.

It could still be a valid data type, or these abstractions could still apply. It just has different meaning from the traditional Maybe.

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

Just to clarify further:
The decision to wrap a value in either a Just or a Nothing must not be made in any of the functions specified in the fantasy land specification. It needs to be in a separate function, unique for Maybe.

Is this correct?

I don't believe that's true. Look at ap. The outcome of it depends entirely on what the inputs are. The semantics of Maybe come through in the some of the functions, but not all. It just depends on each function.

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

@TheLudd to implement map, you need to know whether the input is a Just or a Nothing. There are some functions which are meant to be universally quantified, but that's pretty hard to talk about when dealing with JavaScript.

An easy example of this quantification is Maybe's of. In type theory it be instantiated to:

Maybe.of : forall a. a => Maybe<a>

The forall is really important. It really means for ALL. It can not have any knowledge about the value. There are no operations it should be able to use. Not comparisons, not toString, nothing.

Please let me know if you know a way we can more easily talk about this.

from fantasy-land.

buzzdecafe avatar buzzdecafe commented on May 20, 2024

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

from fantasy-land.

CrossEye avatar CrossEye commented on May 20, 2024

I'm wondering if I"m missing something basic here. Maybe is a type that wraps up the need for null/empty checking at multiple steps of a process. In the strongly-typed Haskell, this means it must be implemented with the Just(a)/Nothing semantics. But those could be thought of more as implementation details than the key idea, no?

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 Just and Nothing. Javascript is dynamically typed, and already has a mechanism for reporting missing values: the null value. So this implementation used that as its null/empty value, and didn't try to mimic Haskell's stricter types.

In other words, this implementation would not allow you to do any non-trivial algebra on Maybe(null), in the same way you can do no non-trivial algebra on Nothing. Is this really far off the mark?

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

Yes, null is a value. If we're going to take the idea of parametricity idea seriously (i.e. forall really means for ALL) then we must be able to represent Just(null).

The Fantasy Land spec is an attempt to take the idea of parametricity seriously. Some tooling to help ensure parametricity would be awesome.

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

I'm wondering if I"m missing something basic here. Maybe is a type that wraps up the need for null/empty checking at multiple steps of a process. In the strongly-typed Haskell, this means it must be implemented with the Just(a)/Nothing semantics. But those could be thought of more as implementation details than the key idea, no?

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 Maybe a as the type of a computation that might fail. If a computation succeeds you get a Just a. If a computation fails you get a Nothing. In this light, would you view Just null the same as Nothing. The former states, "The computation succeeded, the value of that computation was null." The latter states, "The computation failed." These two values have different meanings. Similarly an empty list: [] and a singleton list with null: [null] have different meanings.

from fantasy-land.

CrossEye avatar CrossEye commented on May 20, 2024

The semantics of the data type are encoded in those two values. You might also think of Maybe a as the type of a computation that might fail.

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 null. While we can say "null is a value" if we're trying to define a rigorous type system for JS, we must also recognize that its semantics are specifically that of reporting that the reference/return in question in fact has no value.

I guess my question would be how one would expect Just(null) to behave differently than Nothing. We can assume, I would imagine, that

lift(add)(Just(3), Just(5)); //=> Just(8)
lift(add)(Just(3), Nothing); //=> Nothing

But would not

lift(add)(Just(3), Just(null)); //=> Just(null)

or would you expect to define some new value for that (Just(NaN))?

That it seems to me to act exactly as Nothing should makes me think that they should be one and the same, and hence that we can get away with not defining the subtypes in JS. But, as I said, I'm still new to this.

from fantasy-land.

michaelficarra avatar michaelficarra commented on May 20, 2024

@CrossEye lift(add)(Just(3), Just(null)) is Just(3) because in JS, 3 + null is 3.

from fantasy-land.

CrossEye avatar CrossEye commented on May 20, 2024

@michaelficarra: Yes, unless you buy my argument that null is the signal for "computation failed". 😄

from fantasy-land.

michaelficarra avatar michaelficarra commented on May 20, 2024

I don't. 😏

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

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 null. While we can say "null is a value" if we're trying to define a rigorous type system for JS, we must also recognize that its semantics are specifically that of reporting that the reference/return in question in fact has no value.

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 Maybe (Maybe a). What possible values can be returned here:

  1. Just (Just a) The inner computation succeeded with a value, and the outer computation succeeded as well.
  2. Just Nothing The inner computation failed, but the outer computation succeeded.
  3. Nothing The outer computation failed.

If you assume Just null is equivalent to Nothing how would you distinguish between case 1 and 2 when the first case returns a Just (Just null)?

Suggesting that Just null is equivalent to Nothing is the same as suggesting that [null] is equivalent to []. That's jquery-esque logic and leads to bugs. If the singleton list example is still too wishy washy, how about [null, null, null]? This could be seen as saying, you had three computations and they all failed. This is hopefully not seen as equivalent to [], which can be seen as saying, you had no computations. If you distinguish these cases for lists, you should distinguish them for Maybe.

Maybe a is like a data type representing two possible values:

  1. The empty list: [] is equivalent to Nothing
  2. A singleton list: [a] is equivalent to Just a, for all values a.

from fantasy-land.

CrossEye avatar CrossEye commented on May 20, 2024

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.

R.head([]); //=> null

(Actually it's undefined; I was a bit confused, but let's ignore that issue for the moment.)

This is our failure case. We can't get the head of an empty list. we signal that issue with null/undefined. My goal was in introducing the algebraic types to make them a layer atop the existing code. But if somehow I really need to distinguish between head([]) and head([undefined]) in order to have a reasonable version of Maybe, then I can't build it on top of the existing code, and integrating these types is going to become a lot more intrusive.

That's a real bummer.

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

Well, as mentioned before, it might not fit the semantics of the conventional Maybe a data type, but what you have might still be a useful data type just with slightly different semantics.

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 Functor, then see if it satisfies the laws. If so, you're golden and you can use the other things derivable from that. If it can't properly implement Applicative, but it can implement Apply, then good on you! Not every data type can implement all of these abstractions lawfully. Just go with the ones you can, so that others can use them.

from fantasy-land.

TheLudd avatar TheLudd commented on May 20, 2024

@puffnfresh Regarding this rule:

Maybe.of : forall a. a => Maybe<a>

Is it ok to return a Just<a> since Just is a subtype of Maybe? (correct?)
But Nothing is not ok because even though it is a subtypte of Maybe (correct?), what would be returned would be a Nothing and not a Nothing<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 of had the possibility of returning a Nothing?

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

chain, map (etc) would break as it won't not satisfy the laws.

from fantasy-land.

buzzdecafe avatar buzzdecafe commented on May 20, 2024

The only thing I will miss is:

compose(map(f_2), Maybe, f_1)

... since I won't be able to call Maybe directly. Instead I'll have to do e.g.:

compose(map(f_2), Maybe.from, f_1)

which is a little less sweet.

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

That's looks really nice to me!

from fantasy-land.

CrossEye avatar CrossEye commented on May 20, 2024

@buzzdecafe If I understand what's being said here though, since R.head([]) returns the same as R.head([undefined]), this sort of composition wouldn't work for f_1 = R.head, regardless of whether we used the Maybe constructor or Maybe.from. We would have to rewrite R.head to return a Maybe(a). That's the path I don't want to go down, as I want the types as an optional extension to Ramda, not a core feature. And the same would be true anywhere else we would want a Maybe return.

from fantasy-land.

buzzdecafe avatar buzzdecafe commented on May 20, 2024

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.

from fantasy-land.

TheLudd avatar TheLudd commented on May 20, 2024

@SimonRichardson

chain, map (etc) would break as it won't not satisfy the laws.

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 Maybe were to do the null check in the of function.

from fantasy-land.

CrossEye avatar CrossEye commented on May 20, 2024
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

If I've understood what's been said here, especially the comment from Hardy Jones (#85 (comment)), then that last should actually return Just(Nothing), or Just(undefined), but definitely not simply Nothing.

If I'm still missing the point, that's fine. And of course, this Maybe is our own type. We do want it to follow the FantasyLand laws, but it doesn't have to follow the exact Haskell semantics. This is a different language. What I'm afraid is that by conflating [] and [undefined], we're breaking at least one of the laws.

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

I was hoping for an example (a certain value put into a Maybe) that would break this consistency if Maybe were to do the null check in the of function.

The laws for Applicative depend on the definition of ap. So the answer depends on how that function is implemented. Assuming this definition of ap: https://github.com/ramda/ramda-fantasy/blob/master/src/Maybe.js#L19-L24 then homomorphism for one fails.

Give some function like this:

function foo(x) {
  if (x == null) {
    return 37;
  } else {
    return x;
  }
}

And the homomorphism law should hold.

Maybe.of(foo).ap(Maybe.of(null))
  // Evaluating the functions.
  // Forgive the shoddy syntax to represent a constructed `Maybe` value
  == Maybe({value: foo}).ap(Maybe.of(null))
  == Maybe({value: foo}).ap(Maybe({value: null}))
  // Use the definition of `ap`.
  == Maybe({value: null}).map(Maybe({value: foo}))
  // Use the definition of `map`.
  == Maybe({value: null})

Maybe.of(foo(null))
  == Maybe.of(37)
  == Maybe({value: 37})

These two values are not the same, but they should be. The interchange law also fails to hold.

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

If I've understood what's been said here, especially the comment from Hardy Jones (#85 (comment)), then that last should actually return Just(Nothing), or Just(undefined), but definitely not simply Nothing.

Yes, it should return a Just(undefined) as undefined is not the same as Nothing. Otherwise the homomorphism and interchange laws break.

If I'm still missing the point, that's fine. And of course, this Maybe is our own type. We do want it to follow the FantasyLand laws, but it doesn't have to follow the exact Haskell semantics. This is a different language. What I'm afraid is that by conflating [] and [undefined], we're breaking at least one of the laws.

I think you've hit the nail on the head. You don't have to implement the exact Maybe semantics. There are plenty of libraries that already do that. You've got a data type that works for what you need it for. It seems like it implements Functor lawfully, it might implement Apply lawfully as well (I didn't check), but it doesn't lawfully implement Applicative. This isn't bad, it's just a different data type with different semantics.

from fantasy-land.

TheLudd avatar TheLudd commented on May 20, 2024

@joneshf I am still confused. Why doesn't ramdas Maybe implement Applicative lawfully?
It returns a value of the same Applicative and doesn't check the input.

Maybe.of = function(x) {
    return new Maybe(x);
};

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

@TheLudd of looks fine, map and chain are very broken:

Maybe.of(null).map(function(a) { return 1; })
Maybe.of(null).chain(function(a) { return Maybe.of(1); })

Both expressions should result in Just(1).

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

It doesn't lawfully implement Applicative because it breaks homomorphism and interchange.

You can think of the specification as being another law. So Applicative has four "laws"

  1. a.of(b) must provide a value of the same Applicative and no parts of b should be checked. (type)
  2. a.of(function(a) { return a; }).ap(v) is equivalent to v (identity)
  3. a.of(f).ap(a.of(x)) is equivalent to a.of(f(x)) (homomorphism)
  4. u.ap(a.of(y)) is equivalent to a.of(function(f) { return f(y); }).ap(u) (interchange)

So it holds for the type and identity laws, but fails for homomorphism and interchange laws. And all laws must hold in order to be lawful.

This happens, even though the Functor and Apply instances are lawful.

EDIT: Nope, this data type is not even a Functor

// `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 Apply. This is why the semantics of Maybe/Option from ML languages separates out the two values.

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

@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 Maybe.of(null). The second will give you Maybe.of(10).

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

@TheLudd of looks fine, map and chain are very broken:

Maybe.of(null).map(function(a) { return 1; })
Maybe.of(null).chain(function(a) { return Maybe.of(1); })
Both expressions should result in Just(1).

These examples look funky because of the semantics of the data type and because it's not an Applicative. It is a Chain, interestingly.

To construct the intended values you'd have to already have the nested Maybe. Something like:

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})

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

@joneshf functor composition is absolutely broken.

Oh, right. I was looking at too simple of a case. Yeah, I guess this whole data type is not even a Functor.

from fantasy-land.

TheLudd avatar TheLudd commented on May 20, 2024

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 Just wrapping a null is good for. Or what a Maybe that applies the map function to a null value is good for. But I am new at this and I am sure I'll learn along the way.

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

There probably isn't a good reason for a Just(null), but to get the laws to work for JavaScript, it has to be like that.

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 Maybe.from and this does give you what you want.

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

Really good discussion as well, thanks

from fantasy-land.

davidchambers avatar davidchambers commented on May 20, 2024

I think that my confusion is due to the fact that I don't understand what a Just wrapping a null is good for.

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?

from fantasy-land.

joneshf avatar joneshf commented on May 20, 2024

I think that my confusion is due to the fact that I don't understand what a Just wrapping a null is good for. Or what a Maybe that applies the map function to a null value is good for. But I am new at this and I am sure I'll learn along the way.

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 Just(null) is equivalent to Nothing, then you should also say Just(Just(null)) is equivalent to Nothing, and so on. But if someone is working with a data type that is nested like this, they might want to handle these as separate cases.

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 and evaluate

read :: String -> Maybe Program
evaluate :: Program -> Maybe a

where you might imagine that Program is something like this:

function Program() {}

function Null() {}
Null.prototype = new Program()

function String(s) { this.value = s; }
String.prototype = new Program()
... 

What semantics can read have? Well, if you try to read in a String, and it's not syntactically valid, it should return Nothing. If you read in something and it's syntactically valid, it should return Just(Program) for whatever program you read in.

What semantics can evaluate have? If you try to run a Program and it's not a semantically valid program, it should return Nothing. If you try to run a valid Program it should return Just(a) for whatever Program evaluates to.

You might compose these together with Chain:

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 (print) you should be able to print that value and continue on to loop no problem. This seems like acceptable semantics.

However, if you view Just(null) as equivalent to Nothing, then you don't print the value that was perfectly valid. You instead print an error. This does not seem like acceptable semantics. There are ways around it, but then each function becomes way more complex than it actually need to be.

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.

from fantasy-land.

5outh avatar 5outh commented on May 20, 2024

It may be worth noting that a close analogue of Just(null) is Just(Nothing), and Just(Nothing) is certainly different than Nothing!

from fantasy-land.

davidchambers avatar davidchambers commented on May 20, 2024

If you'd like to review the pull request above, please do so. 😀

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

fyi: scalaz has a similarly to Maybe.from

from fantasy-land.

puffnfresh avatar puffnfresh commented on May 20, 2024

Note that from is already taken by comonad: https://github.com/fantasyland/fantasy-land#from-method (and Maybe can not have a comonad)

from fantasy-land.

SimonRichardson avatar SimonRichardson commented on May 20, 2024

Good spot!
Maybe.fromNullable it is :(

from fantasy-land.

robotlolita avatar robotlolita commented on May 20, 2024

@TheLudd

Maybe.of : forall a. a => Maybe<a>

Is it ok to return a Just since Just is a subtype of Maybe? (correct?)
But Nothing is not ok because even though it is a subtypte of Maybe (correct?), what would be returned > would be a Nothing and not a Nothing?

Just and Nothing aren't really subtypes, they're just tags. The type is always Maybe<a>. It helps if you view types as the following logical relationship:

Maybe(a) = Just(a) ∨ Nothing

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 a maintains the same shape). From this type alone, the following implementation of Maybe.of is completely valid (as in, it type checks -- you can try it in Haskell for example):

Maybe.of :: forall a. a -> Maybe<a>
Maybe.of = function(a){ return Nothing }

What doesn't let you implement Maybe.of like that are the laws surrounding these algebras. For example, the Identity law for applicatives states:

a.of(function(a) { return a; }).ap(v)

So, if you take the implementation of Maybe.of above, that law is most likely broken:

Maybe.of(function(a){ return a }).ap(v) = v
= Nothing.ap(v) = v
= Nothing = v
= false

from fantasy-land.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.