Skip to content

Add ChainRec specification #152

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

Merged
merged 5 commits into from
Sep 5, 2016
Merged

Conversation

safareli
Copy link
Member

@safareli safareli commented Sep 3, 2016

Type of ChainRec is:

class Chain m <= ChainRec m where
  chainRec :: ((a -> c) -> (b -> c) -> a -> m c) -> a -> m b

Fixes #151

@joneshf
Copy link
Member

joneshf commented Sep 3, 2016

This is great! And the other issue was awesome, though I didn't keep up.

I'm a little confused about that type signature. Where does the b come from?

@rjmk
Copy link
Contributor

rjmk commented Sep 3, 2016

c is probably t a b if that helps. So the b comes from somewhere inside (a -> c) -> (b -> c) -> a -> m c.

e.g.`

// f :: (a -> Either a b) -> (b -> Either a b) -> Number -> MonadRec (Either Number String)
var f = Left => Right => n => n > 0 ? MonadRec(Left(n - 1)) : MonadRec(Right("Gotta get low, low, low"))

The earlier function must "contain" a partial function from a -> b, if I understand right. In this case it is the constant function from numbers that returns "Gotta get low, low, low" for every argument, restricted to the non-positives


A value that implements the ChainRec specification must also implement the Chain specification.

1. `T.chainRec((done, next, value) => g(done(value)), i) `is equivalent to `g(i)`
Copy link
Member

Choose a reason for hiding this comment

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

There is something wrong with g. It's certainly takes different types in g(done(value)) and g(i) invocations.

Maybe you meant T.chainRec((done, next, value) => T.of(done(g(value))), i)?

Copy link
Member Author

@safareli safareli Sep 3, 2016

Choose a reason for hiding this comment

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

g takes any type it's like T.of but I did not used T.of as Chain does not requite the method of.

On next line there is:

in case g returns values of type T

i.e. this are equivalent:
Identity.chainRec((done, next, value) => Identity.of(done(value)), 1)Identity.of(1)

Copy link
Member

Choose a reason for hiding this comment

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

Ah, right. It makes sense if we use of as g. Sorry.

Still curious if we could add more laws. Are there any laws for it in PureScript? If not, should we made up our own? Maybe we should add dependency on Monad after all, so we could use of and map in laws?

I just worry what is the value of having a method in the spec if we don't have laws that strictly restrict what it can do?

Copy link
Member Author

@safareli safareli Sep 3, 2016

Choose a reason for hiding this comment

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

@paf31 @garyb do you know of any other "laws" for it?

Copy link
Member

@rpominov rpominov Sep 3, 2016

Choose a reason for hiding this comment

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

Still something bugs me about type of g. It seems like for any law in the spec where we have a function like that we can say "for any g of certain type". For example:

u.map(x => f(g(x))) is equivalent to u.map(g).map(f)

for any:

  • u :: f a
  • g :: a -> b
  • f :: b -> c

a.of(f).ap(a.of(x)) is equivalent to a.of(f(x))

for any:

  • x :: a
  • f :: a -> b

m.chain(f).chain(g) is equivalent to m.chain(x => f(x).chain(g))

for any:

  • m :: m a
  • f :: a -> m b
  • g :: b -> m c

But here we can't say something like that about g because we can't identify it's type. I can't quite articulate why this is a problem, but it feels wrong.

Not sure, but it feels like we can only say "there exists g so that ..." instead of "for any g ...", in which case it's very loose restriction.

Copy link

@paf31 paf31 Sep 3, 2016

Choose a reason for hiding this comment

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

That's right. The first law says these two should give equal results, and the second says chainRec f should use no more stack (asymptotically) than used by f.

Copy link
Member Author

@safareli safareli Sep 4, 2016

Choose a reason for hiding this comment

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

@rpominov type of g is a => T a. maybe I have not clearly expressed that with words. it looks like there is no word on f/g, for example in monad spec . should I remove the line about g? or what you think is best wording for that?

What you think on something like this?

g returns value of type T which contains value of it's first argument type.


About the other laws we could state this

T.chainRec((next,done,v) => p(v) ? f(v).map(done) : g(v).map(next), i) is equivalent to

(function step(v) {
  return p(v) ? f(v) : g(v).chain(step)
}(i))

But stack usage of T.chainRec(f,i) must be at most a constant multiple of the stack usage of f itself.

And types would be:

p :: a -> bool
g :: a -> T b
f :: a -> T a
step :: a -> T b

And we could also include this in laws like this:

const someNameHere = t => eq => p => f => g => x => {
    const a = t.chainRec((next,done,v) => p(v) ? f(v).map(done) : g(v).map(next), x);
    const b = (function step(v) { return p(v) ? f(v) : g(v).chain(step) }(x));
    return eq(a, b);
};

But this must be tested with small inputs as step can blow the stack :D

If we call first law identity then what the name should be for this one? /cc @SimonRichardson

Update: or we could remove first law as it's just specific case of this one and call it identity or something else.

Copy link
Member

Choose a reason for hiding this comment

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

Without putting much thought into it, equivalence looks like it fits the
bill. If anyone disagrees then chime in.

On Sun, 4 Sep 2016, 06:48 Irakli Safareli, [email protected] wrote:

In README.md
#152 (comment)
:

@@ -305,6 +306,30 @@ method takes one argument:

  1. chain must return a value of the same Chain

+### ChainRec
+
+A value that implements the ChainRec specification must also implement the Chain specification.
+
+1. T.chainRec((done, next, value) => g(done(value)), i)is equivalent to g(i)

@rpominov https://github.com/rpominov type of g is a => T a. maybe I
have not clearly expressed that with words. it looks like there is no word
on f/g, for example in monad spec . should I remove the line about g? or
what you think is best wording for that?

What you think on something like this?

g returns value of type T which contains value of it's first argument
type.


About the other laws we could state this

T.chainRec((next,done,v) => p(v) ? f(v).map(done) : g(v).map(next), i) is
equivalent to

(function step(v) {
return p(v) ? f(v) : g(v).chain(step)
}(i))

But chainRec must use constant stack space

And types would be:

p :: a -> boolg :: a -> T bf :: a -> T astep :: a -> T b

And we could also include this in laws like this:

const someNameHere = t => eq => p => f => g => x => {
const a = t.chainRec((next,done,v) => p(v) ? f(v).map(done) : g(v).map(next), x);
const b = (function step(v) { return p(v) ? f(v) : g(v).chain(step) }(x));
return eq(a, b);
};

But this must be tested with small inputs as step can blow the stack :D

If we call first law identity then what the name should be for this one?
/cc @SimonRichardson https://github.com/SimonRichardson


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/fantasyland/fantasy-land/pull/152/files/992f729a420f0ce1343ae67c052b050374329d55#r77447076,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACcaGD2dTr27kK97thrpMa2deKQxuDuUks5qmluqgaJpZM4J0S7m
.

Copy link
Member

Choose a reason for hiding this comment

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

type of g is a => T a

@safareli You've convinced me again that the law is correct. I don't know why my mind keep going back and forth on this :)

But if we have "equivalence" law, the first one seems redundant.

Copy link
Member Author

Choose a reason for hiding this comment

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

equivalence seems good. will remove first one

@rpominov
Copy link
Member

rpominov commented Sep 3, 2016

I'm curious maybe we could make it instance method:

- T.chainRec(f, i)
+ T.of(i).chainRec(f)

?

@rpominov
Copy link
Member

rpominov commented Sep 3, 2016

I'm a little confused about that type signature. Where does the b come from?

I think type should look like this:

class Chain m <= ChainRec m where
  chainRec :: ((a -> c) -> (a -> c) -> a -> m c) -> a -> m a

Edit: #152 (comment) made me realize that ((a -> c) -> (b -> c) -> a -> m c) -> a -> m b is a correct type 👍

@safareli
Copy link
Member Author

safareli commented Sep 3, 2016

I'm curious maybe we could make it instance method:

We should depend least powerful specification. Now we only require structure to implement Chain spec, if we make it instance method then we would need T.of i.e. structure should implement Monad. For Monad it could be derived for example:

T.prototype.monadRec = function(f){
  return this.chain(v => T.chainRec(f, v))
}

// type of `lastPagePostLikes` is `Task Number`
const lastPagePostLikes = Task.chainRec(
  (
    // this function creates some special value from one argument
    // the argument is of same type as `i`, `Page` in this case, it does the recursion 
    next,
    // this function creates some special value from one argument
    // value wrapped in `done` will be in `Task`as result of `Task.chainRec`
    // the argument type is `Number` in this case
    done,
    // on first call it's the `i` and every other invocation 
    // should be of same type `Page` but in JS you can not do that :p
    page
  ) => (
    page.nextPageURI
        // if current page has url to next page load the next page
        // here we have type `Task Page`
      ? HTTP.fetchJSON(page.nextPageURI)
        // here type would be `Task c`
        // but the `c` would be some `Either` like structure
        // so it would be like `Either Page Number`
        .map(next)
        //if we don't have next page url then this is the last page and return post likes
        // type here is `Task Number`
      : Task.of(page.posts.map(getLikes))
        // as this is what we wanted to actually get as the result wrap it in `done`
        // here type would be `Task c`
        // but the `c` would be some `Either` like structure
        // so it would be like `Either Page Number`
        .map(done)
  ),
  // this is `i` initial value. it's type is `Page`
  { nextPageURI : 'posts/1' , posts:[...]}
)

So type of chainRec in this case is

(
  (Page -> Either Page Number) ->
  (Number -> Either Page Number) ->
  Page ->
  Task (Either Page Number)
) -> Page -> Task Number

Replace Number with b and Page with a

(
  (a -> Either a b) ->
  (b -> Either a b) ->
  a ->
  Task (Either a b)
) -> a -> Task b

As value returned from next/done is use only internally it's irrelevant for client. so replace Either a b with c

(
  (a -> c) ->
  (b -> c) ->
  a ->
  Task c
) -> a -> Task b

Replace Task with m and we get

((a -> c) -> (b -> c) -> a -> m c ) -> a -> m b

@safareli
Copy link
Member Author

safareli commented Sep 3, 2016

@SimonRichardson should I also add the law to laws folder?
what should the name be for it?

@SimonRichardson
Copy link
Member

Same name to keep consistency, that would be great 👍

On Sat, 3 Sep 2016, 20:48 Irakli Safareli, [email protected] wrote:

@SimonRichardson https://github.com/SimonRichardson should I also add
the law to laws folder?
what should the name be for it?


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#152 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACcaGKLmMW2W6zPfm2hYui1KzoALHgI4ks5qmc8HgaJpZM4J0S7m
.

@safareli
Copy link
Member Author

safareli commented Sep 3, 2016

I'm asking name of the law, like for monad we have leftIdentity and rightIdentity, but I don't know name for this one, I kinda discovered it when I was writing spec

One thing that comes to my mind is to call itidentity

@SimonRichardson
Copy link
Member

Is that not just identity?

On Sat, 3 Sep 2016, 21:03 Irakli Safareli, [email protected] wrote:

I'm asking name of the law, like for monad we have leftIdentity and
rightIdentity, but I don't know name for this one, I kinda discovered it
when I was writing spec


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#152 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACcaGNQjTyvg6e9uSapNEJX3C750bx8_ks5qmdKlgaJpZM4J0S7m
.

@rpominov
Copy link
Member

rpominov commented Sep 4, 2016

I still think it's good idea to make it depend on Monad, and use instance method as proposed in #152 (comment)

It's true that we should depend on least powerful specification. But in FL we should also try to use instance methods if possible. That's how FL works — it allows to write generic code based on instance methods.

And I don't think we loose much if we make it dependent on Monad. Can we imagine a type that cannot implement of but can implement chainRec?

- remove spaces at the end of some lines
- add newline at the end of id.js
- update ChainRec law
- add chainRec to namespace
- add laws/chainrec.js
- add ChainRec implementation to Id
- add test for ChainRec instance of Id
@safareli
Copy link
Member Author

safareli commented Sep 4, 2016

@rpominov

Can we imagine a type that cannot implement of but can implement chainRec?

maybe Pair (#130).

But in FL we should also try to use instance methods if possible.

It could be instance method as Applicative's of is.

T.chainRec = ....
T.prototype.chainRec = T.chainRec

@safareli
Copy link
Member Author

safareli commented Sep 4, 2016

I could move clean up commit in different PR if necessary

@rpominov
Copy link
Member

rpominov commented Sep 4, 2016

Maybe we don't need dependency on Monad for chainRec to be an instance method e.g., new Pair(1, 'a').chainRec(f) — seems like we don't need of.

T.prototype.chainRec = T.chainRec

Yea, we should at least do this for consistency with of and empty.

@SimonRichardson
Copy link
Member

👍


A value that implements the ChainRec specification must also implement the Chain specification.

1. ```js
Copy link
Contributor

Choose a reason for hiding this comment

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

[minor] These should use single backticks (inline code block) for consistency.

@SimonRichardson
Copy link
Member

This will now need a type signature has the PR #147 has landed.

@SimonRichardson SimonRichardson merged commit b436d6d into fantasyland:master Sep 5, 2016
rjmk added a commit to rjmk/fantasy-land that referenced this pull request Sep 6, 2016
* 'master' of github.com:fantasyland/fantasy-land:
  Add ChainRec specification (fantasyland#152)
  Add type signatures to spec (fantasyland#147)
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.

7 participants