-
Notifications
You must be signed in to change notification settings - Fork 374
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
Conversation
@@ -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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 Flip
s kind is still * -> * -> *
.
There may be some relationship though.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 forOp
, but noFunctor
. 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.
There was a problem hiding this comment.
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?
Do we want to add |
Btw, derivations also act as restrictions (additionally to laws):
Maybe that adds something to the discussion :) |
@rpominov The issue is that a type (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 |
Ah, I see. So the problem is that Haskell's higher kinded types don't allow this because it doesn't support Do we need to care about this in a specification that targets JavaScript? |
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
|
Some more thoughts on this Pro
Con
That said, I think let's drop the mention and maybe raise an issue to return to it? |
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)
⚡ I also removed the dependency mention |
@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)
⚡ |
Should this thing be called Cofunctor for consistency with Comonad? |
Probably not. Cofunctor is the same as Functor http://stackoverflow.com/questions/34732571/why-there-is-no-cofunctor-typeclass-in-haskell/34732721 |
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 ;) |
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 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) ...
How do people feel about this? |
I think |
#### `contramap` method | ||
|
||
```hs | ||
contramap :: Contravariant f => f a ~> (b -> a) -> f b |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 ;)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
And height of img tag in readme should also be updated to |
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. |
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 |
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. |
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 |
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" /> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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". ;)
Contravariant functor PR fixes
@@ -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" /> |
There was a problem hiding this comment.
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" />
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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
Looks like everything has been addressed here. I'm merging. Thanks everyone! |
@joneshf, would you like to release v3.1.0? |
Resolves #149