-
-
Notifications
You must be signed in to change notification settings - Fork 95
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
maybe: implement Semigroup and Monoid #11
Conversation
👍 |
maybe: implement Semigroup and Monoid
Just.prototype.concat = function(maybe) { | ||
return maybe instanceof Just ? Just(this.value.concat(maybe.value)) : this; | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you somehow expecting to enforce a stronger type than JS does for your Maybe
s? Do you have somehow a Maybe(String)
type different from a Maybe([Number])
?
If not, I don't understand your concat
. How do you know that you can concat the value
of this Maybe
with the value of that Maybe
? What happens with Maybe.of([1, 2, 3]).concat(Maybe.of("abc"));
or vice versa?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you know that you can concat the
value
of thisMaybe
with the value of thatMaybe
?
We don't, but according to the spec…
s.concat(b)
b
must be a value of the same Semigroup- If
b
is not the same semigroup, behaviour ofconcat
is unspecified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But are you quoting that in regards to some inner Semigroup or to the Maybe
-wrapped one? Are you trying to define types such as
Maybe (String);
// Maybe('ab').concat (Maybe('cd')); //=> Maybe('abcd')
Maybe (List)
// Maybe([1, 2]).concat (Maybe(['foo'])); //=> Maybe([1, 2, 'foo'])
Maybe (Add)
// Maybe(Add (3)).concat(Maybe(Add (4))): //=> Maybe(Add (7))
Maybe (Multiply)
// Maybe(Multiply(3)).concat (Maybe(Multiply(4))): // Maybe(Multiply(12))
It seems that is what you would want if you are doing this internal concat
. But I see nothing in the implementation that attempts to enforce this sort of stronger typing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Semigroup
here is Maybe a
, so if someone attempts to concat
with anything that is not Maybe a
(the a
must be the same as well), then it is unspecified what happens. There is nothing specifying that the a
is also a Semigroup
in the implementation, but I'm not sure what you'd do there either.
How should that case be handled? It will already throw a runtime error if concat
doesn't exist, what else could you do? More descriptive error possibly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(the
a
must be the same as well)
That's what I was getting at. I see nothing in the implementation to enforce this. Is there meant to be?
There is nothing specifying that the
a
is also a Semigroup in the implementation
I think you would need to enforce that for this to make any sense, or at least enforce a rule that the values have a concat
function. The question is whether you try to enforce something more stringent, namely that the a
's are of the same Semigroup
. If you do so, you get a useful Semigroup
and with either of several possible extensions, you can get useful Monoid
s out of it, as discussed on Gitter. But I don't see anything here that enforces it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are plenty of ways to shoot yourself in the foot with js. Is there a reason
Semigroup
/Monoid
should be held to some stronger requirements?
No, but I was hoping that Maybe
could be. My reason for adding Maybe
to a system that otherwise doesn't have one is to prevent having to do all sorts of error-checking across the system, or, in failing to do that, allowing for the possibility of exceptions arising. So I want my Maybe
to be as bullet-proof as possible.
But I finally went back and looked at the filter
implementation involved, and perhaps I've been confused by the discussion. It looks as though filter
has nothing to do with Semigroups
or Monoids
. There seems to be what might be seen to be a pun on empty
here. There seems to be nothing in the definition of filter
, explicit or implied, that requires empty
be a lawful Monoid identity. filter
only requires that such a method exists (and presumably requires that it returns a value of the same Maybe
.) Moreover all my concerns above had to do with the definition of concat
, and the fact that it could easily throw unexpected exceptions. But that's entirely irrelevant, since the definition of filter
being discussed has nothing at all to do with the Maybe
being a Semigroup
or a Monoid
.
So my take again, would be not to add the Semigroup
or Monoid
without good reason, but since they wouldn't actually get invoked during filter
, I'm less bothered by them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but I was hoping that Maybe could be. My reason for adding Maybe to a system that otherwise doesn't have one is to prevent having to do all sorts of error-checking across the system, or, in failing to do that, allowing for the possibility of exceptions arising. So I want my Maybe to be as bullet-proof as possible.
That's an admirable goal. I think the only way to really make that happen is to stop writing vanilla js, or use a different abstraction. Maybe
is too low an abstraction to protect you from all the possible ways things can go wrong. That doesn't mean Maybe
is worthless, just that it's not up to the task for js. Different languages can use it more powerfully than js though, for the exact reasons you pointed out above. If you have a computer checking your program, you can ensure that you only call concat
on the right thing. In js, you can't have that.
But I finally went back and looked at the filter implementation involved, and perhaps I've been confused by the discussion. It looks as though filter has nothing to do with Semigroups or Monoids. There seems to be what might be seen to be a pun on empty here. There seems to be nothing in the definition of filter, explicit or implied, that requires empty be a lawful Monoid identity. filter only requires that such a method exists (and presumably requires that it returns a value of the same Maybe.) Moreover all my concerns above had to do with the definition of concat, and the fact that it could easily throw unexpected exceptions. But that's entirely irrelevant, since the definition of filter being discussed has nothing at all to do with the Maybe being a Semigroup or a Monoid.
This all sprung from my suggestion here: #10 (comment) When I said, "once and for all", I truly meant "once and for all". There shouldn't need to be an implementation of filter
on each data type. If you have a data type that is a Monad
and a Monoid
, that interact properly, you should be able to use the a single filter implementation:
var filter = function(p, m) {
return m.chain(function(x) {
return p(x) ? m.of(x) : m.empty();
});
}
That method doesn't mention Maybe a
or [a]
, or any other data type. It talks only about Monad
and Monoid
. That implementation is the power of talking about these abstract concepts. We needn't think about, "Does it make sense to filter
this data type?" or, "How do I implement a filter
on this data type?" No, we only need to be concerned with some questions. does this data type implement Monad
? Does this data type implement Monoid
? Do they interact properly? If the answer to all of those questions is yes, then we get filter
for free. You stick that implementation in some generalized library somewhere, and use it on your data type without even thinking about it.
But what does it mean to interact properly? There's some discussion here: purescript/purescript-control#6 (comment), but the idea is that it should satisfy some laws:
- Left distributivity with
Functor
:
(x.concat(y)).map(f) == x.map(f).concat(y.map(f))
- Right annihilation with
Functor
M.empty().map(f) = M.empty()
- Right distributivity with
Apply
:
(x.concat(y)).ap(z) == x.ap(z).concat(y.ap(z))
- Right annihilation with
Apply
M.empty().ap(z) = M.empty()
- Right distributivity with
Chain
:
(x.concat(y)).chain(f) == x.chain(f).concat(y.chain(f))
- Left annihilation with
Chain
M.empty().chain(f) = M.empty()
Since Maybe a
happens to satisfy these laws, it should get filter for free. It has nothing to do with HOW Maybe a
implements these functions. Just that they're lawful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want my Maybe to be as bullet-proof as possible.
That's an admirable goal. I think the only way to really make that happen is to stop writing vanilla js, or use a different abstraction.
Maybe
is too low an abstraction to protect you from all the possible ways things can go wrong.
I wasn't expecting it to solve all my problems, mostly to reduce null
-checking. Javascript code that's full of null
-checks can be more nicely written with the help of some judiciously-placed Maybe
s.
I did understand the idea of writing an abstract implementation that would work for any conforming types. But my reference for the ADTs in Javascript has long been FantasyLand, which makes no mention of any interaction between Semigroup
/Monoid
laws and laws of the types in Monad
's genealogy. While the laws you discuss here make a great deal of sense, this is the first I've heard of them.
And I still have significant reservations because although the filter
function does not actually use concat
, the concat
proposed for Maybe
seems designed for Monoid a => Monoid (Maybe a)
(likely just Semigroup
, but Monoid
seems more likely useful.) However I see nothing in the Sanctuary implementation that says that these Maybe
s are more strongly typed than Maybe *
. Declaring a Semigroup
/Monoid
on Maybe *
doesn't make sense to me. And yet the notion of filter
does make sense for Maybe *
, with a fairly obvious implementation:
Nothing.prototype.filter = function(predicate) {return this;}
Just.prototype.filter = function(predicate) {
return predicate(this.value) ? this : new Nothing();
}
I do understand that this loses out by not taking advantage of generic laws. But it seems one could only do so by pushing more of an external type system into Javascript.
Am I making sense here? Or is my lack of experience with languages like Haskell still making me miss something fundamental? I do have experience with Monoids, Semigroups, etc., but from a mathematical, not a programming point of view. I did take one course in Category Theory in grad school; it was my least favourite course and helped steer me to more discrete fields; my work was in combinatorics and graph theory. I really remember nothing of Monads or some of these other types. And I have less feel for them than I do for the sorts of types that make up an abstract algebra course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't expecting it to solve all my problems, mostly to reduce null-checking. Javascript code that's full of null-checks can be more nicely written with the help of some judiciously-placed Maybes.
Sorry, I think my remark was a little snarky. That wasn't my intention.
You're making perfect sense. My qualm is just that the other functions don't do any checking, but you want concat
to do a bunch. I don't understand why concat
needs to be special. map
doesn't ensure that its argument is a function, ap
doesn't ensure that the Just
holds a function or that its argument is a Maybe a
, chain
doesn't ensure that the argument is a function. And none of them check to ensure that the return value is the proper type. Why should concat
check that it holds a value that is a Semigroup a
, and that its argument is a Maybe
holding the same Semigroup a
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I think my remark was a little snarky. That wasn't my intention.
I didn't sense snark. I sensed us likely talking past each other. Most likely confusion, although perhaps some real fundamental disagreement too. It's hard because I have a mix of strong backgrounds in certain of the underlying topics and weak ones in others -- but strong opinions about everything, I'm afraid!
My qualm is just that the other functions don't do any checking, but you want
concat
to do a bunch. I don't understand whyconcat
needs to be special.
It's more basic than that for me. I simply don't see a reason to include concat
. I haven't seen any good argument that the sort of Maybe
so far defined should be a Semigroup
or a Monoid
. And because of the types of issues discussed, I see real issues with trying to define it that way.
But I think even further down is the notion that I suspect you don't think about Maybe
as a fully-fledged type. Am I right? Do you think in terms of Maybe a
for some other type a
? To me, the Maybe
type as so far defined by Sanctuary, is a type on its own as far as I can tell. And that might explain a lot of our disagreement. Such a type as I see of course cannot comfortably be made part of a Semigroup
. Did I get your position right? Does this help explain our differences? If so, do you want to try to convince me that Maybe
, regardless of current implementation, should only be thought of as Maybe a
?
wrap it in a |
The immediate motivation for this change is to enable
filter
to be defined for Maybe as suggested by @joneshf in #10. Implementing these specifications for Maybe is a good idea regardless.