#lang at-exp slideshow @(require slideshow/code) @(slide #:title "Match" @para{@code[match] is a special form like @code[cond]} @para{But instead of having clauses that evaluate to either true or false, it takes one item and tests it against the set of clauses} 'next @code[(match num-presents [0 "Christmas is ruined!"] [1 "I guess that's alright"] [2 "Good"] [_ "Great"])] ) @(slide #:title "Match" @code[(match num-presents [0 "Christmas is ruined!"] [1 "I guess that's alright"] [2 "Good"] [_ "Great"])] @para{Compare that to using @code[cond]} ; Doesn't fit @code[(cond [(= num-presents 0) "Christmas is ruined!"] [(= num-presents 1) "I guess that's alright"] [(= num-presents 2) "Good"] [#t "Great"])] ) @(slide #:title "Match" @para{If that's all that @code[match] did, it would still be useful, but not very often. But @code[match] can do much more, it can match against structures and lists} @code[(match (list 1 2 3) [(list 1 2 3) 'something] ['() 'nothing] [_ 'something-else]) => 'something] ) @(slide #:title "Bindings" @para{The full power of @code[match] is in it's ability to give names to things.} @para{@code[ (define (rotate lst) (match lst [(list) (list)] [(list a) (list a)] [(list a b) (list b a)] [(list a b c) (list c a b)]))]} @para{@code[>(rotate '(1 2 3)) '(3 1 2)]} ) @(slide #:title "Bindings" @para{I find this really nice when writing recursive code, as I don't need to use @code[first] or @code[rest] anymore, and I never accidentally apply them to the empty list} @para{@code[(define (map f lst) (match lst ['() '()] [(cons x xs) (cons (f x) (map f xs))])) ]} @para{Just a note, you see this pattern of a list being matched into the first and rest and them being called @code[x] and @code[xs], (pronouced like "excess") or @code[a] and @code[as], or @code[b] and @code[bs]. It help keep the relationship between the variables clear.}) @(slide #:title "Bindings" @para{Another example} @code[ (define (filter pred lst) (match lst [(cons x xs) #:when (pred x) (cons x (filter pred xs))] [(cons _ xs) (filter pred xs)] ['() '()]))]) @(slide #:title "Warning" @para{Note one tricky point, @code[empty] is not a literal. On line 87 of @tt{racket/collects/list.rkt} there is the line of code:} @para{@code[(define empty '())]} @para{This means, if you try and match against it, the same thing happens like you try and match against a different variable, like @code[x]} @para{@code[(match (list 1 2 3) [empty 'true] [_ 'false])]} @para{Evaluates to @code['true]!!!} ) @; Nesting, Structs @(slide #:title "Haskell" @para{Now, everything I've shown off here is the basic functionally of pattern matching, and if only use this, I think it'll make your code more clear. Also, pattern matching like this is avalible in Haskell} @para{@code[ map f [] = [] map f (x : xs) = f x : map f xs ]} @para{This will no longer be true from here on, as Racket's @code[match] is actually quite sophisticated. Checkout the documentation for the full list of things it can do.}) @(slide #:title "Equality" @para{If we repeat a binding, what happens? Racket checks to see if the two instances are equal for us.} @para{@code[(match (list 1 2) [(list a a) 'same] [(list a b) 'different]) => 'different ]} @para{@code[(match (list 1 1) [(list a a) 'same] [(list a b) 'different]) => 'same ]}) @(slide #:title "..." @para{Racket also lets you use three dots in a row "..." to collect many elements into a list. For example:} @para{@code[(match (list 1 2 3 4) [(list a as ...) (list as a)]) => '((2 3 4) 1)]} @para{as @code[as] is @code['(2 3 4)].} @para{Note, it can match 0 items.} ) @(slide #:title "..." @para{This even works inside nested lists, which is really cool. Say we were storing students' information in structs:} @para{@code[(struct student (name mark))]} @para{ @code[(match (list (student "Alice" 89) (student "Bob" 87) (student "Eve" 88)) [(list (student name mark) ...) (average mark)]) => 88]} @para{will return the average mark of the class} ) @(slide #:title "and and or" @para{You can also take conjunctions and disjunctions of patterns.} @para{@code[(and pat1 pat2)] matches when both @code[pat1] and @code[pat2] match. Likewise @code[(or pat1 pat2)] matches when either pattern matches.} @para{ @code[(match '(1 (2 3) 4) [(list _ (and a (list _ ...)) _) a]) => '(2 3)]} @para{ @code[(match '(1 2 3) [(or (list 1 _ _) (list 2 _ _)) 'yup]) => 'yup] } ) @(slide #:title "and" @para{@code[and] is quite useful since it can also bind names. This is a common pattern in my code.} @para{ @code[ (match (map read-parse (rest line)) [(and children (list (list 'terminal "LPAREN" "(") (list 'typed-rule lvalue-type _ ...) (list 'terminal "RPAREN" ")"))) (cons 'typed-rule (cons lvalue-type (cons line children)))]) ]} ) @(slide #:title "?" @para{Sometimes you want to transform the data before matching it, that's where @code[(? pred pat ...)] comes in handy. It takes a predicate which must return a true value before the patterns can match. You can supply no extra patterns if you want.} @para{ @code[(match '(1 2 3) [(list (? odd? a) 2 _) a]) => 1]} ) @(slide #:title "app" @para{In a more general way, you can use @code[(app f pat ...)] to match against the result of any function, not just predicates.} @para{ @code[(match '(1 2) [(app length 2) 'yes]) => 'yes]}) @(slide #:title "Match extenders" @para{We can write any of these previous helpers ourselves, or any other ones we can thing ok.} @para{ @code[ (define-match-expander aba? (lambda (stx) (syntax-case stx () [(_ a b) #'(list a b a)]))) (match (list 1 2 1) [(aba? 1 2) 'worked])]})