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

makeLiftShowsPrec doesn't work for recursive types #5

Open
expipiplus1 opened this issue Sep 20, 2016 · 4 comments
Open

makeLiftShowsPrec doesn't work for recursive types #5

expipiplus1 opened this issue Sep 20, 2016 · 4 comments

Comments

@expipiplus1
Copy link

import Data.Map
$(makeLiftShowsPrec ''Map)

This requires an instance of Show1 for Map, There seems to be no way to get this behavior without deriving Show1 for Map.

@RyanGlScott
Copy link
Member

Recursive datatypes are unfortunately the Achilles heel of these make-functions, since they require recursive invocations of, e.g., liftShowsPrec.

In light of this fact, the Haddock's claim that it doesn't require a Show1 instance is misleading. Would you consider this bug to be fixed if I updated the Haddock to reflect this fact?

@expipiplus1
Copy link
Author

expipiplus1 commented Sep 21, 2016

I'd consider this bug fixed, but I think it would then warrant a feature request allowing using makeLiftShowsPrec on a type which doesn't already have a Show1 instance; otherwise what's the point :)

I've not looked at the internals of this function, but would it be possible to make the resulting value a fixed point. Something like this:

makeLiftShowsPrec ''Foo =
  let s = <some expression using 's' in place of 'liftShowsPrec' for 'Foo'>
  in s

A map from (type) Names to (term) Names (representing the liftShowsPrec value specific to that type) would need to be maintained for the TH, should a Name not appear in the map then liftShowsPrec is used, otherwise VarE s is used in place.

@RyanGlScott
Copy link
Member

Well, the primary use case for make-functions (at least, for what I use them) is for scenarios when deriving-compat isn't smart enough to infer the context when coming up with an instance. e.g.:

newtype Fix f a = Fix (f (Fix f a))

instance Show (f (Fix f a)) => Show (Fix f a) where
  showsPrec = $(makeShowsPrec ''Fix) 

The scenario you describe is interesting, but not quite a generalization of what the make functions currently do. Let's call this new function makeLiftShowsPrecTwo. For example, consider this:

newtype Fix f a = Fix (f (Fix f a))

showFix = $(makeLiftShowsPrecTwo ''Fix)

What code should be spliced here? A naïve translation would be something like this:

let lspf = \sp sl p value -> case value of
      Fix f -> showParen (p > 10) $
                   showString "Fix "
                 . liftShowsPrec (lspf sp sl) (liftShowList sp sl)
                                 11 f
in lspf

But that still doesn't work, because it requires an invocation of liftShowList at type Fix f a, which also assumes the existence of a Show1 instance. So we end up needing to generate code for liftShowList as well:

let lspf = \sp sl p value -> case value of
      Fix f -> showParen (p > 10) $
                   showString "Fix "
                 . liftShowsPrec (lspf sp sl) (lsl sp sl)
                                 11 f

    lsl = \sp sl -> showListWith (lspf sp sl 0)
in lspf

This works, but the semantics have changed slightly, since makeLiftShowsPrecTwo now requires a default implementation of liftShowList, whereas makeLiftShowsPrec allows an alternative implementation:

instance Show1 List where
  liftShowsPrec = $(makeLiftShowsPrec ''List)
  liftShowList = {- a custom list show function -}

So if this feature were to be implemented, I'd want it not to be the default, but rather opt-in (perhaps via an Options argument). Does that sound reasonable?

@expipiplus1
Copy link
Author

That's a good example; sounds very reasonable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants