Skip to content
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

everything #2

Merged
merged 1 commit into from
Oct 17, 2016
Merged

everything #2

merged 1 commit into from
Oct 17, 2016

Conversation

davidchambers
Copy link
Member

@davidchambers davidchambers commented May 9, 2016

This pull request defines the following:

  • Z.TypeClass :: (String, Array TypeClass, a -> Boolean) -> TypeClass
  • Z.Setoid :: TypeClass
  • Z.Semigroup :: TypeClass
  • Z.Monoid :: TypeClass
  • Z.Functor :: TypeClass
  • Z.Bifunctor :: TypeClass
  • Z.Profunctor :: TypeClass
  • Z.Apply :: TypeClass
  • Z.Applicative :: TypeClass
  • Z.Chain :: TypeClass
  • Z.ChainRec :: TypeClass
  • Z.Monad :: TypeClass
  • Z.Foldable :: TypeClass
  • Z.Traversable :: TypeClass
  • Z.Extend :: TypeClass
  • Z.Comonad :: TypeClass
  • Z.toString :: Showable a => a -> String
  • Z.equals :: (a, b) -> Boolean
  • Z.concat :: Semigroup a => (a, a) -> a
  • Z.empty :: Monoid m => TypeRep m -> m
  • Z.map :: Functor f => (a -> b, f a) -> f b
  • Z.bimap :: Bifunctor f => (a -> b, c -> d, f a c) -> f b d
  • Z.promap :: Profunctor p => (a -> b, c -> d, p b c) -> p a d
  • Z.ap :: Apply f => (f (a -> b), f a) -> f b
  • Z.of :: Applicative f => (TypeRep f, a) -> f a
  • Z.chain :: Chain m => (a -> m b, m a) -> m b
  • Z.chainRec :: ChainRec m => (TypeRep m, (a -> c, b -> c, a) -> m c, a) -> m b
  • Z.filter :: (Applicative f, Foldable f, Monoid (f a)) => (a -> Boolean, f a) -> f a
  • Z.filterM :: (Monad m, Monoid (m a)) => (a -> Boolean, m a) -> m a
  • Z.reduce :: Foldable f => ((b, a) -> b, b, f a) -> b
  • Z.traverse :: (Applicative f, Traversable t) => (a -> f a, b -> f c, t b) -> f (t c)
  • Z.sequence :: (Applicative f, Traversable t) => (a -> f a, t (f b)) -> f (t b)
  • Z.extend :: Extend w => (w a -> b, w a) -> w b
  • Z.extract :: Comonad w => w a -> a

Original description:

This pull request is not yet ready to merge, but it's well past my bedtime!

In sanctuary-js/sanctuary-def#62 @kedashoe suggested creating a repository for type classes. Having all the Fantasy Land type classes neatly defined in one place sounded great to me, so I've since spent some time making it happen.

While writing the predicates I realized we want Array to be considered a Chain (among other type classes) but Array#chain is not defined. This presents us with three options:

  • force Sanctuary to provide its own Chain type class when we define S.chain;
  • claim that Array satisfies the requirements of Chain and hope that somehow it does (because libraries such as Sanctuary add logic to handle the missing method); or
  • define chain for Array, and provide a chain function which makes use of this definition.

The last option seems best to me. We'll have all the definitions of Fantasy Land methods for built-in types in one place rather than scattered through a dozen or so files in Ramda. :)

Note that while the methods are in place, I've yet to define the functions to wrap them. We should avoid invoking methods directly so we don't invoke missing methods such as Array#chain, and to ensure we use our lawful map rather than Array#map.

@kedashoe
Copy link
Member

kedashoe commented May 9, 2016

The last option seems best to me. We'll have all the definitions of Fantasy Land methods for built-in types in one place rather than scattered through a dozen or so files in Ramda. :)

So sanctuary (or whomever) can use these implementations to help with FL compliance for built in types? For example map could be used here: https://github.com/sanctuary-js/sanctuary/pull/142/files#diff-168726dbe96b3ce427e7fedce31bb0bcR579?

of: 'of',
reduce: 'reduce',
sequence: 'sequence'
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not depend on fantasy-land to get these constants? A change to them would probably be released as a major version, and you can always do like I did; provide both the "easy" names, and the fantasy-land prefixed names.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three things need to happen before this is an option. I just opened fantasyland/fantasy-land#139 for discussion. :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a shame -- any reason why sanctuary-js projects won't delegate to bundlers like browserify?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why sanctuary-js projects won't delegate to bundlers like browserify?

To what end, @rjmk? I'm certainly open to doing so, but it's not clear to me exactly what we stand to gain. I haven't done much front-end development in the past two and a half years, so I'm a bit out of touch. 😜

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it gives control of how to bundle the code to the consumer. It also allows us to be much more flexible in which modules we use (as here). It makes the code cleaner.

It is trivial to generate standalone bundles from npm modules, and so you don't even need to be using a bundler to use an npm package on the front-end.

If we wanted to have a bower version available, we could bundle sanctuary-js projects into a dist/ folder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all sounds reasonable to me. What about AMD? I just want to be clear how all the pieces fit together (and some of the pieces in question are foreign to me).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never used AMD, but the bundles produced by wzrd, for example, are AMD compatible

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I've converted the file to CommonJS format and added fantasy-land as a dependency.

@davidchambers
Copy link
Member Author

So sanctuary (or whomever) can use these implementations to help with FL compliance for built in types?

That's right! :)

For example map could be used here: https://github.com/sanctuary-js/sanctuary/pull/142/files#diff-168726dbe96b3ce427e7fedce31bb0bcR579?

It's better than that, even. The Functor instances for Array a, StrMap a, and a -> b will be defined in this repository. STC.map will dispatch to one of these, or dispatch to a map method when applied to a value of another type. To define S.map, we'll simply use def to wrap STC.map (for currying and type checking). In other words, the logic for map will live here rather than in Sanctuary itself. The nice thing is that we can define, document, and test each Functor instance individually, whereas in Sanctuary we'd end up with a switch block or similar.

@davidchambers davidchambers force-pushed the dc-everything branch 2 times, most recently from 3f03088 to 0d3793a Compare May 11, 2016 10:13
//. | [Chain][] | ✔︎ | | | |
//. | [Monad][] | | | | |
//. | [Extend][] | | | | |
//. | [Comonad][] | | | | |
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type class Array Function Object String
Setoid
Semigroup ✔︎ ✔︎ ✔︎
Monoid ✔︎ ✔︎ ✔︎
Functor ✔︎ ✔︎ ✔︎
Foldable ✔︎
Traversable ✔︎
Apply ✔︎ ✔︎
Applicative
Chain ✔︎
Monad
Extend
Comonad

Could we check any more of these cells? I think I've implemented all the ones Ramda does, plus Object#concat (which is known as merge in Ramda).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we tick the applicative box for arrays?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@svozza
Copy link
Member

svozza commented May 11, 2016

I'm only after getting a chance to look into this now. I didn't appreciate fully what was going on but now I've read the code I really like it. Generalising the dispatching seems like it will be really powerful. This is great.


//# Object$concat :: StrMap a ~> StrMap a
methods.Object$concat = function(strMap) {
var result = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation is a bit hard to follow, does it really need to depend on Array$map? What's actually happening here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're merging two values of type StrMap a. Perhaps this implementation is clearer:

var result = [];
for (var k in this) if (hasOwnProperty_.call(this, k)) result[k] = this[k];
for (k in strMap) if (hasOwnProperty_.call(strMap, k)) result[k] = strMap[k];
return result;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I much prefer that implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@svozza
Copy link
Member

svozza commented May 12, 2016

Any reason why there's no Array$of?

@davidchambers
Copy link
Member Author

Any reason why there's no Array$of?

I had it initially, but then realized that λ.of wouldn't be very useful:

> λ.of([], 42)
[42]

> λ.of(S.Nothing(), 42)
Just(42)

> λ.of(S.Right(0), 42)
Right(42)

Certain functions, such as sequence, take of :: Applicative f => a -> f a as an argument, which is more direct than taking a value with an of method (or a value with a constructor property with a static of method). To such functions one provides R.of, S.Maybe.of, S.Either.of, or similar.


We could define S.of as the sanctified equivalent of this function.

@svozza
Copy link
Member

svozza commented May 12, 2016

But do we not need of to define Array as an Applicative?

@davidchambers
Copy link
Member Author

But do we not need of to define Array as an Applicative?

Great point, Stefano! The eq(λ.Applicative.test([]), true) assertion passed for me locally because my Node version (v4.4.3) defines Array.of. I've defined methods.Array$of to avoid this dependency. :)

@davidchambers davidchambers force-pushed the dc-everything branch 8 times, most recently from f071574 to 6160174 Compare October 15, 2016 21:31
@davidchambers
Copy link
Member Author

I've spent the day sanding the patch. I'll merge this pull request tomorrow. You're welcome to take one last look before I do so. I'm particularly interested in knowing what people think of the readme. Having thought about and worked on this patch for several months I could easily have neglected to explain something important because I take it for granted. Let me know if this is the case.

@kedashoe
Copy link
Member

I'm particularly interested in knowing what people think of the readme.

Looks good! A couple comments from someone who doesn't know much about this stuff :)

one TypeClass value for each Fantasy Land type class;

I'm not sure exactly what this means.. A TypeClass "value"? I think I understand it after I got a little farther down, but maybe there is a more generic way to phrase this at the top so novices such as myself aren't thrown off? Maybe replace "value" with "definition" or just leave the word out altogether?

one function for each Fantasy Land method;

Is this mostly a distinction between "function" and "method"? Is there actually a "Fantasy Land" instance that has methods?

  • Something that doesn't need to be done now (or ever of course), but it might be cool for the TypeClass section to have a table that shows the result of the test for every built in type?
  • The haskell-ish (I think?) examples from the function section are ok I think, although empty(List) = Nil made me pause for a bit and is maybe not necessary anyway?

@davidchambers
Copy link
Member Author

A couple comments from someone who doesn't know much about this stuff

This is the most important perspective. :)

one TypeClass value for each Fantasy Land type class;

I'm not sure exactly what this means.. A TypeClass "value"?

Let me explain the distinction I'm attempting to make, and perhaps you can suggest a better way to phrase it. I'm using type class to refer to a group of types. JavaScript doesn't have types, so it doesn't have type classes, but the idea of type classes is still useful.

I'm using TypeClass value as shorthand for value of type TypeClass. I'm referring to an actual JavaScript value which represents a particular type class.

one function for each Fantasy Land method;

Is this mostly a distinction between "function" and "method"? Is there actually a "Fantasy Land" instance that has methods?

Again, it's possible I chose my words poorly. What I'm trying to say is that FL specifies the behaviour of fantasy-land/map, say, but rather than write functor['fantasy-land/map'](f) we can write Z.map(f, functor). Not only is this convenient, but it allows functor to be [1, 2, 3] or some other value of a built-in type which could provide a fantasy-land/map method but does not.

I'm not sure what you mean by a "Fantasy Land" instance that has methods. Could you elaborate?

Something that doesn't need to be done now (or ever of course), but it might be cool for the TypeClass section to have a table that shows the result of the test for every built in type?

I do like the idea. There is one problem we'll need to solve. Let's tackle it separately. :)

The haskell-ish (I think?) examples from the function section are ok I think, although empty(List) = Nil made me pause for a bit and is maybe not necessary anyway?

I want to show that the function can operate on values of user-defined types. Does the confusion result from the fact that the List type is not described anywhere?

@davidchambers davidchambers force-pushed the dc-everything branch 3 times, most recently from 29068ae to 95695f9 Compare October 16, 2016 16:52
var Function$prototype$chain = function(f) {
var chain = this;
return function(x) { return f(chain(x))(x); };
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fortuitously, @buzzdecafe opened ramda/ramda#1936 today which alerted me to the fact that Function can satisfy the requirements of Chain. The implementation is straightforward. In fact, it's almost identical to that of Function$prototype$ap since the only difference between the two functions is that one takes (a -> b -> c) whereas the other takes (b -> a -> c).


//# toString :: a -> String
//.
//. Returns a useful string representation of its argument.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Avaq pointed out that Z.toString(Object.create(null)) evaluates to '{}', so applying String to the argument must not raise an exception was misleading. I've removed the warning and the Showable a constraint. :)

@kedashoe
Copy link
Member

kedashoe commented Oct 16, 2016

I'm using TypeClass value as shorthand for value of type TypeClass. I'm referring to an actual JavaScript value which represents a particular type class.

I like this explanation :) And the TypeClass section also helps. Kind of just a problem the order the user sees the terms. Maybe a short glossary for terms like this that "value" could link to? Either way, not something to get hung up on at this point.

Again, it's possible I chose my words poorly. What I'm trying to say is that FL...

Reading this one again I think what you have is good. The word "method" is used prominently in the FL spec, I just hadn't read it in a while.

I want to show that the function can operate on values of user-defined types. Does the confusion result from the fact that the List type is not described anywhere?

I see, I don't think I realized that (I like these examples more though knowing that!). Maybe a short sentence before these definitions to explain what List and Cons are?

$seen.push(x);
try { return method.call(x); } finally { $seen.pop(); }
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this change look okay to you, @Avaq?

@davidchambers davidchambers force-pushed the dc-everything branch 2 times, most recently from 3ba300d to 09e93ae Compare October 16, 2016 22:56
@safareli
Copy link
Member

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants