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

Foldable / Traversable #68

Merged
merged 12 commits into from
May 5, 2015
Merged

Foldable / Traversable #68

merged 12 commits into from
May 5, 2015

Conversation

DrBoolean
Copy link

Not sure if everything is how we want it yet, but thought i'd create this pull request to make it easier to see the changes.

@SimonRichardson
Copy link
Member

Works for me 👍

@joneshf
Copy link
Member

joneshf commented May 31, 2014

I've got some questions, for clarity.

Why is Monoid a superclass of Foldable?

If it's for the toArray method, that uses Array's instance of Monoid not the Foldable's.
If it's for the reduce method, isn't that what the second argument is for? I'm imagining that you want a default value for reduce, but isn't that why you stated u.reduce(f) == u.toArray().reduce(f)?

Or is there some other reason?

the Foldable and Functor specficiations.

1. `t(u.traverse(f, of))` is equivalent to `u.traverse(function(y){ return t(f(y)) }, of)`
where `t :: (Applicative f, Applicative g) => f a -> g a` (naturality)
Copy link
Member

Choose a reason for hiding this comment

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

We might want to stick with non-haskell-esque type signatures.

Maybe just state:

t is a function that takes an Applicative of some type and returns an Applicative for another (possibly the same) type.

Although, this is still more descriptive than what the other algebras say 👍

EDIT: Or maybe, we should keep this as an improvement. Yeah, I think having both would be best.

Copy link
Author

Choose a reason for hiding this comment

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

What if we say:

where t is a natural transformation

I believe that's all we need to express in the law.

I 100% agree that Hindley-Milner is probably not the best way to express this to javascript developers coming from all different backgrounds. (scala/f#/haskell/clojure)

@joneshf
Copy link
Member

joneshf commented May 31, 2014

Also, thanks tremendously for taking the time to make this! 👍

@CrossEye
Copy link

I've mostly been lurking here, but I'm confused by the use of Compose here, especially as a constructor function. I understand functional composition, but new Compose(f(x).map(g)) is unclear to me. Nor is it at all clear if that's something that's supposed to have been previously defined in the specification. If so, I'm just missing it. If not, what are we supposed to assume about it?

@joneshf
Copy link
Member

joneshf commented May 31, 2014

Yeah, Compose is supposed to have already been defined somewhere. I've never liked the use of Compose/Identity/etc in the documentation of laws.

Anyway, it's usually got a haskell definition like so:

newtype Compose f g x = Compose (f (g x))

So the kinds of f and g should both be * -> *.

What this means is you'd use it like: Compose [[2],[1,2,3]] or Compose (Just ["hi"]) or something similar.

In js you'd probably do something like:

var Compose = function(x) {
    this.val = x;
];
Compose.prototype.map = function(f) {
    return new Compose(this.val.map(function(y) { return y.map(f); }));
};
...

Does that help?

@CrossEye
Copy link

Thanks. Since I first posted, I've been able to mostly work out what Compose must do, but it's nice to have a simple explanation.

My bigger concern is having this undefined term in the specification.

I'm in the process of trying to implement some of the standard Functors and Monads in Ramda and would like to see if we can follow the fantasy-land spec. Having the spec grow while we're at it might be a bit disheartening, although I'm not sure it will really affect us. But mostly I'm concerned about having the specification depend on such an undefined term.

@DrBoolean
Copy link
Author

Hi @joneshf

Glad to help and I appreciate the discussions on this. I'm very excited to try to get these classes into the spec (they're working on getting these into base for haskell as well).

So it turns out, I got a bit confused about the monoid superclass. I saw the graphic from typeclassopedia: http://www.haskell.org/haskellwiki/File:Typeclassopedia-diagram.png
and the code from Foldable in haskell:

  fold :: Monoid m => t m -> m
  fold = foldMap id

  foldMap :: Monoid m => (a -> m) -> t a -> m
  foldMap f = foldr (mappend . f) mempty

But it turns out the monoid constraint is not on the foldable structure, but instead the possible monoid inside. If it doesn't have a monoid inside on a call to fold it simply raises an error.

So I can update the spec (and graphic) to remove the monoid superclass as it's unnecessary.

@DrBoolean
Copy link
Author

@CrossEye I dislike these random objects as well. I was simply porting it from the previous haskell work.

As you've already figured out Compose is just functor composition so you can fmap/ap/traverse once:

fmap(fmap(f), Some([x]))

vs

fmap(f, Compose(Some([x]))

Without the Compose it's just:
u.traverse(function(x){ return f(x).map(g); }, of) is equivalent to u.traverse(f, of).map(function(v){ return v.traverse(g, of) })

Which is indeed equivalent! Correct me if I'm wrong, but I believe pure is the motivation in haskell. The Compose definition is most likely to lift the value in the correct nested context using pure. In our case, we're forced to pass of (pure) in so we don't need to define an extra wrapper type that knows how to lift values into it's context.

Incidentally, I've come to realize that mismatching of is the cause of most of my errors when using traversable. I originally wanted to try to extract the of off the prototype of the result, but that doesn't work for failure and now I have a second reason - no need for Compose

I don't mind Identity, however, since it's the main example bundled with the spec.

I'll make these changes now if we're down.

@CrossEye
Copy link

CrossEye commented Jun 1, 2014

@DrBoolean:

Correct me if I'm wrong, but I believe pure is the motivation in haskell.

I couldn't possibly correct you on this. My Haskell experience is confined to a single reading of a beginners' book. (LYAH). I've learned a fair bit about what it does, a reasonable amount about how it works. I can mostly figure out Haskell code when I see it, but I have definitely not internalized the ideas.

I don't mind Identity, however, since it's the main example bundled with the spec.

Even here, I don't think I agree, although it's less of an issue than Compose. I would rather the spec holds together entirely as a spec, without ever bringing in such implementation details. Also the one @puffnfresh included is called Id, not Identity.

I'll make these changes now if we're down.

I have no objections; I'm still too new to this stuff. I was hoping that this was a pretty well finished spec when I started working on my implementations after the Hardcore FP class you taught a few weeks ago. Alas...

@DrBoolean
Copy link
Author

@CrossEye

I hear you on the concerns.

As far as changes go, I wouldn't be worried about the spec evolving. The previous algebra's don't have any reason to change and new ones will just build on the behavior.

Foldable and Traversable are newer, but they have been solid for years and proven so useful they've become part of every day idiomatic code. It's just this initial definition in js that's a little tricky since it's not a 1:1 port from scala or the haskell.

I think it's great that we can build on the spec by adding new algebras that work with the previous ironclad ones. What I'm trying to say is that Ramda can use the spec and shouldn't be in any danger of having to redo work.

For Id, I'd love to be able to use it in the spec as it provides a neutral type for laws and derivations. I'm not sure what the best approach is there because I do agree with the drawbacks, but I don't believe it's possible to write the identity law for traverse without Id.

Derivations are another matter though.

For instance, Functor can be derived from Traversable with the help of Id:

function(f){ return this.traverse(function(x){ return new Id(f(x)) }, Id.of).value; }

Foldable can also be derived from Traversable in a similar fashion with Const and Endo.

var foldMap = function(f, of) {
  return this.traverse(function(x){ return new Const(f(x)) }, function(x){ return Const.of(of(x)) }).value;
}

t.prototype.reduce = function(f, acc) {
  return this.foldMap(function(x){
  return new Endo(function(y){ return f(y,x); })
  }, Endo.of).value(acc);
}

Const uses the monoid instance of the value inside to implement ap and of as concat and empty respectively. Endo is function composition in monoid form. (e.g. f.concat(g) == f(g(x)))

So if someone can define traverse they get map and reduce for free. "It's the essence of that iteration y'all"! - J Gibbons.

That's pretty ridiculous for the spec, I realize. But this is fantasy land - land of the free. Land where everything plugs together generically. Satisfaction guaranteed.

Perhaps there can be a derivation library that just extends the prototypes for us. At that point we wouldn't need to add all that to the specs.

That doesn't solve the laws issue though. I don't know how to get around the fact that I need a neutral applicative functor to express the identity law.

@joneshf
Copy link
Member

joneshf commented Jun 16, 2014

Well, here's an idea.

To go on @phadej's comment, it would be nice to have some reason to depend on the spec. For these types that are simple enough, and also used in the laws, why not put them into the implementation.js file?

So, we'd have an actual reason to have this spec as a dependency, and we'd have a reason to actually start versioning it properly. Not to mention, we could have these laws where data types like Id and Compose are used without feeling like we're putting an extra burden on implementors.

In other news, I think this PR should be accepted. Would be great to have Foldable and Traversable in here.

@DrBoolean
Copy link
Author

I think that's a terrific idea. I can put Endo and Const in implementation.js too if anyone cares to derive foldable from traversable.

I was thrilled to wake up this morning and find I'd been added as an owner. I feel odd about merging my own pull request so I'll hold off on that, but Yippee!

Can anyone help with that graphic as an svg? Not sure how the old one was created...

@CrossEye
Copy link

Can anyone help with that graphic as an svg? Not sure how the old one was created...

I have no experience with it, but that seems to be a MetaPost document. There is a previewer online, and I can use it to regenerate the SVG and PNG, and I imagine it wouldn't be too hard to reverse-engineer the format (figures/dependencies.mp) But I also don't think that keeping with MetaPost is that important, unless someone who knows it is still around...

@DrBoolean
Copy link
Author

Thinking about the dependency on the spec issue... Perhaps it could be a dev dependency for quickchecking compliance. The spec would include Id and a quickcheck test suite with something like https://github.com/folktale/laws (or just expand laws to check everything). Then a small test script that's setup to take a type and the array of algebra's it implements.

Just thinking out loud, but I believe the dev dependency might be the way to go.

@joneshf
Copy link
Member

joneshf commented Jul 28, 2014

I was thinking of something similar with the quickcheck, though using purescript.

There was a library that did something similar with the laws a while back, can't remember what it was at the moment though.

@DrBoolean
Copy link
Author

That would solve the dep issue and the Id issue in 1 go. I'd certainly "use the spec" in apps from here on out. I'll create a pull request for it to get the discussion in it's proper place

@davidchambers
Copy link
Member

What's the status of this pull request?

@DrBoolean
Copy link
Author

I'm down to merge if others are. I could also break this into just Foldable. Is it blocking ramda?

@buzzdecafe
Copy link
Contributor

it ain't blocking me. it would be nice to have for ramda-fantasy

@5outh
Copy link
Member

5outh commented May 5, 2015

I'm happy with this, 👍 for merge from me

@joneshf
Copy link
Member

joneshf commented May 5, 2015

You had my vote last year ;).

@DrBoolean
Copy link
Author

Right then let's do this!

DrBoolean pushed a commit that referenced this pull request May 5, 2015
@DrBoolean DrBoolean merged commit acc52e1 into fantasyland:master May 5, 2015
@davidchambers
Copy link
Member

🎉

@CrossEye
Copy link

CrossEye commented May 9, 2015

🍰


1. `x` is the initial accumulator value for the reduction

### Traversable

Choose a reason for hiding this comment

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

@DrBoolean May I have an example of Traversable and Sequence? Pretty pls?

Copy link
Author

Choose a reason for hiding this comment

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

Here's a gist of the laws: https://gist.github.com/DrBoolean/c4949a0f4b8f9ba8261f

A quick example would be:

[Promise.of(1), Promise.of(2)].sequence(Promise.of)
//=> Promise([1,2])

It turns [Promise] to Promise []

Choose a reason for hiding this comment

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

Oh cool. Thanks sir.
Going over the gist now.

@joneshf joneshf mentioned this pull request Jun 16, 2015
@joneshf joneshf mentioned this pull request Feb 26, 2016
@rpominov rpominov mentioned this pull request Apr 12, 2016
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.

8 participants