Reputation: 6152
I'd like to write a function that matches on different syntax of the input argument, e.g.,
(define (foo e)
(match e
[int (print "int")]
[(and _ _) (printf "and")])
)
such that I can call it with:
(foo 1) --> prints "int"
(foo (and #t #f)) --> prints "and"
but that doesn't seem to work. What did I do wrong?
Upvotes: 0
Views: 490
Reputation: 643
There is not a short yet complete response to this.
The first thing is, you cannot do this with a procedure. There are predicates you can use to determine if something is a number, like number?
, but you can't catch syntax like and
with this.
But the really, really, really important point is that you can't catch anything like this: by the time foo
sees e
, the expander has already expanded any syntax and evaluated the expression. That is, for instance, (and #t (+ 3 4))
has been converted into an expression like (if #t (+ 3 4) #f)
and this expression is already evaluated by the time you see e
inside the procedure, in which case e
is just 7
. In Racket, arguments to procedures are evaluated before the procedure is called. This means, roughly, all syntax transformers are applied, and the resulting expanded expression is evaluated, and then the procedure is called on the resulting value.
If you only care about tinkering with s-expressions, you can quote
the argument and play with the resulting list. (quote (and 2 3)
will give you a list containing the symbol 'and
and not the syntax, so this probably isn't what you want. I mention it for some completeness.
The way to manage your question in Racket is to use macros. Again, the problem is that expressions passed as arguments to a procedure are already evaluated by the time the procedure is called, so you have to work at the level of define-syntax
in order to catch expressions before they're expanded and evaluated. That is, to solve your specific problem you need something like a procedure which takes some syntax as an argument and gives back syntax:
#lang racket/base
(require (for-syntax racket/base syntax/parse))
(define-syntax (foo stx)
(syntax-parse stx
#:literals (and)
[(_ i:number)
(syntax (displayln "int"))]
[(_ (and a b))
(syntax (displayln "and"))]))
This procedure takes some syntax and produces some other syntax. The syntax argument stx
(which could be named anything, it's just convention) must match a clause or else it is a syntax error. When you define syntax, you are essentially making a new programming language and this is a new construct in that language. If it matches, such as the case (and #t #f)
, it will replace the syntax #'(foo (and #t #f))
with something like #'(displayln "and")
. It won't actually display "and" until this is evaluated.
To get started on macros see the Racket Guide or the Racket Reference.
Upvotes: 2
Reputation: 17203
To add just a bit to the excellent answer from law-of-fives, the feature I think you're looking for were called "FEXPR"s in Lisp. A FEXPR was a procedure that received the syntax of the arguments, rather than their values.
Several people pointed out the problems with FEXPRs, though (cf Wand, "The Theory of Fexprs is Trivial"); specifically, they make compilers very very unhappy. Instead, we now have Racket's macro system, which manages to combine expressiveness with a clear boundary between compile-time and run-time execution.
Upvotes: 0
Reputation: 31147
The problem is that the match pattern int
matches everything.
> (match 42
[int "foo"]
[_ "bar"])
"foo"
If you want to match the symbol int
you need to write 'int
> (match 'int
['int "foo"]
[_ "bar"])
"foo"
> (match 42
['int "foo"]
[_ "bar"])
"bar"
Also note that in you example: (foo (and #t #f))
the arguments of the functions call are evaluated before the function is called. That is, first (and #t #f)
is evaluated with #f
as result. Then foo
is callled: (foo #f)
.
If you want to call foo
with a list-and-symbols representation the expression, then use: (foo '(and #t #f))
.
Upvotes: 0