title | path | date |
---|---|---|
A Quickie: A Use Case for Impredicative Polymorphism | impredicative-polymorphism | October 19, 2019 |
Amulet now (as of the 18th of October) has support for impredicative polymorphism based on Quick Look impredicativity, an algorithm first proposed for GHC that treats inference of applications as a two-step process to enable inferring impredicative types.
As a refresher, impredicative types (in Amulet) are types in which a
forall
{.amuçet} appears under a type constructor (that is not
(->)
{.amulet} or (*)
{.amulet}, since those have special variance in
the compiler).
Quick Look impredicativity works by doing type checking of applications in two phases: the quick look, which is called so because it's faster than regular type inference, and the regular type-checking of arguments.
Given a n
-ary application
f x1 ... xn
:
After we have each of the quantifiers, we quickly infer a type for each
of the simple arguments in
x1 ... xn
.
Here, simple means either a
variable, literal, application or an expression annotated with a type
x : t
{.amulet}. With this type in hands, we unify it with the type
expected by the quantifier, to collect a partial substituion (in which
unification failures are ignored), used to discover impredicative
instantiation.
For example, say f : 'a -> list 'a -> list 'a
{.amulet} (the cons
function)1, and we want to infer the application f (fun x -> x) (Nil @(forall 'xx. 'xx -> 'xx))
{.amulet}. Here, the quick look will inspect
each argument in turn, coming up with a list 'a ~ list (forall 'xx. 'xx -> 'xx)
{.amulet} equality by looking at the second argument. Since the
first argument is not simple, it tells us nothing. Thus, the second
phase starts with the substitution 'a := forall 'xx. 'xx -> 'xx
{.amulet}.
However, instead of taking the quantifiers directly from the function's
inferred type, we first apply the substitution generated by the
quick-look. Thus, keeping with the example, we check the function (fun x -> x)
{.amulet} against the type forall 'xx. 'xx -> 'xx
{.amulet},
instead of checking it against the type variable 'a
{.amulet}.
This is important because checking against a type variable degrades to
inference + subsumption, which we wanted to avoid in the first place!
Thus, if we had no quick look, the function (fun x -> x)
{.amulet}
would be given monomorphic type 't1 -> 't2
{.amulet} (where
't1'
{.amulet}, 't2
{.amulet} are fresh unification variables), and
we'd try to unify list ('t1 -> 't2) ~ list (forall 'xx. 'xx -> 'xx)
{.amulet} - No dice!
Most papers discussing impredicative polymorphism focus on the boring, useless example of stuffing a list with identity functions. Indeed, this is what I demonstrated above.
However, a much more useful example is putting lenses in lists (or
optional
{.amulet}, either
{.amulet}, or what have you). Recall the
van Laarhoven encoding of lenses:
type lens 's 't 'a 'b <- forall 'f. functor 'f => ('a -> 'f 'b) -> 's -> 'f 't
If you're not a fan of that, consider also the profunctor encoding of lenses:
type lens 's 't 'a 'b <- forall 'p. strong 'p => 'p 'a 'b -> 'p 's 't
These types are both polymorphic, which means we can't normally have a
list (lens _ _ _ _)
{.amulet}. This is an issue! The Haskell lens
library works around this by providing a LensLike
{.haskell} type,
which is not polymorphic and takes the functor f
by means of an
additional parameter. However, consider the difference in denotation
between
foo :: [Lens a a Int Int] -> a -> (Int, a)
bar :: Functor f => [LensLike f a a Int Int] -> a -> (Int, a)
The first function takes a list of lenses; It can then use these lenses
in any way it pleases. The second, however, takes a list of lens-like
values that all use the same functor. Thus, you can't view
using the
head of the list and over
using the second element! (Recall that
view
uses the Const
{.haskell} functor and over
{.amulet} the
Identity
{.amulet} functor). Indeed, the second function can't use the
lenses at all, since it must work for an arbitrary functor and not
Const
{.haskell}/Identity
{.haskell}.
Of course, Amulet lets you put lenses in lists: See lens_list
and
xs
at the bottom of the file.
Assume that the 'a
{.amulet} variable is bound by a forall 'a.
{.amulet} quantifier. Since we don't use visible type application in
the following example, I just skipped mentioning it. ↩︎