Skip to content

Port Clown, Joker, Product (Product2), Costar from bifunctors/profunctor #27

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 38 commits into from
Jan 27, 2021
Merged

Port Clown, Joker, Product (Product2), Costar from bifunctors/profunctor #27

merged 38 commits into from
Jan 27, 2021

Conversation

JordanMartinez
Copy link
Contributor

@JordanMartinez JordanMartinez commented Dec 22, 2020

First PR fixing purescript/purescript-profunctor#23

I found that Flip couldn't be ported here because its Functor instance uses lmap which will be defined in the Bifunctor class.

I also named Bifunctor.Product Functor.ProductBi for a short period of time before I realized it should be called Product2 now. Once I realized that, I renamed it to Product2.

Product was excluded because a version of it with a different kind 
signature already exists in this repo
This prevents module name clashes when pulling in bifunctors as a 
transitive dependency
Clown, Cowrap, Joker, and Wrap were ignored because all but Cowrap 
already exist in the Functor folder and Cowrap must exist in a different 
repo
Its Functor instance depends on Bifunctor, so it cannot be defined here
Wrap won't compile because it still uses `rmap`
import Data.Functor.FunctorRight (class FunctorRight)
import Data.Newtype (class Newtype)

-- | Make a `Functor` over the first argument of a `Bifunctor`
Copy link
Member

Choose a reason for hiding this comment

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

With the reference to `Bifunctor`, this doesn't seem quite appropriate anymore. Although honestly I can't say the wording makes a lot of sense to me in its original context either.

What would people think about changing the doc comment to something like this?

-- | Lift a one-parameter type constructor to a two-parameter type constructor
-- with a phantom second parameter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about

Makes a type that takes two type parameters (e.g. Tuple) into a Functor by operating on the first parameter rather than the second:

-- Functor instance for normal Tuple
map show (Tuple 1 2) == (Tuple 1 "2")

-- Functor instance for Clowned Tuple
map show (Clown (Tuple 1 2)) == Clown (Tuple "1" 2)

Copy link
Member

Choose a reason for hiding this comment

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

I don't think that's what a Clown does, though? mapping over a Clown has no effect on the inner value, only the phantom argument. And I don't think there needs to be special attention paid to the case when Clown is applied to a two-parameter type versus a one-parameter type, does there? A Clown (Tuple 1 2.0) has type Clown (Tuple Int) Number _, and the Int part doesn't have any effect on further clownery, right? (It's been a while since I've read about clowns and jokers so I could be missing something important.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, whoops. You're right.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about this then?

Lifts a type that takes one type parameter to a type that takes two type parameters where the second parameter is a phantom type.

data Box a = Box a
-- these values are the same at runtime
Box a
Clown (Box a) :: Clown a Int
Clown (Box a) :: Clown a String

type TupleInt x = Tuple Int x
-- these values are the same at runtime
Tuple 4 "foo" :: TupleInt String
Clown (Tuple 4 "foo") :: Clown (TupleInt String Boolean)
Clown (Tuple 4 "foo") :: Clown (TupleInt String Number)

Copy link
Member

Choose a reason for hiding this comment

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

I like the general direction, although none of that actually type-checks. (The first set of examples need to be Clown Box a Int, etc.; the second set needs to have the parentheses around TupleInt ... removed and TupleInt itself needs to be defined without the x parameter so that it isn't partially applied, or it needs to be a newtype or something.)

import Data.Functor.FunctorRight (class FunctorRight)
import Data.Newtype (class Newtype, un)

-- | Make a `Functor` over the second argument of a `Bifunctor`
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as on Clown but with the proposed language being:

-- | Lift a one-parameter type constructor to a two-parameter type constructor
-- with a phantom first parameter

import Prelude
import Data.Functor.FunctorRight (class FunctorRight, rmap)

-- | The Product of two `Bifunctor`s.
Copy link
Member

Choose a reason for hiding this comment

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

Another reference to `Bifunctor` which should probably change to two-parameter type constructors or something?

import Data.Functor.FunctorRight (class FunctorRight, rmap)
import Data.Newtype (class Newtype)

-- | Provides a `Functor` over the second argument of a `Bifunctor`.
Copy link
Member

Choose a reason for hiding this comment

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

over the second argument of a `FunctorRight` ?

import Data.Functor.FunctorRight (class FunctorRight)
import Data.Newtype (class Newtype)

-- | `Costar` turns a `Functor` into a `Profunctor` "backwards".
Copy link
Member

Choose a reason for hiding this comment

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

This could mention that it also turns a Contravariant into a Bifunctor... but maybe that doesn't need to be said explicitly? I'm more on the fence with this one than with my other doc suggestions.

@kl0tl
Copy link
Member

kl0tl commented Dec 27, 2020

Should we specialize the second parameters of Clown, Joker, Product2, Star and Wrap to Type because of their FunctorRight instance? If yes we should then also specialize the first parameter of Costar because of the Comonad constraint in its Category instance and the first parameter of Star because of the Monad constraint in its Category instance.

@JordanMartinez
Copy link
Contributor Author

Should we specialize

Probably. I've pushed the changes.

@hdgarrood
Copy link
Contributor

If you want to improve the docs for Clown and Joker, I'd recommend reading the paper "Clowns to the Left of me, Jokers to the Right (Pearl)", since as far as I know that is where they come from, and it's difficult to find a motivating use case for them without having read it. I think the techniques which make use of these data types should be considered advanced, so we arguably shouldn't have docs which take the position that the reader needs help understanding what newtype Clown p a b = Clown p a means.

@hdgarrood
Copy link
Contributor

Regarding these data types' kinds: I think it's a bit weird to specialise only the second parameter to Type. If the FunctorRight instance is a reason to specialize the second parameter to Type, then surely the Bifunctor instance is a reason to specialize the first parameter to Type as well? I think I prefer the idea of specializing both rather than specializing neither, and we aren't breaking anything by leaving it specialized, as they're effectively both specialized to Type pre-0.14.

Quoting Harry: "The real function of Wrap here is to provide a Functor 
instance for a data type with 2 type parameters given only a 
FunctorRight instance. But I can't think of a good reason why someone 
would provide only FunctorRight and not Functor, so I'm actually 
wondering if now isn't a good time to just drop Wrap entirely?"

If we drop it now and need to add it later for some reason, the addition 
would not be a breaking change. If we don't remove it now and later find 
we should have removed it, that will be a breaking change.
@JordanMartinez
Copy link
Contributor Author

I've removed Star and Split due to Harry's comment and removed Wrap due to Harry's comment

@JordanMartinez
Copy link
Contributor Author

I think I prefer the idea of specializing both rather than specializing neither

Done.

@JordanMartinez
Copy link
Contributor Author

"Clowns to the Left of me, Jokers to the Right (Pearl)"

I read through that yesterday. I understood the idea of refactoring two mutually recursive functions that would otherwise blow the stack into versions that would no longer blow the stack. I got lost in the parts beyond that due to all the symbols. Plus, I wasn't trying my hardest to read it either.

Perhaps the docs on Clown (and vice versa for Joker) could say

This advance type's usage and its relation to Joker is best understood by reading through Clowns to the Left, Jokers to the Right (Functional Pearl)

Those that want to understand them will. Those who get scared off by the paper will find a less advanced solution to their problem (or curiosity).

@JordanMartinez
Copy link
Contributor Author

Should we specialize the second parameters of Clown, Joker, Product2, Star and Wrap to Type because of their FunctorRight instance? If yes we should then also specialize the first parameter of Costar

@kl0tl I also pushed these changes in this PR. The Star and Wrap one weren't pushed due to Harry's comments above.

show (Product2 x y) = "(Product2 " <> show x <> " " <> show y <> ")"

instance functorRight :: (FunctorRight f, FunctorRight g) => FunctorRight (Product2 f g) where
rmap f (Product2 x y) = Product2 (rmap f x) (rmap f y)
Copy link
Member

Choose a reason for hiding this comment

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

If we're dropping Wrap, Product2 ought to have a Functor instance as well, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah... Yeah. I'll add that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pushed.

Copy link
Contributor

@hdgarrood hdgarrood left a comment

Choose a reason for hiding this comment

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

LGTM 🎉

@JordanMartinez
Copy link
Contributor Author

I've updated the docs for Clown and Joker. When I generated the docs locally, this documentation was not showing up. I'm not sure if that's because of the link or not. We can fix it later if needed. I'd like to get this in.

@JordanMartinez JordanMartinez changed the title Port bifunctor/profunctor newtypes to this repo where possible Port Clown, Joker, Product (Product2), Costar from bifunctors/profunctor Jan 27, 2021
@JordanMartinez JordanMartinez merged commit 82a2339 into purescript:master Jan 27, 2021
@JordanMartinez JordanMartinez deleted the addFunctorRight branch January 27, 2021 20:15
@JordanMartinez
Copy link
Contributor Author

This PR actually introduced an issue. Since it's been so long since I worked on this initially, I had forgotten a few things. Most importantly, functors already depends on bifunctors. profunctor does not depend on functors or bifunctors. I'm assuming functors should be updated to depend on profunctor as well? If so, then I think all of the newtypes we originally thought to port here still can be ported here (aside from those that can be only one or the other)

@JordanMartinez JordanMartinez mentioned this pull request Jan 30, 2021
1 task
thomashoneyman pushed a commit that referenced this pull request Jan 30, 2021
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.

4 participants