The problem is with trying to fit a heterogeneous set of function types into a homogeneous list type / homogeneous lookup-result type.
In the comments you noted that you cannot give the type primitive-procedures : (All (a b) (Listof (Pairof Symbol (-> a * b)))
because the type declared differs from every procedure in the actual list. You probably tried adding All
because you were trying to accommodate all the heterogeneous types in there. But the Listof
type, and more importantly the lookup
function you use to get primitives out of the list, have fundamentally homogeneous types. You'll have to make the type homogeneous and work around the types within the list.
If the values in your target language are the same as your host for a meta-circular interpreter, the simplest choice for this homogeneous function type is (-> Any * Any)
.
(: primitive-procedures : (Listof (Pairof Symbol (-> Any * Any))))
(: lookup-primitive : (-> (Listof (Pairof Symbol (-> Any * Any))) Symbol (-> Any * Any)))
Then the use of lookup-primitive
should be fine using apply
. However the most complicated part is the definition of primitive-procedures
with this homogeneous type.
Just using
(define primitive-procedures
(list (cons 'car car)
(cons 'cdr cdr)
(cons 'list list)
...))
isn't enough and gives a type mismatch.
car
cannot be used as (-> Any * Any)
. It can be used as (-> (Pairof Any Any) Any)
.
cdr
cannot be used as (-> Any * Any)
. It can be used as (-> (Pairof Any Any) Any)
.
list
can be used as (-> Any * Any)
. That's good.
So we must wrap car
and cdr
somehow, with functions that check the number of arguments and check that the argument is a pair, before passing the arguments to car
and cdr
.
(lambda [args : Any *]
(match args
[(list arg)
(unless (pair? arg) (error 'car "wrong argument type"))
(car arg)]
[_
(error 'car "wrong number of arguments")]))
Now this lambda expression can be used as (-> Any * Any)
, but it's clunky. It's even worse when you consider we would have to do this for every primitive that doesn't already fit:
(define primitive-procedures
(list (cons 'car (lambda [args : Any *]
(match args
[(list arg)
(unless (pair? arg) (error 'car "wrong argument type"))
(car arg)]
[_
(error 'car "wrong number of arguments")])))
(cons 'cdr (lambda [args : Any *]
(match args
[(list arg)
(unless (pair? arg) (error 'cdr "wrong argument type"))
(cdr arg)]
[_
(error 'cdr "wrong number of arguments")])))
(cons 'list list)
...))
This code is ugly and repeats itself a lot. I would define a macro called prim
to do this pattern for me:
(require syntax/parse/define)
(define-simple-macro (prim (arg-pred ...) proc)
#:with (arg-id ...) (generate-temporaries #'(arg-pred ...))
(lambda [args : Any *]
(match args
[(list arg-id ...)
(unless (arg-pred arg-id) (error 'proc "wrong argument type")) ...
(proc arg-id ...)]
[_
(error 'proc "wrong number of arguments")])))
Then we can use it in primitive-procedures
like
(define primitive-procedures
(list (cons 'car (prim (pair?) car))
(cons 'cdr (prim (pair?) cdr))
(cons 'list list)
...))
And now it is finally defined with the homogeneous type (Listof (Pairof Symbol (-> Any * Any)))
.
apply
in typed racket. – alinsoar