If you've seen any of my codegen posts, you'll know that I use Proxy
in a bunch of places in order to provide the types for instance resolving, so that I can extract the string name I want to use for my generated code.
Normal Proxy Usage
So the normal class definition looks something like this:
class GetName a where
getName :: Proxy a -> String
instance getNameInt :: GetName Int where
getName _ = "Int"
So to get the name from the Int
type, I would create a Proxy
like so:
main = do
log $ getName (Proxy :: Proxy Int)
But wait, what if I already had a value defined?
two :: Int
two = 2
To create a Proxy
out of this, I would have to define a function like so:
mkProxy :: forall a. a -> Proxy a
mkProxy _ = Proxy
But this isn't so simple in some other cases. Say we had a class for extracting keys from a RowList
, then we'd have to provide a function for providing a row type to work with this:
class Keys (xs :: RowList) where
keysImpl :: RLProxy xs -> List String
keys :: forall row rl
. RowToList row rl
=> Keys rl
=> RProxy row
-> List String
keys _ = keysImpl (RLProxy :: RLProxy rl)
mkRProxyFromRecord :: forall row
. Record row
-> RProxy row
mkRProxyFromRecord _ = RProxy
But do we really need to bother with always working with a concretely typed Type
-kinded Proxy and # Type
-kinded RProxy?
Concrete to polymorphic
Luckily, no, we don't. By taking advantage of the fact that we can create higher kinded types in PureScript, we can define a type variable that will contain the inner type. So the above keys
function can be rewritten:
keys :: forall g row rl
. RowToList row rl
=> Keys rl
=> g row -- this will work for any type with the row as a param!
-> List String
keys _ = keysImpl (RLProxy :: RLProxy rl)
So now this function can be called with either a RProxy
, a normal Record
, or even a Variant
.
We can rewrite the GetName
class to use this instead:
class GetName a where
getName :: forall f. f a -> String
instance getNameInt :: GetName Int where
getName _ = "Int"
And now we can call getName
with either a Proxy or anything that is Type -> Type
kinded, i.e. contains our value as an inner type at the type level:
main :: forall e. Eff (console :: CONSOLE | e) Unit
main = do
log $ getName (Proxy :: Proxy Int)
log $ getName (Identity two)
And ta-da, we don't have to make some Proxy
-making function or anything just to work with concrete values' types.
Conclusion
Hopefully this has shown you that you can always replace any concrete type with a polymorphic one if you aren't using all of the operations associated with them. And even in the cases where you are using some operations of them, e.g. Semigroup append of List items, you can make sure that no unwanted operations are used by having a polymorphic variable that is then constrained for the right type classes and their methods.
Links
P.S.
Unfortunately this only works if your language has higher kinded types and higher ranked types. Without the former, you can't declare f a
/ f :: Type -> Type
, and the latter forall f.
quantification in the type class method.
Haskellers using GHC8 may opt to use Type Applications with Allowed Ambiguous Types to not use proxies altogether, though you will need a solution for extracting types from values.