Pierre Carbonnelle
Pierre Carbonnelle

Reputation: 2625

How to pattern match on Proxy?

I need a method to conditionally apply a function depending on the specific instance of a class.

I tried using Proxy to annotate the function with the type of its input:

class ApplyIf b where
  applyIf :: Show b => proxy a -> (a -> a) -> b -> String

instance ApplyIf Int where
  applyIf (p :: Proxy Int) f b = show (f b)
  applyIf _                _ b = show b

instance ApplyIf String where
  applyIf _ _ b = show b

main = do
  putStrLn $ applyIf (Proxy:: Proxy Int) (*2) 1    -- 2
  putStrLn $ applyIf (Proxy:: Proxy Int) (*2) "ok" -- ok

But I get an 'Illegal type signature: 'Proxy Int' error on line 5.

Should I use some other mechanisms, like Tagged, Typeable, ... ?

Upvotes: 2

Views: 421

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170839

Proxy is a type that holds no data, but has a phantom parameter of arbitrary type (or even kind).

I.e. at runtime you can't tell if you have a Proxy Int and so can't pattern match on this. Instead of Proxy you need TypeRep:

{-# LANGUAGE TypeApplications #-}
import Type.Reflection

class ApplyIf a where
  applyIf :: Show b => TypeRep a -> (a -> a) -> b -> String

instance ApplyIf Int where
  applyIf tr f b | tr == typeRep @Int = show (f b)
                 | otherwise = show b

(you need TypeApplications for @Int). You could use typeOf (0 :: Int) as well.

EDIT: would this do what you want? See Data.Type.Equality.

{-# LANGUAGE TypeApplications #-}
import Type.Reflection
import Data.Type.Equality

-- could have Typeable a constraint instead of a TypeRep a param
applyIf :: (Show b, Show c, Typeable b) => TypeRep a -> (a -> c) -> b -> String
applyIf tr f b = 
  case testEquality tr (typeOf b) of
    Just Refl -> show (f b)
    Nothing -> show b  

main = do
  putStrLn $ applyIf (typeRep @Int) (*2) 1    -- 2
  putStrLn $ applyIf (typeRep @Int) (*2) "ok" -- ok

Upvotes: 3

melpomene
melpomene

Reputation: 85877

Do you mean something like this?

import Data.Typeable

applyIf :: (Show a, Typeable a, Show b, Typeable b) => (b -> b) -> a -> String
applyIf f x = case cast x of
    Nothing -> show x
    Just y  -> show (f y)

main = do
  putStrLn $ applyIf ((*2) :: Int -> Int) (1 :: Int)
  putStrLn $ applyIf ((*2) :: Int -> Int) "ok"

Here we use Typeable to check at runtime whether a and b are the same type, which either lets us apply f or not.

Upvotes: 3

Related Questions