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)withaandbbound.
- 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-idcan 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
abound to 0,bbound to 1
- for structure
(and pat ...)is used to combine a list of patterns. The typical usage is(and id pat)where you can bindidand still check thepatagainst the entire value.oris also available but not that useful.(? expr pat ...): combine a predicate and theandpattern. I.e. first, applyexpron the value to match, if#t, the additionalpatare matched using the aboveandpattern.
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->datumaccepts one syntax object, and return the raw listdatum->syntaxaccepts 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 madd1sub1absmaxmingcdlcm: least common multipleroundfloorceilingtruncate: towards 0numeratordenominator
Computation
sqrtexpt e p: e to the power of pexp zlog 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-exprmust produce a procedure that accept exactly one argument, the result oftest-expris that argument. The value is returned.test-exprwithout 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 bodywhenunless(for ([id seq-expr] #:when guard-expr #:unless guard-expr) body)for/list,for/vector,for/hashfor/and,for/orfor/sum,for/productfor/first,for/lastfor/foldfor*: 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-lengthstring-refsubstring str start [end]string-copystring-appendstring->listlist->stringstring=?,string<?, ..string-ci=?, …string-upcase,string-downcase,string-titlecase,string-foldcase(normalize for different locale)
With racket/string:
string-joinstring-replacestring-splitstring-trimstring-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: useprintinstead ofdisplay. Default separator is " ", default limit-marker is "…"~s: usewrite. Default separator is " ", default limit-marker is "…"
Byte string
make-bytes k [b]bytes-lengthbytes-refsubbytes bstr start [end]bytes-copybytes-appendbytes->listlist->bytesbytes=?, …bytes->string/utf-8bytes->string/localebytes->string/latin-1string->bytes/utf-8string->bytes/localestring->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-matchpattern 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-selectaccepts 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),\ncaptured.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:
lengthlist-reflist-tailappendreversemap,andmap,ormapfor-eachfoldl,foldrfilter pred lst: return list with items that makespred#t.removesortmember,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?firstrestsecondlastlist-updatelst pos updater: the pos index is updated with(updater (list-ref lst pos))list-set lst pos valueindex-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 rdrop-common-prefix l rsplit-common-prefix l rflatten vcheck-duplicates lstremove-duplicates lstpartition prod lst: return two lists, with items thatprodevaluates to#tand#frespectively. It is the same as
(values (filter pred lst) (filter (negate pred) lst))
range end: [0,end)range start end [step=1]shuffle lstcombinations lst [size]: if size is given, return only combination of length size.permutations lstargmin proc lst: return the first elemnt in lst that minimize(proc elem)argmax
Vectors
vector-lengthvector-refvector-set!: it makes sense to set a vector, because it takes constant time to access and updatevector->listlist->vectorvector-fill! vec vvector-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-mapvector-appendvector-take,vector-dropvector-take-right,vector-drop-rightvector-split-at,vector-split-at-rightvector-copyvector-filtervector-filter-notvector-count proc vecvector-argmin,vector-argmaxvector-membervector-sortvector-sort!
13 Hash Tables
(hash key val ... ...)hash-set hash key vhash-ref hash keyhash-has-key?hash-updatehash-removehash-clearhash-keyshash-valueshash->listhash-keys-subset? hash1 hash2: hash1 is a subset of hash2?hash-count hashhash-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-rangein-naturalsin-listin-vectorin-stringin-lines [in=(current-input-port)]in-hashin-hash-keys,in-hash-values,in-hash-pairsin-directory [dir use-dir?]: It is depth first. The path are built, not individual components. Ifdiris 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/setset-member?set-addset-removeset-empty?set-countset-firstset-restset-copyset-clearset-unionset-intersectset-subtractset=?subset? st1 st2: st1 is subset of st2?proper-subset? st1 st2: strict subsetset->listin-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#:superoptionmyid: constructor, unless#:constructor-nameoption 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#:mutableoption, or struct option#:mutablefor 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#:mutablefor 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#:autofields#: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.#:inspectorspecify 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.#:guardspecify a guard procedure, or just#fto 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-errorraise-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-threadthread-suspendthread-resumekill-threadbreak-threadsleep [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 vthread-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-futurefuture-enabled?future?processor-countfor/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-portcurrent-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'binaryor'textopen-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 procwith-input-from-file path thunk: setcurrent-input-portto 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->stringport->linesdisplay-linescall-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-charread-byteread-lineread-bytes-lineread: read a datum from an input portread-syntax: like read, but produce a syntax object, with source-location information
20.3 Writing
write-charwrite-bytenewlinewrite-stringwrite-byteswrite: 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,printlnfprintf 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-printpretty-writepretty-displaypretty-format
21 OS
(getenv name)(putenv name value)
In racket/os
gethostnamegetpid
21.1 Path
string->pathpath->stringbuild-path base sub ...absolute-path?,relative-path?path->directory-path: fromx/ytox/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-pathare 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.extshould 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-pathpath-get-extensionpath-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 programfile-exists?link-exists?delete-filerename-file-or-directory old newfile-size: in bytescopy-file src destmake-file-or-directory-link to path: createpath, link toto(soft or hard??)current-directoryget or set, this is a parameterdirectory-exists?make-directorydelete-directorydirectory-list [path #:build build?]: list of all files or directories inpath. path defaults to current directory, while build? defaults to#f. If#:buildis#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:displayvtopathwrite-to-file v path:writevtopathdisplay-lines-to-file lst path [#:separator sep]: as name suggests, add line seperatorscopy-directory/files src destdelete-directory/filesfind-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 -pmake-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-socketudp-bind! udp-socket hostname-string port-noudp-connect! udp-socket hostname-string port-noudp-send-to udp-socket hostname port-no bstrudp-send udp-socket bstrudp-receive! udp-socket bstrudp-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?.#fmeans 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'runningor the exit codesubprocess-killsubprocess-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 procedureproccan 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*withsystemprocess/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))