I want to print the return type of a function ("Integer" in this case):
{-# LANGUAGE FlexibleInstances #-}
import Data.Default
class HasReturnType a where
getReturnType :: a -> String
instance Default a => HasReturnType (a->b) where
getReturnType f = getReturnType (f def)
instance HasReturnType Integer where
getReturnType _ = "Integer"
instance {-# Overlappable #-} HasReturnType a where
getReturnType _ = "<unmatched case>"
inc :: Integer -> Integer
inc = ( 1)
main = do
putStrLn $ getReturnType inc
Instead, it is printing "<unmatched case>". It seems odd to me, since the return type of inc::Integer->Integer
is clearly an Integer
.
Is it possible to match on the instance of the return value type of a function call like this?
(The example is silly, it is a toy snippet based on something more complicated. I'm just trying to understand why it isn't matching the more concrete instance.)
CodePudding user response:
{-# LANGUAGE FlexibleInstances, ScopedTypeVariables #-}
module FuncReturn where
class HasReturnType a where
getReturnType :: a -> String
instance HasReturnType b => HasReturnType (a -> b) where
getReturnType f = getReturnType $ f undefined
-- instance HasReturnType b => HasReturnType (a -> b) where
-- getReturnType f = getReturnType (undefined :: b)
-- needs ScopedTypeVariables ^^^^
instance HasReturnType Integer where
getReturnType _ = "Integer"
instance HasReturnType Int where
getReturnType _ = "Int"
instance HasReturnType String where
getReturnType _ = "String"
instance HasReturnType Bool where
getReturnType _ = "Bool"
instance {-# OVERLAPPABLE #-} HasReturnType ab where
getReturnType _ = "<unmatched case>"
-- getReturnType length ===> "Int"
-- getReturnType (&&) ===> "Bool"
-- getReturnType ( ) ===> ERROR - Unresolved overloading Type: (Num a, hasReturnType a) => [Char]
inc :: Integer -> Integer
inc = undefined -- doesn't need a binding
-- getReturnType inc ===> "Integer"
CodePudding user response:
Compare your instance:
instance (Default a) => HasReturnType (a->b) where
getReturnType f = getReturnType (f def)
with this variant (difference underlined):
instance (Default a, HasReturnType b) => HasReturnType (a->b) where
---------------
getReturnType f = getReturnType (f def)
In the former instance, the call getReturnType (f def)
requires to solve the constraint HasReturnType b
. GHC immediately tries to resolve that, when b
is still unknown, and the only instance that applies is the generic "unmatched case" one, so it commits to that.
By contrast, the the latter instance we can resolve the constraint using the larger context: this makes GHC choose that, effectively delaying the choice of the instance from this call point to the one in main
. Hence that works.
Thumb rule: GHC tries to solve constraints at each call point. If you don't want to commit to a specific instance at that point, you need to provide the constraint in the context, so that selection is delayed. (Of course, if we don't use overlapping instances the exact point where GHC commits to an instance is immaterial.)
CodePudding user response:
your example is incorrect. The only valid codepath is going through the default case. It's short circuiting the typeclass instance resolution.
A fixed example that fill do what you expect is:
{-# Language FlexibleInstances #-}
import Data.Typeable
class Default a where
def :: a
instance Default Integer where
def = 1
class HasReturnType a where
getReturnType :: a -> String
instance (Default a, Typeable b) => HasReturnType (a->b) where
getReturnType f = show . typeOf $ f def
instance {-# Overlappable #-} HasReturnType a where
getReturnType _ = "<unmatched case>"
instance HasReturnType Integer where
getReturnType _ = "Integer"
inc :: Integer -> Integer
inc = ( 1)
main = do
putStrLn $ getReturnType inc