Comments (14)
I would love this. I've been doing work with ASTs where instead of writing a recursive type, I parametrize the AST everywhere it would recur and tie the knot separately [http://blog.ezyang.com/2013/05/the-ast-typing-problem/ mentions this under "two-level types"]. Besides the flexibility, its nice because Functor Foldable Traversable do just about everything you need to do.
I was annoyed that I had to order my parameters very carefully to get the maximally-useful instances of those traits, but bifunctor would solve that. On the flip side however, not being able to derive means i cut down boilerplate in some places only to gain in back in others. Naturally ASTs can get pretty big so defining them by hand can be especially annoying.
tl;dr, Big +1 would love this. Happy to help do it myself, but I know none of the relevant TH APIs.
from bifunctors.
I'd have no objection to adopting a generic framework for bifunctor construction, if one showed up on my doorstep. ;)
It should be fairly straightforward to walk the terms.
from bifunctors.
I found this is an interesting problem. I wrote a naive implementation for generating Bifunctor instance, but it cannot handle composed data types. For the case like the one given by hyPiRion, it is easy to write Template Haskell code to generate the instance. However, problem will come when you need to handle composed data type definition such as tuple, list, and many other user defined data types. Take the following as example:
data A a b c = A (Maybe (c,b)) [b]
Somehow, the map should be applied recursively to its all parameterized data type and some of them depends on Functor typeclass since we need to use fmap on the structure. For structure like (c,b), the implementation would somehow depends on Bifunctor typeclass itself. The order c b would make it difficult to implement such template Haskell code for me. I will look into that.
from bifunctors.
Coincidentally enough, I'm working on something pretty similar to this as well. The next version of text-show
will have a Template Haskell deriver for Show1
and Show2
(which are structurally very similar to Functor
and Bifunctor
).
As for composed data types, the comments in TcGenDeriv
give a description of how DeriveFunctor
handles it:
fmap :: a -> b -> f a -> f b
fmap f x = ...
$(fmap 'a 'b) x = x -- when b does not contain a
$(fmap 'a 'a) x = f x
$(fmap 'a '(b1,b2)) x = case x of (x1,x2) -> ($(fmap 'a 'b1) x1, $(fmap 'a 'b2) x2)
$(fmap 'a '(T b1 b2)) x = fmap $(fmap 'a 'b2) x -- when a only occurs in the last parameter, b2
$(fmap 'a '(b -> c)) x = \b -> $(fmap 'a' 'c) (x ($(cofmap 'a 'b) b))
cofmap :: (a -> b) -> (f b -> f a)
cofmap f x = ...
$(cofmap 'a 'b) x = x -- when b does not contain a
$(cofmap 'a 'a) x = error "type variable in contravariant position"
$(cofmap 'a '(b1,b2)) x = case x of (x1,x2) -> ($(cofmap 'a 'b1) x1, $(cofmap 'a 'b2) x2)
$(cofmap 'a '[b]) x = map $(cofmap 'a 'b) x
$(cofmap 'a '(T b1 b2)) x = fmap $(cofmap 'a 'b2) x -- when a only occurs in the last parameter, b2
$(cofmap 'a '(b -> c)) x = \b -> $(cofmap 'a' 'c) (x ($(fmap 'a 'c) b))
So if we adapt this for Bifunctor
, it would probably pretty similar, except there'd be two cases for type variables. In addition, arguments of kind * -> *
would have fmap
used on them, whereas arguments of kind * -> * -> *
(or higher) would have bimap
used on them.
When I get a change, I'm going to port over the text-show
TH machinery to the invariant
package. After I do that, specializing Invariant2
to Bifunctor
should be pretty straightforward.
from bifunctors.
@RyanGlScott Could you tell me how will you handle situation like:
data T a b c = A (b ,(c, [(b,c)]) , Int, a)
in this case we cannot just apply function bifunctor over (b,c,Int,a), since first and second only apply over the last 2 parameters, so we have to pattern match explicitly for the first 2 parameters.
from bifunctors.
data T a b c = A (b ,(c, [(b,c)], Int, a)
There's mismatched parentheses in that data type, so I'm not sure which way to interpret it. If I read it as data T a b c = A (b ,(c, [(b,c)]), Int, a)
, then my (hypothetical) implementation would generate something like this:
instance Bifunctor (T a) where
bimap f g (A x) = case x of
(a, b, c, d) -> A (f a, bimap g (fmap (bimap f g)) b, c, d)
Let's call the function which generates that mkBimap
. In general, if you have a constructor argument of type T
(which isn't one of the last two type parameters), then if T
has no type variables, then you'd generate id
. If T
has one type variable a
, then you'd generate fmap $(mkBimap a)
. If T
has two type variables a
and b
, then you'd generate bimap $(mkBimap a) $(mkBimap b)
.
(I didn't do this with the tuple, however, since DeriveFunctor
singles tuples out as a special case, along with function types.)
Here is the part of text-show
which does what I described above (except for Show2
instead of Bifunctor
, which has some semantic differences.) I'll try to release text-show-1
soon as a (vaguely related) proof-of-concept.
from bifunctors.
Just in case I didn't answer your original question, if you have a data type like this:
data Four a b c d = Four a b c d
data Bad a b = Bad (Four a b Int Int)
then my implementation would explicitly reject a derived Bifunctor
instance for Bad
, since the last two type parameters of Bad
can only appear in the last two type positions of any constructor field's type. So Bad
would be rejected, but something like data Good a b = Good (Four Int Int a b)
would be accepted.
from bifunctors.
@RyanGlScott Thanks for answering, you answered what I asked. You would reject cases like Bad, while I thought we should match aginst the patterns since it is not quite different from data Bad a b = Bad (a,b,Int,Int)
. I generally understand your method, however, I am still a newbie of TH, still need time to look into the existing code.
from bifunctors.
The problem with matching against the pattern in general is that it requires you to know the internal details of the type: constructors, etc. and how to do so when the type is recursive becomes non-trivial.
It'd be better to accept a more limited form that doesn't depend on anything magic.
from bifunctors.
@HaskellZhangSong The "matching against patterns" strategy is a perfectly valid way of doing things (but magical, as @ekmett notes), and the genifunctors
package takes this approach. With genifunctors
, you could define instance Bifunctor Bad where bimap = $(genFmap ''Bad)
(it automatically infers that Bad
should have two mapping functions).
I didn't go with this approach primarily since I wanted to mimic the way DeriveFunctor
works as closely as possible. In addition, once Bifoldable
and Bitraversable
get added to base
, we may need to extend DeriveFunctor
/DeriveFoldable
/DeriveTraversable
to work for Bifunctor
/Bifoldable
/Bitraversable
, and (hopefully) the TH implementation in this package could serve as a jumping-off point.
from bifunctors.
@RyanGlScott I can see your point.~
from bifunctors.
Sweet!
from bifunctors.
Awesome! Has there been a release with this yet (doesn't look like it) and if not when do you think you'll do one?
from bifunctors.
I'd like to make a pull request before bifunctors-5.1
is released, since at the moment my TH code breaks the build on GHC 7.0 and 7.2, and I realized a way to reduce the amount of CPP hackery needed to make this work with data families.
from bifunctors.
Related Issues (20)
- Bad default bitraverse HOT 5
- Make a release HOT 5
- Functor (Foldable..) instances to Bifunctors (Bifoldable..) like Product HOT 2
- Drop support for pre-8.0 versions of GHC in the `5` branch HOT 1
- bifunctor-classes-compat and old bifunctors HOT 3
- Depend on assoc for instances HOT 3
- Kind mismatch in derived instances HOT 2
- Change order of Data.Bifunctor.Fix HOT 7
- Add function that map the same function on both parameters HOT 5
- Category cat => Monoid (Join cat a)
- Bicayley HOT 2
- Please update dependencies to allow template-haskell-2.15 HOT 1
- deriveBifunctor fails on Compose HOT 7
- Deriving Bifunctor falls over on sufficiently higher-rank field types
- Deriving Bifunctor et al. chokes on type families with an appropriate return kind
- Applicative Tannen
- deriveBifoldable generates an unused-variable HOT 5
- Use of source-repository-packages/cabal.project in CI HOT 7
- WrappedBifunctor should have an Applicative instance
- Add Lift instances for Template Haskell
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from bifunctors.