fantasyland / fantasy-land

Specification for interoperability of common algebraic structures in JavaScript
MIT License
10.11k stars 375 forks source link

Why do `Bifunctor` and `Profunctor` need to implement `Functor`? #214

Open joneshf opened 7 years ago

joneshf commented 7 years ago

Sorry, I haven't been paying much attention to the hierarchy.

rpominov commented 7 years ago

I can only guess, but maybe because we wanted to make a.bimap(id, f) == a.map(f) and a.promap(id, f) == a.map(f). Maybe we should add corresponding laws?

Btw, If we do so, we'll basically move another two derivations to laws #188 .

joneshf commented 7 years ago

Ah, I see. That's pretty confusing since something like Either can implement Bifunctor and can't implement Functor, but Either a can't implement Bifunctor and can implement Functor.

rpominov commented 7 years ago

Yeah, confusing it is. Another mismatch in Haskell vs JavaScript environments. In JS we usually think about just an Either type, without considering number of type arguments. And we just think that Either implements Functor and Bifunctor. At least this is how I usually tend to think about it.

Not sure what we should do with the spec, tbh. Probably remove the dependency, but I personally kinda like if spec demands that a.bimap(id, f) == a.map(f) for example. Although again not sure what that means in terms of Haskell-like type system.

rjmk commented 7 years ago

I would like us to have the ability to talk about these kinds of relationships.

I am uncomfortable with talking about a type implementing both bifunctor and functor. I am slightly uncomfortable with the way the spec allows talking about functor instances for Either (rather than Either a).

So I vote to remove the dependency and for us to work out a way to talk about (a) the kinds of types (b) the partial application of types.

masaeedu commented 6 years ago

Is #290 a duplicate of this?

gabejohnson commented 6 years ago

@masaeedu I don't think so. This issue is concerned with how the spec talks

about (a) the kinds of types (b) the partial application of types.

or fails to do so.

There appear to be two ways in which constraints are specified in the spec which are illustrated in the Bifunctor definition.

A value that implements the Bifunctor specification must also implement the Functor specification.

and

bimap :: Bifunctor f => f a c ~> (a -> b, c -> d) -> f b d

In the former case, a value (not type) that implements the Bifunctor algebra is constrained to values which also implement the Functor algebra.

In the latter case, a type (not a value) is defined as implementing the Bifunctor algebra if it has a bimap method with the signature f a c ~> (a -> b, c -> d) -> f b d and satisfies the laws stated in the previous section.

I think this confusion could be eliminated if we used a class declaration-like notation

(Functor (f a), Functor (f b)) => Bifunctor f where
  bimap :: f a c ~> (a -> b, c -> d) -> f b d

The "Type signature notation" section introduced in #223 mentions "typeclass" while discussing constraints on type variables without giving a definition. The implication is that "typeclass" is synonymous with the term "algebra" used elsewhere in the spec.

gabejohnson commented 6 years ago

Even better (and addressing #290) would be

(Functor (f a), Functor (f b)) => Bifunctor f where
  bimap :: f a c ~> (a -> b, c -> d) -> f b d
  lmap  :: f a c ~> (a -> b) -> f b c
masaeedu commented 6 years ago

@gabejohnson Perhaps we need a meta-spec to specify how to talk about specs like this one. ;)