Racket
Table of Contents
- 1. TODO racket blog
- 2. Basic
- 3. Black Magic
- 4. Pattern Matching (racket/match)
- 5. Macros
- 6. Rackunit
- 7. Numbers
- 8. Procedure
- 9. Control Structure
- 10. String
- 11. Regular Expression
- 12. Pair, List, Vector
- 13. Hash Tables
- 14. Sequence
- 15. Hash set (use racket/set)
- 16. structure
- 17. Multiple Values
- 18. Exception
- 19. Concurrency
- 20. IO
- 21. OS
- 22. Trouble shooting
- 23. Logger
A separate racket-lib file describes some interesting libraries.
1 TODO racket blog
A lot of good reading, a good way to pass time.
2 Basic
2.1 Local binding
The local binding is established by let
family. Apart from normal
let
, racket has a second form, known as named let.
(let proc-id ([id init-expr] ...) body ...+)
It first evaluates the init-exprs, the resulting values become arguments to an application of a procedure.
(lambda (id ...) body ...+)
Within the body, proc-id
is bound to the procedure itself.
(let fac ([n 10]) (if (zero? n) 1 (* n (fac (sub1 n)))))
2.2 require
require
introduces bindings. It can only be used in two context, the
top-level context, or the module context (in which it introduce module
bindings).
To require a installed module, use (lib "rel-string")
, and its
widely used shorthand (require id)
where id is the unquoted string.
When requiring a local file, use plain relative (to current directory) path in a string. The path should NOT start or end with a slash. It seems that the suffix is optional.
To use a absolute path, you have to use (file string)
, and
expand-user-path
is called, so you can use:
- relative path
- tide home directory
- absolute path
The #lang
is a shorthand.
#lang racket decl ... ;; equivalent to (module name racket decl ...)
Where name is the file name.
3 Black Magic
http://www.greghendershott.com/2015/07/keyword-structs-revisited.html
(begin-for-syntax (define syntax->keyword (compose1 string->keyword symbol->string syntax->datum)))
4 Pattern Matching (racket/match)
The syntax:
(match val-expr clause ...) clause = [pat [#:when cond-expr] body ...+]
cond-expr is in teh scope of pat (to have the bind or not??).
The clauses are checked one-by-one, and the body of first match will be in the tail position.
Pattern can be
_
to match anything and abandon it.- a single id which matches anything and bind to it. An ID can appear
more than once, in which case the pattern is considered matching
only if all of the bindings of ID are same.
- e.g.
(list a b a)
will not match'(1 2 3)
, but will match'(1 2 1)
- e.g.
- a list which binds to the destruction.
- The quote can not be used to construct list of symbols, it will
match verbatically instead. For that, use quasiquote, which supports
the evaluation and splice-eval.
- e.g.
`(1 ,a ,b)
will match'(1 2 3)
witha
andb
bound.
- e.g.
- hash-table can be used to match the key and values, Using
...
in it means collect into a list.- e.g.
(hash-table ("key1" a) ("key2" b))
. - e.g.
(hash-table (key val) ...)
will match#hash(("a" . 1) ("b" . 2))
, and key will be'("b" "a")
- e.g.
- cons can be used to match pairs
struct-id
can be used to match fields by position. Use(struct struct-id _)
to match an instance of structure itself. E.g.- for structure
(struct tree (val left right))
- pattern
(tree a (tree b _ _) _)
will match (tree 0 (tree 1 #f #f) #f)
- with
a
bound to 0,b
bound to 1
- for structure
(and pat ...)
is used to combine a list of patterns. The typical usage is(and id pat)
where you can bindid
and still check thepat
against the entire value.or
is also available but not that useful.(? expr pat ...)
: combine a predicate and theand
pattern. I.e. first, applyexpr
on the value to match, if#t
, the additionalpat
are matched using the aboveand
pattern.
There are some syntax sugar for matching:
(match-lambda clause ...)
: equivalent to(lambda (id) (match id clause ...))
5 Macros
Matthias Felleisen boils down macros into three main categories:
- Binding form
- Change order of evaluation
- Make DSLs
Different from common lisp where you have compile time and runtime, racket has the concept called level. The level 0 is roughly runtime, and level 1 is compile time. But there're also level -1 and level 2, 3, …, thus it is more general. But typically the first two levels are used.
When using racket syntax, you typically need to require the base
library for it, by (require (for-syntax racket/base))
.
Everything boils down to define-syntax
and syntax-case
.
define-syntax
is nothing fancy. It just define a binding, same as
define, but the binding is in effect at level 1. Thus actually we
typically still define it as a lambda expression, thus it has the
shorthand to write argument (stx) in the same line. syntax-rules
itself is a lambda expression surounding syntax-case
. Thus second
form does not use syntax-rules, but use syntax-case directly.
(define-syntax foo (syntax-rules () ((_ a ...) (printf "~a\n" (list a ...))))) ;; <=> (define-syntax (foo stx) (syntax-case stx () (_ a ...) #'(printf "~a\n" (list a ...))))
syntax-case
match a given syntax object against patterns, and return
another syntax object. It is doing the transformation. You can
actually do the transformation yourself, using sytax->datum
,
operates on it, and use datum->syntax
to convert it back. So
syntax-case
just provides an easier way to do that, in the sense
that you don't need to convert explicitly. Instead, you specify by
position the argument, to match the datum, and construct a syntax
object as a result.
(syntax-case stx-expr (literal-id ...) [pattern result-expr] ...)
Note the result is result-expr
, that means the expr is going to be
executed, and the return value should be a syntax object.
(define-syntax (foo stx) (syntax-case stx () [(_ a b c) #'(if a b c)]))
See, stx is matched against the pattern (_ a b c)
, and
destructed. a b c
can then be used to construct the returned syntax
object. Note, the return must be a syntax object, it replaces the (foo
xxx) and be evaluated. The first is _
because we don't care about
the leading identifier #'foo
.
syntax-rules
is a lambda expression, that calls syntax-case
to
return a syntax object. It is used to define multiple patterns and
templates at one time. Note that the result is a "template" instead of
"expr", meaning it is restricted: cannot run any code, merely return
the template as if quoted. Thus when using syntax-rules, the result
need not be quoted by syntax
.
(syntax-rules (literal-id ...) [(id . pattern) template] ...) ;; <=> (lambda (stx) (syntax-case stx (literal-id ...) [(generated-id . pattern) (syntax-protect #'template)] ...))
define-syntax-rule
is shorthand for define-syntax
and
syntax-rules
. The pattern is a list, the first is an identifier, the
following are pattern variables that matches anything. The template is
the constructed form to replace the old form. It is not quoted,
because it uses syntax-rules to construct. All pattern variables will
be replaced by the actual form.
(define-syntax-rule (id . pattern) template) ;; <=> (define-syntax id (syntax-rules () [(id . pattern) template]))
This is so constrained. The following is equivalent to the above:
(define-syntax-rule (foo a b c) (if a b c))
with-syntax
is often used to nest syntax. It is like let
but is
able to bind pattern variables.
(syntax-case <syntax> () [<pattern> <body>] ...) (syntax-case (list stx-expr ...) () [(pattern ...) (let () body ...+)]) ;; <=> (with-syntax ([<pattern> <stx-expr>] ...) <body> ...+)
5.1 Reader
To understand how macro works, we need to know how the reader handles the program.
A datum is the basic output of a read. Datum can be compound, in which
case the reader is recursively read the components. Some datums are
interned by the reader, i.e. their values are always eq?
when they
are equal?
. Such datums includes: symbols, keywords, strings, byte
strings, regexps, characters, numbers.
Some special read notation:
#(1 2 3)
for vectors#s(struct-id 1 2 3)
for prefab structure types. note that for complex structure, the print format is not intuitive.#hash(("a" . 5) ("b" b))
for hash tables
5.2 Syntax Model
A syntax object is a simple racket value + scope set + phase level.
When require something, those functions are not visible in
level 1. Thus if you want to use those when macro expands, you need
(reqire (for-syntax racket/base))
. Similarly, for-meta
can be
used to specify any number as shift level.
Similaryly, a top-level begin
is not visible in macro, we need
begin-for-syntax
to bind variables to use at level 1.
Use these to expand a macro:
(expand top-level-form)
: fully expand(expand-once top-level-form)
: expand only once
Here's an example from Racket Guide that implements call-by-reference
Should generate
(define (do-f get-a get-b put-a! put-b!) (define-get/put-id a get-a put-a!) (define-get/put-id b get-b put-b!) (swap a b)) (do-f (lambda () x) (lambda () y) (lambda (v) (set! x v)) (lambda (v) (set! y v)))
The test code:
(define-cbr (f a b) (swap a b)) (let ([x 1] [y 2]) (f x y) (list x y))
The actual implementation:
(define-syntax-rule (define-get/put-id id get put!) (define-syntax id (make-set!-transformer (lambda (stx) (syntax-case stx (set!) [id (identifier? (syntax id)) (syntax (get))] [(set! id e) (syntax (put! e))]))))) (define-syntax-rule (define-cbr (id arg ...) body) (begin (define-syntax id (syntax-rules () [(id actual (... ...)) (do-f (lambda () actual) (... ...) (lambda (v) (set! actual v)) (... ...))])) (define-for-cbr do-f (arg ...) () ; explained below... body))) (define-syntax define-for-cbr (syntax-rules () [(define-for-cbr do-f (id0 id ...) (gens ...) body) (define-for-cbr do-f (id ...) (gens ... (id0 get put)) body)] [(define-for-cbr do-f () ((id get put) ...) body) (define (do-f get ... put ...) (define-get/put-id id get put) ... body)]))
The define-for-cbr is pretty tricky, the following with-syntax
is
better:
(define-syntax (define-for-cbr stx) (syntax-case stx () [(_ do-f (id ...) body) (with-syntax ([(get ...) (generate-temporaries #'(id ...))] [(put ...) (generate-temporaries #'(id ...))]) #'(define (do-f get ... put ...) (define-get/put-id id get put) ... body))]))
5.3 Hygienic
A very good writing about syntax-case, and how to (NOT) write non-hygienic macros. http://blog.racket-lang.org/2011/04/writing-syntax-case-macros.html
- a syntax object is a plain datum with some lexical context information
syntax->datum
accepts one syntax object, and return the raw listdatum->syntax
accepts one context syntax object to donor its context, and a plain datum to be converted.- scheme macro is hygienic, i.e.
- if it inserts a binding, it will be renamed through its lexical scope
- if it refers a free variable, it refers to the one in scope in which the definition of the macro happens.
Thus, to break the hygienic
(define-syntax (while stx) (syntax-case stx () [(_ test body ...) (syntax-case (datum->syntax stx 'it) () [it #'(let loop () (let ([it test]) (when it body ... (loop))))])]))
or using with-syntax
to bind pattern variable:
(define-syntax (while stx) (syntax-case stx () [(_ test body ...) (with-syntax ([it (datum->syntax stx 'it)]) #'(let loop () (let ([it test]) (when it body ... (loop)))))]))
This is primarily used to introduce a binding that is visible to the outside world. It seems that syntax parameters can do that better.
6 Rackunit
Since racket has the test
module concept, there needs no unit test
framework. However, it seems that rackunit
provides some
predicate functions.
In racket, each file is a module with the file name as the module
name. You can define a submodule using module*
and module+
. The
former can only appear exactly once for each module, while the latter
can appear multiple times, all of them concatenated into a single
module as if using module*
.
Thus, folks typically use module*
to define a main
module, which
will be run by racket after the enclosing module by
racket
. module+
is used to define test
modules, and will be
executed by raco test
command.
rackunit
provides check APIs and also organize tests into cases and
suites. A check is a simple check, like equality. A test case is a
group of checks. If one of them fails, the following will not be
executed, and the test case fails. A suite is a group of test cases,
and has a name.
Check APIs (all of them accepts an optional message at the end):
check-eq?
check-not-eq?
check-equal?
check-not-equal?
check-pred pred v
: check if apply pred on v will produce other than #fcheck-= v1 v2 epsilon
: |v1-v2| <= epsiloncheck-true v
: #tcheck-false v
: #fcheck-not-flase v
: not #fcheck op v1 v2
: generic form, op is(-> any any any)
fail
: fail unconditionally, useful when developing to mark some tests
The following does not accept message, because they are straightforward:
check-match v pattern
: check if v match pattern
test-begin expr ...
is used to group exprs, while test-case name
body ...+
accept a name for them, and get reported if test fails.
Test suites are not going to run by default. This allows you to
specify which tests to run. There're text (run-tests
in
rackunit/text-ui
) and gui (test/gui
in rackunit/gui
) interfaces
to select tests. Create a suite using (test-suite name-expr test
...)
. The tests can be single check or a test case.
7 Numbers
/
: provide the fraction if given two numbers, not to round it.quotient n m
:(truncate (/ n m))
remainder n m
: seems that the result has the same sign with nmodulo n m
: seems that the result has the same sign with madd1
sub1
abs
max
min
gcd
lcm
: least common multipleround
floor
ceiling
truncate
: towards 0numerator
denominator
Computation
sqrt
expt e p
: e to the power of pexp z
log z [b (exp 1)]
Random
random k
:[0,k)
random min max
:[min,max)
random-seed k
With racket/random
:
random-sample seq n
8 Procedure
The define
keyword can be used to bind a id to a variable, but most
likely you are binding a procedure. So the syntax for arguments
matters.
(define (head args) body ...+) args = arg ... | arg ... . rest-id arg = arg-id | [arg-id default-expr] | keyword arg-id | keyword [arg-id default-expr]
Note how the rest-id are used to implement the ...
by using one dot.
The context matters. In an internal-definition context, a define
binds a local binding. At top level, it introduces top-level
binding.
In application of procedures, apply
will apply the procedure with
content of the list as argument, thus the procedure must accept as
many parameters as the length of list. The list is actually more
flexible, i.e. collected using list*
.
compose
accepts one or more procedures, and composes them by
applying one by one, and fold result into parameter to the next. The
last procedure is applied first. There're two versions, compose
allow arbitrary number of values to be passed between procedure calls,
as long as the number of results and parameters match. compose1
restricts this to exactly one value.
9 Control Structure
if
(cond [test-expr then-body ...+] ...)
(cond cond-clause ...) cond-clause = [test-expr then-body ...+] | [else then-body ...+] | [test-expr => proc-expr] | [test-expr]
and
: A typically trick:(and (some expr) #t)
to return a boolean value- if no expr, return
#t
- one expr, return its value in tail position.
- Multiple exprs
- if first eval to
#f
, return #f - otherwise recursive call with the rest of exprs in tail position.
- if first eval to
- if no expr, return
test-expr => proc-expr
:proc-expr
must produce a procedure that accept exactly one argument, the result oftest-expr
is that argument. The value is returned.test-expr
without a body will return the result oftest-expr
. Not in tail position.(case val-expr [(datum ...) then-body ...+] ...)
: if val-expr matches one of datum, execute the bodywhen
unless
(for ([id seq-expr] #:when guard-expr #:unless guard-expr) body)
for/list
,for/vector
,for/hash
for/and
,for/or
for/sum
,for/product
for/first
,for/last
for/fold
for*
: like for, but with implicit #:when #t between each pair. Thus all clauses are nested.for*
also has the form of different return values.
10 String
The reading syntax of characters starts with #\
, with following
forms
ASCII | name | desc |
---|---|---|
0 | #\null | |
8 | #\backspace | |
9 | #\tab | \t |
10 | #\newline #\linefeed | linefeed (\n), move cursor to next line |
11 | #\vtab | |
12 | #\page | page break |
13 | #\return | carriage return (\r), move cursor to begin |
32 | #\space | |
127 | #\rubout | |
#\<digit8>{3} | Unicode for octal number | |
#\<digit16>{1,4} | Unicode for Hex | |
#\<c> | the single character |
As a side note, windows use \r\n
, Unix use \n
, Mac OS use \r
APIs
make-string k [char]
string-length
string-ref
substring str start [end]
string-copy
string-append
string->list
list->string
string=?
,string<?
, ..string-ci=?
, …string-upcase
,string-downcase
,string-titlecase
,string-foldcase
(normalize for different locale)
With racket/string
:
string-join
string-replace
string-split
string-trim
string-contains?
s containedstring-prefix?
s prefixstring-suffix?
s suffix
With racket/format
:
~a
: accept a value, usingdisplay
. It accepts several keyword arguments:#:separator ""
: the function actually accepts multiple values, each of them is connected with separator#:width
#:max-width
#:min-width
#:limit-marker ""
: if the string is longer than the width, use this as indication of "more".#:align
:(or/c 'left 'center 'right) = 'left
#:pad-string " "
: when width is less than the specified width, this is used to pad
~v
: useprint
instead ofdisplay
. Default separator is " ", default limit-marker is "…"~s
: usewrite
. Default separator is " ", default limit-marker is "…"
Byte string
make-bytes k [b]
bytes-length
bytes-ref
subbytes bstr start [end]
bytes-copy
bytes-append
bytes->list
list->bytes
bytes=?
, …bytes->string/utf-8
bytes->string/locale
bytes->string/latin-1
string->bytes/utf-8
string->bytes/locale
string->bytes/latin-1
11 Regular Expression
#rx"xxx"
: regular expression#px"xxx"
: perl regular expression
Functions:
regexp-quote
: generate a regular expression string that match the string literallyregexp-match
pattern input [start-pos end-pos]: find the pattern in the input. and return a list containing the result (only one). If no match, return #f. If has capture group, return the match and all captured group.regexp-match*
: match multiple times, return list of results.#:match-select
accepts a procedure (defaults tocar
). Examples: values (all), cadrregexp-match-position
: likeregexp-match
, but return list of number pairs, each is a range of [start, end).regexp-match?
: return #t or #fregexp-match-exact?
: return #t only if entire content matches.regexp-split pattern input
: complement ofregexp-match*
regexp-replace pattern input insert
: replace the first match. Match can be referenced by using&
(whole match),\0
(whole match),\n
captured.regexp-replace*
: replace allregexp-replaces input ([pat rep] ...)
: doregexp-replace*
for each replacement in order, chained. Which means latter can operate on former.regexp-replace-quote
: produce string suitable to use as replacement (unquoting\
and&
)
Input port specific:
regexp-try-match
: likeregexp-match
, but if the input is a port, don't read the input on failure.regexp-match-peek
: do not read input ports on both failure and successregexp-match-peek-positions
: return positionsregexp-match-peek-immediate
: non-blocking on input port
(regexp-match #rx"x(.)" "12x4x6") ;; '("x4" "4") (regexp-match* #rx"x(.)" "12x4x6" #:match-select var) ; default ;; '("x4" "x6") (regexp-match* #rx"x(.)" "12x4x6" #:match-select values) ; all ;; '(("x4" "4") ("x6" "6")) (regexp-match* #rx"x(.)" "12x4x6" #:match-select cadr) ;; '("4" "6")
12 Pair, List, Vector
The variants tradition:
- v: use eqv?
- q: use eq?
- f: accept and use a procedure
The APIs:
length
list-ref
list-tail
append
reverse
map
,andmap
,ormap
for-each
foldl
,foldr
filter pred lst
: return list with items that makespred
#t
.remove
sort
member
,memf
(using function): if found, return the tail list starting from the matchfindf
: like memf, but return just the matched element.assoc v lst
: the first element of lst whose car equal to v. E.g.(assoc 1 '((1 2) (3 4)))
returns'(1 2)
. variants:assv
,assq
,assf
from racket/list
empty?
first
rest
second
last
list-update
lst pos updater: the pos index is updated with(updater (list-ref lst pos))
list-set lst pos value
index-of lst v
: return the index of the first vindex-where lst proc
: use functionindexes-of
,indexes-where
: return all matchestake lst pos
: take only the first pos elementsdrop lst pos
: same as list-tailsplit-at lst pos
: same as(values (take lst pos) (drop lst pos))
takef
,dropf
,splitf-at
: take all the elements satisfying the function.take-right
,drop-right
,split-at-right
, and their f-versionlist-prefix? l r
: whether l is prefix of rtake-common-prefix l r
drop-common-prefix l r
split-common-prefix l r
flatten v
check-duplicates lst
remove-duplicates lst
partition prod lst
: return two lists, with items thatprod
evaluates to#t
and#f
respectively. It is the same as
(values (filter pred lst) (filter (negate pred) lst))
range end
: [0,end)range start end [step=1]
shuffle lst
combinations lst [size]
: if size is given, return only combination of length size.permutations lst
argmin proc lst
: return the first elemnt in lst that minimize(proc elem)
argmax
Vectors
vector-length
vector-ref
vector-set!
: it makes sense to set a vector, because it takes constant time to access and updatevector->list
list->vector
vector-fill! vec v
vector-copy! dst dst-start src [src-start] [src-end]
A box is like a single-element vector, typically used as minimal mutable storage.
box
: create a boxbox?
unbox
: return the contentset-box! box v
: return#<void>
box-cas! box old new
: atomically update content from old to new, return#t
. If does not contain old, nothing changed, and return#f
.
From racket/vector
:
vector-map
vector-append
vector-take
,vector-drop
vector-take-right
,vector-drop-right
vector-split-at
,vector-split-at-right
vector-copy
vector-filter
vector-filter-not
vector-count proc vec
vector-argmin
,vector-argmax
vector-member
vector-sort
vector-sort!
13 Hash Tables
(hash key val ... ...)
hash-set hash key v
hash-ref hash key
hash-has-key?
hash-update
hash-remove
hash-clear
hash-keys
hash-values
hash->list
hash-keys-subset? hash1 hash2
: hash1 is a subset of hash2?hash-count hash
hash-empty?
hash-union
: requireracket/hash
14 Sequence
Sequence is designed to be used with for
. Not only list and vectors
are sequence, hash table is also sequence. Dictionary and set are also
sequences. List can also be dictionary type.
sequence?
Constructing sequences
in-range
in-naturals
in-list
in-vector
in-string
in-lines [in=(current-input-port)]
in-hash
in-hash-keys
,in-hash-values
,in-hash-pairs
in-directory [dir use-dir?]
: It is depth first. The path are built, not individual components. Ifdir
is not given, use current dir. If use-dir?with signature (path? . -> any/c)
is given, it acts like as a filter of the results
15 Hash set (use racket/set)
set v ...
: construct a hash setlist->set lst
: construct from listfor/set
set-member?
set-add
set-remove
set-empty?
set-count
set-first
set-rest
set-copy
set-clear
set-union
set-intersect
set-subtract
set=?
subset? st1 st2
: st1 is subset of st2?proper-subset? st1 st2
: strict subsetset->list
in-set
16 structure
struct id maybe-super (field ...) struct-option ... field = field-id | [field-id field-option ...]
The struct
form creates a structure type (unless #:prefab
is
specified), and some names (along with others). Now we use myid
as
the provided id:
struct:myid
: the structure type descriptor, can be used in#:super
optionmyid
: constructor, unless#:constructor-name
option is specifiedmyid?
: predicate proceduremyid-myfield
: accessor procedure for each field
16.1 Field options
There are two available field options:
#:auto
: automatic fields: the constructor does not accept argument for that field, the auto value by#:auto-value
(defaults to#f
) is used.#:mutable
:set-myid-myfield!
: destructively update field. A mutable field is defined in one of two ways: defined for the fields with#:mutable
option, or struct option#:mutable
for all fields. Specify both results in syntax error.
16.2 Subtyping
You can specify super class in one of two ways: maybe-super or via
#:super
option. Specify both results in syntax error. Subtype will
inherit fields, when initialize, initialize those parent fields first.
16.3 Structure options
#:mutable
: same as set#:mutable
for all fields#:super
: same as set maybe-super#:prefab
: means previously fabricated. Also known as predefined, globally shared. Such structure types are globally shared, and they can be print and read back. If it has a super class, obviously it must also be prefab. It is inherently transparent, and cannot have a guard or property. I.e. it cannot be used together with#:transparent
,#:inspector
,#:guard
,#:property
.#:auto-value
: supply one value for all#:auto
fields#:transparent
: shorthand for#:inspector #f
. All structures are by default opaque, thus the print out format does not show any information. If the structure is transparent, the print information can see the data. Theequal?
will also works by recursively compare all fields, while for opaque structures, this require to define generic method forequal?
. However, the prints cannot be read back, to do which the prefab is required.#:inspector
specify an inspector. This is intended for use by debuggers. It is related to reflection, i.e. providing access to structure fields and structure type information.#:guard
specify a guard procedure, or just#f
to turn it off. This is used to filter the arguments to constructor. It accepts n+1 arguments: the n constructor arguments, plus the name of the structure, and return n arguments that is actually used for construction. It is called "guard" in the sense that it can raise exceptions.#:property
: this can be specified multiple times for multiple properties. A property is associated with the type, not the instance. Subtype will inherit property, and can override it. The usage is TODO, and how to retrieve is also TODO.#:methods
: TODO
Other
#:authentic
#:name
#:extra-name
#:constructor-name
#:extra-constructor-name
#:reflection-name
#:omit-define-syntaxes
#:omit-define-values
16.4 Generic Interface
require racket/generic
.
First define the interface.
(define-generics printable (gen-print printable port) (gen-port-print port printable) (gen-print* printable [port] #:width width #:height height))
We are defining a generic id called printable
. The gen:printable
will be the transformer binding used when defining the structure. The
followings are the methods that are supposed to be defined. Note:
there must be a printable
literally in each of these methods. It
does not matter which position, but this particular position should be
kept as the variable in your actual definition. The arguments are
nothing new, including optional variable, default values, as well as
keyword arguments.
Define the structure. To declare that this structure satisfies a
generic interface, specify it in #:methods
. It accepts two values:
gen:name
, and method-defs
. You can supply multiple #:methods
of
course. Each of the def is a define of the function, very normal. Note
that the variable that corresponds to the printable
, by position, is
the data object. Since there cannot be duplicate arguments, you cannot
use this twice (this of course is not likely what you want).
There's a define/generic
that has a fixed form of two arguments,
local-id
and method-id
. The latter can only be one of these
generic method. It is the form used to create a binding. Using just
define cannot create this, because gen-print
will not be in
scope. And define/generic
can only be used here. And interestingly
inside a generic function, the gen-print
is in scope, and can be
bound by a let
expression (why??).
(struct num (v) #:methods gen:printable [(define/generic alias gen-print) (define/generic alias2 gen-print*) ;; (define alias3 gen-print) (define (gen-print n port) (fprintf port "Num: ~a" (num-v n))) (define (gen-port-print port n) (let ([alias2 gen-print]) (gen-print n port) (alias n port) ;; (alias2 n) ;; (alias3 n port) ))])
Use like this:
(gen-port-print (current-output-port) (num 8) )
17 Multiple Values
values produce multiple values value, to consume that, typically use
let-values
, let*-values
, define-values
. Also, binding forms that
can destruct values can also be used.
18 Exception
For now, I only care about how to handle exceptions. To do that:
- call-with-exception-handler f thunk: (f ex)
- with-handlers ([pred-expr handler-expr] …) body …+
(with-handlers ([exn:fail:syntax? (λ (e) (displayln "got a syntax error"))] [exn:fail? (λ (e) (displayln "fallback clause"))]) (raise-syntax-error #f "a syntax error"))
Here's the hierarchy of built-in exceptions
- exn
- exn:fail
- exn:fail:contract
- exn:fail:syntax
- exn:fail:read
- exn:fail:filesystem
- exn:fail:network
- exn:fail:out-of-memory
- exn:fail:unsupported
- exn:fail:user
- exn:break
- exn:fail
To raise an exception, you can use:
raise
: too general, don't use for nowerror
: raise exn:failraise-user-error
raise-syntax-error
19 Concurrency
Comparison
- Thread: all the threads are running parallel, but they run on the same processor.
- Future: can utilize multiple processors
Thread
thread thunk
: create a thread to run, and return immediately with thread descriptor. When thunk terminates, the thread terminates. Threads are managed in current custodian.thread?
current-thread
thread-suspend
thread-resume
kill-thread
break-thread
sleep [secs=0]
: cause the current thread to sleep. 0 simply hint other threads to execute (useful??).thread-running?
thread-dead?
thread-wait thd
: block until thd terminatesthread-send thd v
thread-receive
: block until a v is readythread-try-receive
: non-block version
Parameters are procedures, which optionally accepts one argument. If no argument, get the value. Given the arguement, set the value. This is like a global variable, thus suitable for a command line option storage. The parameters are local to thread, and sub thread inherit parent ones, but not shared. This means setting the parameter will not affect the parameter in other thead (including parent thread).
To make a parameter, simply:
(define aaa (make-parameter #f)) (aaa) ; => #f (aaa 3) (aaa) ; => 3
Parameters are often used by parameterize it in some content, instead of set directly.
(parameterize ([param value-expr] ...) body ...+)
Future (racket/future
)
future thunk
: return the future. It will not run, until touch it.touch f
: blockingly run the future f, and return the result. After touch returns, the results are still hold in the future. You can touch it again and retrieve the same result. Then, how to run in parallel? Create a thread to touch it??current-future
future-enabled?
future?
processor-count
for/async (for-clause ...) body ...+
Places can also use multiple cores. Place enables greater parallelism than future, because it creates a new racket VM, and include separate garbage collection. Thus the setup and communication cost is higher. Places can only communicate through place channels.
20 IO
20.1 ports
20.1.1 General operation
eof
: global variableeof-object?
close-input-port
,close-output-port
current-input-port
,current-output-port
,current-error-port
: can be used to get/set the currentflush-output out
: Input or output ports are both block-buffered by default. Terminal output port is line-buffered. This function cause the port to be flushed immediately
20.1.2 File IO
open-input-file path [#:mode flag]
: return an input port. mode can be'binary
or'text
open-output-file path [#:mode flag #:exists flag]
: exist flag includes- error
- append
- replace: remove old file, create a new one
open-input-output-file path [#:mode flag #:exists flag]
call-with-input-file path proc
: proc is(input-port? . -> . any)
. When proc returns, the port is closed.call-with-output-file path proc
with-input-from-file path thunk
: setcurrent-input-port
to file. As it is similar tocall-with-input-file
, the port is closed when thunk returns.with-output-to-file path thunk
20.1.3 String IO
open-input-string str
: create a string port using stropen-output-string
: create a output string portget-output-string out
: read from a output string port. This should be used with the above method, specifically the out should be(and/c output-port? string-port?)
.
20.1.4 Extra
Requires racket/port
. This is actually the most commonly used
helpers. All of these have bytes counterparts.
port->string
port->lines
display-lines
call-with-output-string proc
: proc:(output-port? . -> . any)
with-output-to-string proc
: proc is(-> any)
call-with-input-string str proc
: proc:(input-port? . -> . any)
with-input-from-string str proc
: proc is(-> any)
20.2 Reading
read-char
read-byte
read-line
read-bytes-line
read
: read a datum from an input portread-syntax
: like read, but produce a syntax object, with source-location information
20.3 Writing
write-char
write-byte
newline
write-string
write-bytes
write
: write a datum so that it can be read backdisplay
: write string without the quotesprint
: this is pretty weird. The existence rationale is that, display and write both have specific output convention. But print has no pre-assumed convention, and the environment is free to modify its behavior.writeln
,displayln
,println
fprintf out form v ...
- out is an output port
- form is a format string.
~n
: new line~a
: display~s
: write~v
: print
printf form v ...
: equivalent tofprintf (current-output-port) form v ...
eprintf form v ...
: print to (current-error-port)format form v ...
: return the string
with racket/pretty
pretty-print
pretty-write
pretty-display
pretty-format
21 OS
(getenv name)
(putenv name value)
In racket/os
gethostname
getpid
21.1 Path
string->path
path->string
build-path base sub ...
absolute-path?
,relative-path?
path->directory-path
: fromx/y
tox/y/
resolve-path
: follow soft link. Note that itself does not expand user path.cleanse-path
: most racket functions clean the path before use, unless it does not access filesystem (i.e. onlyl do a form checking).cleanse-path
,expand-user-path
,simplify-path
are exceptions in the sense that they does not access filesystem, but will do cleanse. But what exactly cleanse does?expand-user-path
: a leading~
is replaced by the user home directory.simplify-path
: nomalize as much as possible. I.e. remove- redundant path separators (except single trailing separator)
..
,.
split-path
: remove the last component (without consideration of trailing/
, as we will see in the 3rd return value), and return 3 values (e.g. "aa/bb/cc/"):- base:
aa/bb/
- name:
cc
- must-be-dir?:
#t
- base:
explode-path
: split path extensively, the first one is rootpath-replace-extension path ext
: extension starts from the last dot.ext
should lead by a dot. If no dot in the path, simply add it.path-add-extension path ext [sep #"_"]
: add the extension. If there's a dot in the path, the last dot will be replaced by sep.
From racket/path
file-name-from-path
path-get-extension
path-has-extension?
file-relative-path base path
: how to do from base TO path(find-relative-path "a/b" "a/b/c/d")
returnsc/d
normalize-path path
: complete, expand (NOT expand-user-path, .. but what??), resolve soft linkssimple-form-path
: complete, then simplify. This is said to be used more often thannormalize-path
.
21.2 File System
find-system-path kind
, where kind is'home-dir
'temp-dir
find-executable-path program
file-exists?
link-exists?
delete-file
rename-file-or-directory old new
file-size
: in bytescopy-file src dest
make-file-or-directory-link to path
: createpath
, link toto
(soft or hard??)current-directory
get or set, this is a parameterdirectory-exists?
make-directory
delete-directory
directory-list [path #:build build?]
: list of all files or directories inpath
. path defaults to current directory, while build? defaults to#f
. If#:build
is#t
, each of the results are built with prefixpath
. Note that this is not recursive, for that, use the sequence generatorin-directory
.
From racket/file
:
file->string
: this READs the file content to a stringfile->value
: READs a single S-expression usingread
. Seems that the file can contain morefile->list path [proc = read]
: reads the file content with proc until EOFfile->lines
: read into lines, without line separatorsdisplay-to-file v path
:display
v
topath
write-to-file v path
:write
v
topath
display-lines-to-file lst path [#:separator sep]
: as name suggests, add line seperatorscopy-directory/files src dest
delete-directory/files
find-files predicate [start-path]
: start-path defaults ot current. Use predicate to filter what should be returned. Seems that this is recursive.make-directory*
: seems to bemkdir -p
make-parent-directory*
: this is very convenient in making a necessary directory to write a filemake-temporary-file [template copy-from-filename directory]
: create it, and return path.- template:
"rkttmp~a"
- copy-from-filename
- a path: the created one is a copy of the path
- #f: which is also default, create an empty file
'directory
: create a directory(!!!) instead
- directory:
#f
, means use default temporary path (/var/tmp
)
- template:
21.3 Networking
I'm not going to dig deep on this because I don't use it. Just listing available functions. Needs require
TCP (racket/tcp
)
tcp-listen port-no
: returntcp-listener?
tcp-connect hostname port-no
: returninput-port?
output-port?
tcp-accept listener
: returninput-port?
output-port?
tcp-close listener
UDP (racket/udp
)
udp-open-socket
udp-bind! udp-socket hostname-string port-no
udp-connect! udp-socket hostname-string port-no
udp-send-to udp-socket hostname port-no bstr
udp-send udp-socket bstr
udp-receive! udp-socket bstr
udp-send-to*
,udp-send*
,udp-receive!*
: non-block- udp-close udp-socket
21.4 Processes
subprocess stdout stdin stderr cmd arg ...
- the command runs ASYNC, it seems that it will run immediately
- If provided a port, it will use that. Otherwise (provide
#f
), it will create one, and get returned. The return value is exactly the same:subprocess? port? port? port? path-string? string?
.#f
means no, no matter as parameter or return value. - stderr can be
'stdout
, in which case the corresponding return value will be#f
- All ports returned must be closed manually
- since the ports have capacity, it is possible to have deadlock
subprocess-wait
: block until subprocess terminatesubprocess-status
: returns either'running
or the exit codesubprocess-kill
subprocess-pid
In racket/system
:
system cmd
: execute cmd through shell command SYNChronously. Return #t for success, #f for failsystem* cmd arg ...
: differ in:- execute directly instead of through shell command
- obviously arguments are provided as arguments instead of in string
system/exit-code cmd
: same assystem
, but the return is exit codesystem*/exit-code cmd arg ...
process cmd
: run ASYNC, through a shell, return (input port, output port, PID, stderr, proc). All ports must be closed manually. The procedureproc
can accept one argument, and is used to interact with the process. The argument can be:'status
: return one of'running
,'done-ok
,'done-error
'exit-code
'wait
: block until terminate'interrupt
: send SIGINT'kill
process* cmd arg ...
: like the difference ofsystem*
withsystem
process/ports out in error-out cmd
: You can provide the ports (the return will be#f
), or provide#f
(the ports are created and returned).process*/ports out in error-out cmd arg ...
21.5 CMD parsing (racket/cmdline)
The command-line
macro actually parse the command line. The
current-command-line-arguments
is actually a parameter that returns
a vector of strings. It is the cmd args that used to run the racket
program. Thus command-line
consumes this value. But since it is a
parameter, you can access it as many times as you want.
All the arguments are actually keyword arguments, but they must appear in order, according to the grammar.
(command-line [name-expr] [argv-expr] flag-clause ... finish-clause)
The flag clauses can be:
#:multi
: flags can appear multiple times#:once-each
: each flag can appear one time#:once-any
: one of the flag can appear#:final
: this is like#:multi
, but no argument is treated as flag any more after it (means they are all left over)
Each of them will be followed by some flag-sepcs
:
flag-spec ::= (flags id ... help-spec body ...+) flags ::= flag-string | (flag-string ...+) help-spec ::= string | (string-expr ...+)
Flags are equivalent, usually to supply -x
and --longer-x
. If
help-spec
is a list of strings, they are printed in separate lines.
The flag-clause
can also be some general printing service, followed
by strings to print
#:usage-help
: this is going to be printed right after the usage of the command#:ps
: insert at the end of the help
Finish clause just use #:args arg-formals body ...+
. It is intended
to handle left over arguments. arg-formals can be just a single ID, in
which case it will be a list of left over arguments. It can also be a
list, which indicates how many left over are expected. The body are
executed and the value of last is returned as the result.
A typical command line parser looks like this. It typically:
- set parameters
- print messages
- return file lists
(define verbose-mode (make-parameter #f)) (define profiling-on (make-parameter #f)) (define optimize-level (make-parameter 0)) (define link-flags (make-parameter null)) (define file-to-compile (command-line #:program "compiler" #:once-each [("-v" "--verbose") "Compile with verbose messages" (verbose-mode #t)] [("-p" "--profile") "Compile with profiling" (profiling-on #t)] #:once-any [("-o" "--optimize-1") "Compile with optimization level 1" (optimize-level 1)] [("--optimize-2") ("Compile with optimization level 2," "which includes all of level 1") (optimize-level 2)] #:multi [("-l" "--link-flags") lf "Add a flag" (link-flags (cons lf (link-flags)))] #:args (filename) filename))
22 Trouble shooting
22.1 racket cannot find browsers
Browsers are declared in sendurl.rkt, with
(define all-unix-browsers '( firefox google-chrome galeon opera mozilla konqueror ;; ... ))
chromium is not in the list, thus
(require net/sendurl) unix-browser-list ;; empty (send-url "google.com") ;; error
The trick is to create a soft link for chromium named "google-chrome". Also, the default is using firefox … So I need to make sure firefox is uninstalled. Is there a better way to configure browser??
The racket-doc
will use the local racket document to search, thus in
order for it to work, install racket-doc
package.
23 Logger
(define lg (make-logger)) (define rc (make-log-receiver lg 'debug)) (current-logger lg) (void (thread (lambda () (let loop () (print (sync rc)) (loop))))) (log-error "error") (log-fatal "fatal") (log-debug "just a debug")
(require racket/logging) (let ([my-log (open-output-string)]) (with-logging-to-port my-log (lambda () (log-warning "Warning World!") (+ 2 2)) 'warning) (get-output-string my-log))