folktale / data.task

Migrating to https://github.com/origamitower/folktale
MIT License
425 stars 45 forks source link

Setoid, Semigroup, Monoid, Applicative #15

Closed DrBoolean closed 10 years ago

DrBoolean commented 10 years ago

I pulled setoid from eq-future and I'm not entirely in love with semigroup's implementation, but I find these interfaces very useful.

robotlolita commented 10 years ago

Ah, so, I've removed the equality instance before because of the partiality (https://github.com/folktale/data.future/issues/4), I want to try my hands at a Cofuture implementation using ES6 generators later so I can add these.

The Monoid and Applicative instances look good to me. What do you think is missing/wrong with the Semigroup instance?

DrBoolean commented 10 years ago

The equality makes total sense :) I can update to remove it here in a minute.

The semigroup instance was lifted from http://hackage.haskell.org/package/reactive-0.11.5/docs/src/FRP-Reactive-Future.html which makes time explicit (and therefore pure?). Plus, I don't love the done variable. I do find the instance useful though as a conceptual || to applicative's &&

The other valid instances I could think of were:

  1. longest/immediate (takes the longest)
  2. concat/empty (concats success values)

The latter can be implemented as [f1, f2].sequenceA().map(concat) and the former is typically to know when all have finished which is achieved by either: liftA2(done, f1, f2) or [f1, f2].sequenceA() so I felt the earliest instance was the most useful.

robotlolita commented 10 years ago

Ah, I see.

Regarding the Monoid instance, a.concat(b) does have some non-determinism for this Future (I guess with an explicit Time you don't have this problem? But it wouldn't fit here I guess). Unsure to which extent this non-determinism is desirable, as the choice is an useful operation indeed.

DrBoolean commented 10 years ago

Okay, yeah. It's still totally pure until you call fork - time doesn't have anything to do with it. I'd find quite a few uses for mconcat in my daily apps.

On Jun 20, 2014, at 3:24 PM, robotlolita notifications@github.com wrote:

Ah, I see.

Regarding the Monoid instance, a.concat(b) does have some non-determinism for this Future (I guess with an explicit Time you don't have this problem? But it wouldn't fit here I guess). Unsure to which extent this non-determinism is desirable, as the choice is an useful operation indeed.

— Reply to this email directly or view it on GitHub.

robotlolita commented 10 years ago

I meant it would be deterministic with a Time value. But yeah, it does look quite useful, thankies :)

leoasis commented 10 years ago

I'd like to jump here again (besides also Twitter) to suggest that the default monoid instance for future should be the one that concats the results of both futures (requiring that the value of the future is a semigroup). That's an intuitive default for a Future (see default impl for Maybe in Haskell), regardless of wether it is implemented easily in some other way. As fantasy land specs say, those other two instances could be provided via a wrapper, which would make the intention of the monoid clearer (something like First and Last for Maybe).

DrBoolean commented 10 years ago

I agree that it is more intuitive. I'm not married to it by any means.

If I were to make Earliest(Future) and Latest(Future), the code could simply be foldMap(Earliest)

Would those live in this project?

robotlolita commented 10 years ago

Sorry for taking too long to respond "Orz.

Anyway, yeah, I think wrappers (and monad transformers) should live in this project.

DrBoolean commented 10 years ago

No prob. I'll make some wrappers and see how it looks. Good call @leoasis - we can have our intuition and eat it too.

leoasis commented 10 years ago

Cool! Looking forward to that!

DrBoolean commented 9 years ago

Just wanted to mention that I added the more intuitive concat() locally, which was simple and I liked it more, but empty() ended up being a roadblock so I abandoned that monoid instance.

The reason is we cannot know the type of the future value and therefore cannot make an empty() value in advance without explicit type help. There's a trick that can defer the computation if empty() is on the left hand side, but that does not pass the monoid associativity laws.

leoasis commented 9 years ago

I see what you're saying. This will be a common problem for monoids depending on the underlying types to be monoids as well, since the empty function does not receive any parameters, and in these cases the empty function will be a function of the empty function of the underlying type. And I'll say "function" one more time. Function.

Perhaps this is something to discuss in the Fantasy Land repo. One approach I'm thinking about is to have a constructor method to define a Future of a specific type: Future.ofType(Type) and make that have the empty method, just calling this.type.empty() inside the future callback, and if not specified throw a clear message saying that more information is needed.

Not the best solution perhaps, but this is where dynamically typed languages fall short I think.

DrBoolean commented 9 years ago

hehe, function indeed.

That's an interesting solution! I think there needs to be some brainstorming around how we'd do this for empty, of, lift, etc. Definitely a fantasy land thing