Help us understand the problem. What is going on with this article?

Polymorphic Proxy fun in PureScript

More than 1 year has passed since last update.

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.

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away