Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type-scheme-case #159

Open
gelisam opened this issue Sep 8, 2022 · 0 comments
Open

type-scheme-case #159

gelisam opened this issue Sep 8, 2022 · 0 comments
Labels
enhancement New feature or request

Comments

@gelisam
Copy link
Owner

gelisam commented Sep 8, 2022

As discussed with @david-christiansen:

We currently have a type-case macro whose scrutinee is a value of type Type. Even though our Hindley–Milner type system does infer polymorphic types for our definitions, it is impossible to observe a forall. Consider the following attempt:

(define my-id
  (lambda (x) x))

-- my-id : ∀(α : *). (α → α) ↦
-- #<closure>
(example my-id)

(define-macro (force-type)
  (>>= (which-problem)
       (lambda (problem)
         (case problem
           [(expression type)
            (type-case type
              [(-> a b)
               (type-case a  -- stuck here
                 [(else _)
                  (pure 'my-id)])])]))))

-- No progress was possible:
--    (AwaitingTypeCase _)
--    (GeneralizeType _ _ _)
(example
  (:: my-id (:: (force-type) (nil))))

my-id has a polymorphic type, but in the list (:: my-id (:: (force-type) (nil)), its type is specialized to ?1 -> ?1. Thus, (force-type)is able to observe that the outer type is a function type, but gets stuck when trying to force the unification variable?1`. This is as-designed: making sure macros get stuck when attempting to force unification variables is the key which ensures that macros cannot behave in fragile ways.

There are cases where pattern-matching on those foralls would be useful. For example, in #157 if we want to support polymorphic implicit conversion functions, we need to pattern-match on the polymorphic type to check if it matches the (-> actual expected) type we are looking for. As another example, we might wish to define a #lang in which polymorphic functions can only be instantiated at types with specific shapes, not at all types, as this makes it possible to implement "non-parametric polymorphism", a runtime type-case which covers all the possible shapes.

The proposed solution is a type-scheme-case macro whose scrutinee is an identifier, not a type. The type-scheme of an identifier is a single forall, a possibly-empty list of universally-quantified type variables, and a type in which those type variables may occur:

`(type-scheme-case 'id
   [(forall (A B C) T)
    (... (type=? A T)
         (type-case T ...))])

Once #155 is implemented, it will be possible to combine type-case and type-equality to examine T and figure out where A, B and C occur inside that type expression.

This should not break predictability, because the type-scheme-case expression obviously needs to block until the Hindley–Milner generalization step has determined which unification variables must be turned into universally-quantified variables. If the scrutinee identifier is bound in a nested let and refers to variables bound in outer lets, T may contain unification variables in addition to occurrences of the bound quantification variables, but type-case and type=? should already handle those well.

@gelisam gelisam added the enhancement New feature or request label Oct 6, 2022
@gelisam gelisam mentioned this issue Oct 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant