GithubHelp home page GithubHelp logo

Comments (14)

Ericson2314 avatar Ericson2314 commented on May 10, 2024

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.

ekmett avatar ekmett commented on May 10, 2024

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.

HaskellZhangSong avatar HaskellZhangSong commented on May 10, 2024

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.

RyanGlScott avatar RyanGlScott commented on May 10, 2024

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.

HaskellZhangSong avatar HaskellZhangSong commented on May 10, 2024

@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.

RyanGlScott avatar RyanGlScott commented on May 10, 2024

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.

RyanGlScott avatar RyanGlScott commented on May 10, 2024

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.

HaskellZhangSong avatar HaskellZhangSong commented on May 10, 2024

@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.

ekmett avatar ekmett commented on May 10, 2024

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.

RyanGlScott avatar RyanGlScott commented on May 10, 2024

@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.

HaskellZhangSong avatar HaskellZhangSong commented on May 10, 2024

@RyanGlScott I can see your point.~

from bifunctors.

Ericson2314 avatar Ericson2314 commented on May 10, 2024

Sweet!

from bifunctors.

pchiusano avatar pchiusano commented on May 10, 2024

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.

RyanGlScott avatar RyanGlScott commented on May 10, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.