Home > OS >  class instance selection of a function return value
class instance selection of a function return value

Time:05-20

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
  • Related