Avoiding parametric function binding

I'm having some difficulty avoiding a tight parametric binding of function parameters, which is limiting the (de)composability of my expressions. I'm curious as to whether there is an option or method I haven't tried to achieve this. Here's an example test case:
data Var = V1 (Maybe Int) | V2 (Maybe String)
test = V1 (Just 1)
Given this structure, I can do something like this:
elemStr :: (Show a) => Maybe a -> String elemStr = show
varStr (V1 x) = elemStr $ id x varStr (V2 x) = elemStr $ id x
main = putStrLn . varStr $ test
This operation extracted the internal value from the Var container, and then passes it to a parametric function (id) and that result to another parametric function with a class restriction on the input. This is fine so-far. However, what I'd like to do is decompose this to allow more flexibility (using id is pretty boring) without having to repeat the extraction boilerplate each time. My first attempt:
varElem :: forall x . (Show x) => Var -> x varElem (V1 x) = x varElem (V2 x) = x
main = putStrLn . elemStr . varElem $ test
This fails because even though I've specified the same class constraint for the output type of varElem that elemStr requires on its input element, the compiler binds the parametric type of x when it processes the first (V1) definition and fails with an error on the second definition because it asserts that x must be an Int and it found a String. I realized that the parametric output type was awkward, so I tried inverting the design (somewhat similar to fmap):
onVarElem :: forall a . (Show a) => (Maybe a -> String) -> Var -> String onVarElem f (V1 x) = f x onVarElem f (V2 x) = f x
main = putStrLn . onVarElem elemStr $ test
This is probably a better design, but still fails for the same reason: Couldn't match expected type `Int' with actual type `[Char]' Expected type: Maybe Int Actual type: Maybe String In the first argument of `f', namely `x' In the expression: f x Even changing onVarElem so the second parameter was a simple variable and performing the pattern match in an internal where or let binding failed because the first application of f bind its parametric values. Is there a way to delay this parametric binding to allow composable function specifications? Thanks, Kevin -- -KQ

Maybe you want a deconstructor (sometime called an eliminator)? deconsVar :: (Maybe Int -> a) -> (Maybe String -> a) -> Var -> a deconsVar f g (V1 a) = f a deconsVar f g (V2 b) = g b

On Sat, 31 Dec 2011 08:50:05 -0700, Stephen Tetley
Maybe you want a deconstructor (sometime called an eliminator)?
deconsVar :: (Maybe Int -> a) -> (Maybe String -> a) -> Var -> a deconsVar f g (V1 a) = f a deconsVar f g (V2 b) = g b
That works and has the advantage of allowing a single deconstructor definition that can be reused in multiple places, but it requires me to add an argument for each wrapped type, which causes some ripple effect if the type changes. Also, if that argument is parametric over the possible inputs (as asserted by a class restriction) then it starts to get a bit tedious: main = putStrLn . deconsVar elemStr elemStr $ test If I find myself adding a V3 and a V4 to my datatype in the future then that would change to: main = putStrLn . deconsVar elemStr elemStr elemStr elemStr $ test I was hoping to find a way that would let the functor argument retain its parametricity and therefore not need such repetition. -Kevin -- -KQ

On Sat, Dec 31, 2011 at 11:09 PM, Kevin Quick
varElem :: forall x . (Show x) => Var -> x varElem (V1 x) = x varElem (V2 x) = x
main = putStrLn . elemStr . varElem $ test
The problem here is that you want the return type to depend on the 'value' of Var, which is not known until runtime. Maybe you can wrap the 'conflicting' return type inside an existential type: data VarWrap = forall x . (Show x) => Wrap x instance Show VarWrap where show (Wrap x) = show x varElem :: Var -> VarWrap varElem (V1 x) = Wrap x varElem (V2 x) = Wrap x You will need an 'ExistentialQuantification' language extension to do this.

On Sat, Dec 31, 2011 at 4:09 PM, Kevin Quick
onVarElem :: forall a . (Show a) => (Maybe a -> String) -> Var -> String
onVarElem f (V1 x) = f x onVarElem f (V2 x) = f x
main = putStrLn . onVarElem elemStr $ test
This is probably a better design, but still fails for the same reason:
Couldn't match expected type `Int' with actual type `[Char]' Expected type: Maybe Int Actual type: Maybe String In the first argument of `f', namely `x' In the expression: f x
The problem is the scope of the quantification of the type variable 'a'. You can use higher-rank types (via the Rnak2Types or RankNTypes language extension) to achieve what you want. Change the type of 'onVarElem' to onVarElem :: (forall a . (Show a) => Maybe a -> String) -> Var -> String In contrast to the previous type, this type says that the provided function must be polymorphic over Show instances 'a', while the previous type would have allowed any specialization of 'a' with a Show instance. With the previous type, the call onVarElem (maybe show (show . not)) would have been legal, with the new type it is not, which is necessary for the implementation to be well typed. If you don't want to use a language extension that allows you to require polymorphism on function arguments, then Stephen's solution is a good workaround. Sebastian

On Sun, 01 Jan 2012 05:29:42 -0700, Sebastian Fischer
On Sat, Dec 31, 2011 at 4:09 PM, Kevin Quick
wrote: onVarElem :: forall a . (Show a) => (Maybe a -> String) -> Var -> String
The problem is the scope of the quantification of the type variable 'a'. You can use higher-rank types (via the Rank2Types or RankNTypes language extension) to achieve what you want. Change the type of 'onVarElem' to
onVarElem :: (forall a . (Show a) => Maybe a -> String) -> Var -> String
Thanks to both Sebastian and Sean for the solution (abbreviated from Sebastian's reponse above). -Kevin P.S. Sorry for the late followup: some minor system issues prevented checking email for a little while. The delay notwithstanding, *all* of the responses were much appreciated. -- -KQ
participants (4)
-
Kevin Quick
-
Sebastian Fischer
-
Stephen Tetley
-
Yucheng Zhang