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

adds contravariant functor to spec #150

Merged
merged 15 commits into from Jan 30, 2017
Merged

adds contravariant functor to spec #150

merged 15 commits into from Jan 30, 2017

Conversation

rjmk
Copy link
Contributor

@rjmk rjmk commented Aug 21, 2016

Resolves #149

@@ -380,7 +402,7 @@ method takes two arguments:
### Profunctor

A value that implements the Profunctor specification must also implement
the Functor specification.
the Functor and Contravariant Functor specifications.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm in two minds about including this here.

In some other typed languages we'd have an issue where the variance of the type arguments of contravariant and profunctors don't line up. We don't have this issue with a regular covariant functor and profunctor because both last type arguments are covariant (we just have to partially apply the type constructor to take the kind of profunctor from * -> * -> * to * -> *).

The other half of my mind says this is probably being pedantic as we're not bound by such a type system so it's not really a problem.

Copy link
Contributor Author

@rjmk rjmk Aug 22, 2016

Choose a reason for hiding this comment

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

Hmm.

Am I right in thinking that there could be a class AmateurFunctor with the opposite problem?

class AmateurFunctor f where
  amateurmap :: (a -> b) -> (c -> d) -> f a d -> f b c

I'm slightly unclear if there's a connection between the variance of the type arguments in the kind and the variance of type parameters in the type.

Copy link
Contributor

Choose a reason for hiding this comment

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

Am I write in thinking that there could be a class AmateurFunctor with the opposite problem?

In something like Haskell, a type that implements AmateurFunctor could only be declared as Contravariant.

class AmateurFunctor f where
  amateurmap :: (a -> b) -> (c -> d) -> f a d -> f b c

class Contravariant f where
  contramap :: (b -> a) -> f a -> f b

instance AmateurFunctor f => Contravariant (f a) where
  contramap = amateurmap id

To also declare it as a Functor you would need to flip the order of the type arguments with a newtype.

newtype Flip f b a = Flip { unFlip :: f a b }

instance AmateurFunctor f => Functor (Flip f a) where
  fmap f = Flip . amateurmap f id . unFlip

If however we were to ignore the notion of type arguments, it could just be stated that:

p.promap(f, g) = p.contramap(f).map(g) = p.map(g).contramap(f)

It looks a little odd for me to see contramap and map being applied directly to the same type, but as the Flip newtype above highlights, the types are effectively identical.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Am I right in thinking that there could be a class AmateurFunctor with the opposite problem?

In something like Haskell, a type that implements AmateurFunctor could only be declared as Contravariant.

Yup, that's the sort of thing I was thinking.

I think I get it now. It's for the same reason that the functor for a tuple is on the second element and for a function is on the codomain?

One thing I'm slightly confused about is the variance of type arguments. I think that in a value level function f :: a -> b -> c, both of the first two params are in negative position. Is that different for a kind T :: * -> * -> *? Or am I misapplying reasoning in an inappropriate domain?

Copy link
Member

@joneshf joneshf Aug 22, 2016

Choose a reason for hiding this comment

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

I don't think it's that straight forward. Take Flip f a b. How these variables are used depends on f. If you have

newtype Reader a b = Reader (a -> b)
newtype Op a b = Op (b -> a)

Then, Flip Reader a b has the a and b in positive and negative position, respectively.
But, Flip Op a b has the a and b in negative and positive position, respectively.
All while Flips kind is still * -> * -> *.

There may be some relationship though.

Copy link
Contributor Author

@rjmk rjmk Aug 22, 2016

Choose a reason for hiding this comment

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

I see! I think. Here's a statement my understanding would predict to be true: there's a Contravariant instance for Op, but no Functor. Is that correct?

I'm still not quite sure I get

In some other typed languages we'd have an issue where the variance of the type arguments of contravariant and profunctors don't line up

In that I think I see the problem now, but I'm not sure I see it's relation to variance of the type arguments

Also there seems to me to be an inessential semantic issue at hand. In that one speaks of, say, the functor instance for functions, but it's seems somewhat conventional / tied to Haskell's syntax that we say fixing the first type variable gives us 'the' functor instance. Might it not be more accurate to talk of the functor instance for functions of fixed domain?

It may be that I have a fairly deep misunderstanding that would take a lot of teaching effort to resolve, so will happily accept being told to go and read something etc.

Copy link
Member

Choose a reason for hiding this comment

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

Here's a statement my understanding would predict to be true: there's a Contravariant instance for Op, but no Functor. Is that correct?

Yep!

Might it not be more accurate to talk of the functor instance for functions of fixed domain?

Probably 😉.

I'll let @scott-christopher respond to your other questions, as the lack of type variables is confusing me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, @joneshf.

I think I am also pro removing it as a dependency. Are you happy for me to push up the removal, @scott-christopher?

@rpominov
Copy link
Member

rpominov commented Aug 22, 2016

Do we want to add promap derivation via map and contramap to Derivations? Or do you conclude that this is a bad idea to connect promap and contramap (the discussion above went way over my head)?

@rpominov
Copy link
Member

Btw, derivations also act as restrictions (additionally to laws):

If a data type provides a method which could be derived, its behaviour must be equivalent to that of the derivation (or derivations).

Maybe that adds something to the discussion :)

@rjmk
Copy link
Contributor Author

rjmk commented Aug 22, 2016

@rpominov The issue is that a type P a b, if there's a profunctor instance for P has a (covariant) functor instance for P a, but a contravariant functor instance for P _ b (which is not a valid expression). So then in fact the derivation could not be achieved

(If we were to allow the 'partial application' in the type to be in the middle as well as the end, then we would not know whether the contrafunctor instance for P a b was on the a or the b so we wouldn't be able to have a reliable derivation)

@rpominov
Copy link
Member

Ah, I see. So the problem is that Haskell's higher kinded types don't allow this because it doesn't support P _ b syntax. But this problem exist only in Haskel, right? And also everything is still correct semantically, the problem is only with syntax.

Do we need to care about this in a specification that targets JavaScript?

@rjmk
Copy link
Contributor Author

rjmk commented Aug 22, 2016

I'm not sure it's only syntax (I am far from an expert); I worry that we introduce a possibility for unlawfulness with that degree of freedom

El 22/08/2016, a las 18:51, Roman Pominov notifications@github.com escribió:

Ah, I see. So the problem is that Haskell's higher kinded types don't allow this because it doesn't support P _ b syntax. But this problem exist only in Haskel, right? And also everything is still correct semantically, the problem is only with syntax.

Do we need to care about this in a specification that targets JavaScript?


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or mute the thread.

@rjmk
Copy link
Contributor Author

rjmk commented Sep 1, 2016

Some more thoughts on this

Pro

  • As it stands, there's nothing to stop someone defining Semigroup b => instance Monad Either _ b, for Fantasy Land (please excuse the notation)

Con

  • Part of the purpose of this repo is to help people port knowledge from languages with ADTs
    • Demanding a contravariant instance for a profunctor would be very confusing for them
  • We're confident that the way it is in Haskell fits together sensibly. I, at least, am not sure I want to gamble that nothing goes wrong when you stop considering type constructors as kind functions and partially applying them to get the various instances
  • Being a dependency to profunctor has little effect on if contravariant is useful

That said, I think let's drop the mention and maybe raise an issue to return to it?

@SimonRichardson
Copy link
Member

Can you update to the latest master (you'll also need to add a type signature), then it's one step closer to merging.

Thanks.

* 'master' of github.com:fantasyland/fantasy-land:
  Add ChainRec specification (fantasyland#152)
  Add type signatures to spec (fantasyland#147)
@rjmk
Copy link
Contributor Author

rjmk commented Sep 6, 2016

I also removed the dependency mention

@SimonRichardson
Copy link
Member

@rjmk you need to update again sorry, more changes have just been dropped. 🎱

* 'master' of github.com:fantasyland/fantasy-land:
  change spec to require prefixed names (fantasyland#146)
@rjmk
Copy link
Contributor Author

rjmk commented Sep 6, 2016

@rpominov
Copy link
Member

Should this thing be called Cofunctor for consistency with Comonad?

@rpominov
Copy link
Member

Probably not. Cofunctor is the same as Functor http://stackoverflow.com/questions/34732571/why-there-is-no-cofunctor-typeclass-in-haskell/34732721

@rjmk
Copy link
Contributor Author

rjmk commented Sep 29, 2016

You may be interested to know that there is no contravariant (co)monad! Because if you tried to nest the instances of the monad, you'd have contra-contravariance, which is just covariance! So your join would be unlawful

Another way at looking at it is that the contravariance ruins the monoidality of the endofunctor ;)

@rpominov
Copy link
Member

Lecture about Contravariant Functor from Category Theory course by Bartosz Milewski: https://youtu.be/wtIKd8AhJOc?t=26m46s .

It can be interesting for people who are still learning (like me). I like the analogy he makes that if Functor is a box, than Contravariant Functor is like an engine that needs a value of type a as fuel in order to run. Creates good intuition!

Also he talks a bit about the name (Cofunctor vs Contravariant Functor) and about connection between Contravariant Functor and Profunctor.

* 'master' of github.com:fantasyland/fantasy-land: (29 commits)
  Version 2.1.0
  Add Alt, Plus and Alternative specs (fantasyland#197)
  Use uppercase letters for Type representatives in laws (fantasyland#196)
  Fix id_test and argument order in laws (fantasyland#193)
  Version 2.0.0
  Another go at updating dependencies (fantasyland#192)
  release: integrate xyz (fantasyland#191)
  test: remove unnecessary lambdas (fantasyland#190)
  require static methods to be defined on type representatives (fantasyland#180)
  lint: integrate ESLint (fantasyland#189)
  Enforce parametricity (fantasyland#184)
  readme: tweak signatures to indicate that methods are not curried (fantasyland#183)
  Fix reduce signature to not use currying (fantasyland#182)
  Link to dependent specifications (fantasyland#178)
  Add Fluture to the list of implementations (fantasyland#175)
  laws/functor: fix composition (fantasyland#173)
  laws/monad: fix leftIdentity (fantasyland#171)
  Minor version bump
  bower: add bower.json (fantasyland#159)
  Fix chainRec signature to not use currying (fantasyland#167)
  ...
@rjmk
Copy link
Contributor Author

rjmk commented Nov 12, 2016

How do people feel about this?

@safareli
Copy link
Member

I thinkfigures/ should be updated too.

#### `contramap` method

```hs
contramap :: Contravariant f => f a ~> (b -> a) -> f b
Copy link
Member

Choose a reason for hiding this comment

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

If Contravariant is the name of the type class, should we remove "Functor" from the heading?

Copy link
Contributor

@evilsoft evilsoft Nov 15, 2016

Choose a reason for hiding this comment

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

Are there any examples of contra variance on value based ADTs?

I have only seen them on function based bits that have their returns fixed. I know it moves away from the Haskell definition, but would it make sense to adopt a signature more like:

Contravariant f => f a b ~> (c -> a) -> f c b

or

Contravariant f => f (a -> b) ~> (c -> a) -> f (c -> b)

Or is that too limiting for the spec?

EDIT: Not specific to your question @davidchambers just not very good at a github ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

Or....does this just imply that the return is fixed and only specifies where the variance occurs?

In any case, it may make for an easier intuition for consumers determining if they indeed have some contra variance available to them

Copy link
Member

Choose a reason for hiding this comment

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

Sure, you can have something like.

data Foo a = Foo

And that can be contravariant:

instance Contravariant Foo where
  contramap _ _ = Foo

Interestingly, it's also covariant 😄

instance Functor Foo where
  fmap _ _ = Foo

Or if you're lazy:

{-# LANGUAGE DeriveFunctor #-}

data Foo a = Foo deriving (Functor)

We shouldn't use the signatures above because they do limit the spec too much. For instance Foo couldn't be Contravariant with either of those signatures.

Copy link
Contributor

@evilsoft evilsoft Nov 15, 2016

Choose a reason for hiding this comment

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

Perfect! Thanks @joneshf!! just the answer I was looking for!!

TIL!

Copy link
Member

Choose a reason for hiding this comment

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

To answer @davidchambers question: yes, I think we should rename this to Contravariant. Afterwhich, we should merge this.

@rjmk Are you up to this last change? If not, let me know and I'll make it and get this merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@safareli
Copy link
Member

safareli commented Nov 15, 2016

And height of img tag in readme should also be updated to 271

@JeffDownie
Copy link

JeffDownie commented Dec 5, 2016

Is it a good idea to hide the dependency of profunctor? A profunctor looks (at least to my eyes) a lot harder to understand than a contravariant functor anyway - if someone can figure out their object is a profunctor, they can figure out it's a contravariant functor.

Hiding the links between these structures really detracts from using fantasyland as a learning resource too.

@rjmk
Copy link
Contributor Author

rjmk commented Dec 5, 2016

In Haskell, there is no dependency of Profunctor on Contravariant functor. Contravariant functors only have one 'free' type variable, whereas Profunctors have two. For any profunctor P, we can define data P' b a = P' (P a b) such that P' b is a contravariant functor (nb: not P'). Considering that we use Haskell syntax for the type signatures, I think we should probably stick to what they can express

@JeffDownie
Copy link

It just seems weird, though, limiting ourselves to what a different languages' type signature can represent.

Haskell doesn't seem like the perfect standard to hold this repo to (at least in this case, don't get me wrong, most of the time it's amazing :D). Saying something is or isn't a given mathematical structure based on the order of arguments in its' constructor just feels icky/wrong.

@rjmk
Copy link
Contributor Author

rjmk commented Dec 6, 2016

I get that concern. However, considering the construction of the rest of the spec in terms of Haskell syntax, I don't think this PR is the appropriate place to diverge

@JeffDownie
Copy link

True - that sound excessive for this PR :D

@@ -4,7 +4,7 @@

(aka "Algebraic JavaScript Specification")

<img src="logo.png" width="200" height="200" />
<img src="logo.png" width="200" height="271" />
Copy link
Member

Choose a reason for hiding this comment

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

Is this change intentional? Perhaps you meant to update the dimensions of figures/dependencies.png instead?

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 dear! Yep, that's right

@@ -13,6 +13,7 @@ digraph {
Extend;
Foldable;
Functor;
"Contravariant Functor";
Copy link
Member

Choose a reason for hiding this comment

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

Should this match the name of the section heading? I would like to see both named Contravariant or ContravariantFunctor (I don't mind which).

@@ -13,6 +13,7 @@ digraph {
Extend;
Foldable;
Functor;
Contravariant;
Copy link
Member

Choose a reason for hiding this comment

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

figures/dependencies.png still shows "Contravariant Functor". ;)

@@ -28,7 +29,7 @@ structures:
* [Bifunctor](#bifunctor)
* [Profunctor](#profunctor)

<img src="figures/dependencies.png" width="888" height="340" />
<img src="figures/dependencies.png" width="1068" height="347" />
Copy link
Member

Choose a reason for hiding this comment

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

Rather than the image's natural dimensions, I think we want the dimensions of the scaled image on GitHub:

<img src="figures/dependencies.png" width="888" height="289" />

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, Didn't realize we were scaling things 😊.

unspecified.
2. `f` can return any value.

2. `contramap` must return a value of the same Functor
Copy link
Contributor

Choose a reason for hiding this comment

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

"a value of the same Functor"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! I imagine this is a hangover from the Contravariant Functor name

@joneshf
Copy link
Member

joneshf commented Jan 30, 2017

Looks like everything has been addressed here. I'm merging. Thanks everyone!

@joneshf joneshf merged commit 5ac5a9f into fantasyland:master Jan 30, 2017
@davidchambers
Copy link
Member

@joneshf, would you like to release v3.1.0?

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

10 participants