#lang axe
Some handy tools that you might need. Just like the axe in your hand. Source
#lang axe | package: axe |
#lang axe export all identifiers from #lang racket. Thus you can safely replace #lang racket with #lang axe.
#lang axe re-exports lexi-lambda’s Generic Collections which provides a generic interface for all racket collections.
1 Reader Extension
Reader extension is enabled by the #lang axe line on the top of your source file.
1.1 Raw String
#lang axe r"raw string" r’raw string’
This syntax is borrowed from Python. If the string literals you are trying to write contains lots of (mixed) quotes, or escaped forms, raw string can help a lot.
For example if we want to match a single digit we have to compose regular expression: (pregexp "\\d"). Note that we have to write \\ to make racket’s reader happy and make us unhappy.
With this reader extension, the \\ character is interpreted as raw. But note that \" is interpreted as " in r"\"" form. The same goes to ' in r'\''.
>
(list r'\t\a\b\c\d\1 \'quote\'') '("\\t\\a\\b\\c\\d\\1 'quote'")
>
(list r'''mixe 'single quote' with "double quote""''') '("mixe 'single quote' with \"double quote\"\"")
Note that """ or '''' are matched eagerly. So that in r"""ends with"""", racket will treat the last quote as the begin of another quoted string.
Now we have an easy way to read in raw strings, we still got some troubles building up regular expressions. Thus we provide pregexp-raw and regexp-raw to build up regular expressions from raw strings.
>
(regexp r"(\t*)\1") #rx"(\\t*)\\1"
>
(pregexp-raw r"(\t*)\1") #px"(\t*)\\1"
> (regex-escape-raw "\\a\\b\\t\\n\\1") "\a\b\t\n\\1"
(regexp (regex-escape-raw raw-string)) (pregexp (regex-escape-raw raw-string))
1.2 Raw regexp
#lang axe #rx/raw regexp/ #px/raw regexp/ r/raw regexp string/
Typing regular expressions could be difficult in racket. In python, we can write a regular expression in raw mode: r"(\t*)\1". When translated into racket, we have #px"(\t*)\\1". Note that we need to add another \ character to escape \\1.
It is inconvenient. Luckily we have #lang at-exp so that we can do things like this:
>
(regexp-match @regexp{(.*)\1} "123123") '("1231" "123")
\1 is one example that you do NOT want something to be escaped by racket reader. But sometimes you do need it: such as when type @px"\t", you actually wants to match the #\tab. If we use at-exp for it, it will treat \t as two characters: \\ and t. Which will not be recognized as #\tab character in racket’s "pregexp" compiler, nor "regexp" of course.
Thus we introduce the new form:
#lang axe (regexp-match #px/(\t*)\1/ "\t\t")
reports '("\t\t" "\t"). That means the you can write raw regexp and enclose it with #px/ and /. The same goes to #rx/raw/
The "raw regexp string" is like raw regular expressions, but they can be used as replace string in regexp-replace. The following two forms are equal:
#lang axe #px/(\t*)\1/ (pregexp r/(\t*)\1/)
And the r/raw regex string/ can be use where regexp strings are needed:
#lang axe (regexp-replace #px/(\d+)/ "abc123xyz" r/-\1-/)
We got result "abc-123-xyz".
1.3 Quick Keyword
This one is simple. You can replace #:key with :key.
1.4 Applicable Dictionary
Borrowed from #lang rackjure, we redefines #%app to make dictionaries applicable:
; When (dict? d) is #t (d key) => (dict-ref d key #f) (d key default) => (dict-ref d key default)
Note here that we don’t support rackjure’s set syntax: (d key value). We prefer clojure style. You can also use key as a procedure to retrieve the contents of a dict:
(key d) => (dict-ref d key) (key #f) => #f ; unless (or (procedure? ‘key‘) (dict? ‘key‘))
The reason that #f is returned for the (key #f) is that we can use it together with the threading macro to fetch the contents of nested dicts:
(~> dict 'a 'b 'c)
expands to:
('c ('b ('a dict)))
And is applied as:
(dict-ref (dict-ref (dict-ref dict 'a) 'b) 'c)
1.5 Dictionary initialization with {}
Racket supports writting dict literals as:
#hash((k0 . v0) (k1 . v1))
It is straightforward but requires more typing than:
{k0 v0 k1 v1 ...}
Especially when typing nested lists:
{k0 v0, k1 {key value, key value}}
Note the character , is optional here. In axe , is treated as whitespace if followed by other whitespaces. Thus you can use it as a delimiter whitespace or use it as unquote normally.
Borrowed form rackjure, we provide current-curly-dict parameter to specify the type of dict it expands to.
parameter
(current-curly-dict v) → void? v : procedure?
Examples:
>
(parameterize ([current-curly-dict hasheq]) {'k0 0, 'k1 1}) '#hasheq((k0 . 0) (k1 . 1))
>
(parameterize ([current-curly-dict hasheqv]) {'k0 0 'k1 1}) '#hasheqv((k0 . 0) (k1 . 1))
1.6 Lambda literals
Thanks to rackjure and curly-fn for the idea and implementation.
The clojure reader lets us to define function literals through:
#(+ % %2)
It is equivalent to this in clojure:
(fn [% %2] (+ % %2))
Or in racket:
(λ (% %2) (+ % %2)) (lambda (% %2) (+ % %2))
In the #(...) form, arguments are denoted using identifiers prefixed with %.
% ‹digit›+ is a positional argument.
% on its own is an alias for %1.
%& is a rest argument.
%: ‹id› is a keyword argument.
Racket uses #( ) for vector literals by default. It is overwritten by axe. You can use #[ ] for that if needed. Besides, axe provides other forms for lambda literals: #fn(), #λ( ) and #lambda( ).
>
(sequence->list (map #(+ 1 %) '(1 2 3))) '(2 3 4)
>
(sequence->list (map #fn(+ % %2) '(1 2 3) '(1 2 3))) '(2 4 6)
>
(#lambda(apply list* % %&) 1 '(2 3)) '(1 2 3)
>
(#(* 1/2 %:m (* %:v %:v)) #:m 2 #:v 1) 1
>
(#(* 1/2 %:m (* %:v %:v)) :m 2 :v 1) 1
>
(#(begin %2) 'ignored 'used) 'used
Remember in the above example, we use :m to replace #:m, see Quick Keyword.
There are some pitfalls about lambda literals that normally you should not care about:
Nested lambda literals are not supported, the #( ... ) will be treated as vectors as it is in racket. Other forms such as #fn( ... ) will cause error.
It is safe to write '% and its variants, but not `%. That means quoted % is not treated as argument but not the quasiquote.
You should not use it to write complicated functions. It will be hard to read, use lambda instead.
2 Handy Macros
2.1 Threading Macros
(require axe/threading) | package: axe |
Threading macros are first introduced in clojure. It helps us to change nested function calls into "pipelines". For example: we are calculating some values with a complicated nested function calls:
> (- (bytes-ref (string->bytes/utf-8 (symbol->string 'abc)) 1) 2) 96
> (~> 'abc symbol->string string->bytes/utf-8 (bytes-ref 1) (- 2)) 96
Note that symbol->string and string->bytes/utf-8 are not enclosed in parenthesis.
~> macro also enables you to use placeholder(_) to specify the position of the arguments you want to place. So that you can achieve something like this:
> (~> '(1 2 3) (map sqrt _) (apply + _) (- 20 (* _ 2))) 11.707471260116055
You can also change the symbol for place holder to any identifier you like:
> (require (rename-in axe [_ %]))
> (~> '(1 2 3) (map sqrt %) (apply + %) (- 20 (* % 2))) 11.707471260116055
Threading macro in axe will handle lambda well, so that the following example works correctly.
> (~> 10 (lambda (x) (- x 1))) 9
> (~> 10 (lambda (x) (- x 1)) (lambda (x) (* x x))) 81
If the placeholder _ is included in the body of lambda expression, the ~> macro will instead replace the placeholder with the result of previous "pipeline" and generate a new lambda function.
(~> 10 (lambda (x) (- x 1))) => ((lambda (x) (- x 1)) 10) (~> 10 (lambda (x) (- x _))) => (lambda (x) (- x 10))
So that you can do the following:
> ((~> 10 (lambda (x) (- x _))) 1) -9
Finally, we have some litte hack in threading macro, now you can NOT use [...] or {...} to represent function applications. But we already use {...} for dictionaries, so hope it won’t be too strange for you.
If there are multiple clauses, thread the first clause as the second item in the second clause, recursively.
Examples:
> (and~> '(2 4 5) (map add1 _) (index-where even?) (* 2)) 4
> (and~> '(2 4 6) (map add1 _) (index-where even?) (* 2)) #f
> (map (λ~> add1 (* 2)) (range 5)) #<stream>
syntax
syntax
syntax
syntax
syntax
syntax
syntax
syntax
The threading module, and rackjure had already provide such functionality and good documents. But rackjure do not support placeholder and threading module do not support placeholder in nested function calls like (~> expr (fun1 (func2 _ arg))).
2.2 Conditionals
(require axe/conditionals) | package: axe |
This module provides alternatives to built in if, when and cond.
(let ([val test-expr]) (if val (match-let ([binding-expr test-expr]) then-expr) else-expr))
(let ([val test-expr]) (when val (match-let ([binding-expr test-expr]) body ...)))
(if (not test-expr) then-expr else-expr)
(when (not test-expr) body ...+)
syntax
cond-clause = [test-expr then-body ...+] | [else then-body ...+] | [test-expr => proc-expr] | [test-expr]
> (let ([lst '(x y z a b c)]) (cond-let it [(member 'a lst) (length it)] [else 0])) 3
> (let ([dct #hasheq((a . #f) (b . (1 2)) (c . (10 20)))]) (cond-let (list a b) [(dict-ref dct 'a) (+ a b)] [(dict-ref dct 'b) (- a b)] [(dict-ref dct 'c) (* a b)] [else 0])) -1
3 Utility Functions
3.1 Dictionary
(require axe/dict) | package: axe |
Now axe depends on data/collection, you are safely to use it instead.
axe provide some wrappers over the dictionary related functions provided by racket/dict.
procedure
dict : (and/c dict? immutable?) key : any/c value : any/c
> (dict-extend '((a . 1)) 'b 2 'a 3) '((a . 3) (b . 2))
> (dict-extend #hash() 'a 1 'b 2 'a 3) '#hash((a . 3) (b . 2))
procedure
→ (and/c dict? (not/c immutable?)) dict : (and/c dict? (not/c immutable?)) key : any/c value : any/c
> (dict-extend! (make-hash) 'b 2 'a 3)
procedure
d0 : (and/c dict? immutable?) d1 : dict?
procedure
d0 : (and/c dict? (not/c immutable?)) d1 : dict?
> (dict-merge '((a . 1)) '((b . 2) (c . 3)) '((a . 4) (c . 5))) '((a . 4) (b . 2) (c . 5))
> (define d (make-hash '((a . 1)))) > (dict-merge! d '((b . 2) (c . 3)) #hash((a . 4) (c . 5))) > d '#hash((a . 4) (b . 2) (c . 5))
> (dict-merge-with list #hash((a . 1)) '((b . 2) (c . 3)) '((a . 4) (c . 5))) '#hash((a . (1 4)) (b . 2) (c . (3 5)))
> (dict-merge-with - '((a . 5)) '((b . 2) (c . 3)) '((a . 4) (c . 5))) '((a . 1) (b . 2) (c . -2))