569 lines
17 KiB
Racket
569 lines
17 KiB
Racket
#lang scribble/manual
|
||
@(require scribble/example racket/sandbox
|
||
(for-label typed/racket/base graph
|
||
"../utils.rkt"
|
||
(only-in typed/graph Graph)
|
||
(only-in racket/set set)
|
||
(only-in racket/stream stream->list stream-take)))
|
||
|
||
@title[#:tag "utils"]{dds/utils: Various Utilities}
|
||
|
||
@defmodule[dds/utils]
|
||
|
||
This module defines miscellaneous utilities, supporting the other modules of
|
||
the package: evaluating sexps, manipulating lists,
|
||
@hyperlink["https://orgmode.org/"]{Org-mode} interoperability, etc.
|
||
|
||
@(define utils-evaluator
|
||
(parameterize ([sandbox-output 'string]
|
||
[sandbox-error-output 'string]
|
||
[sandbox-memory-limit 50])
|
||
(make-evaluator 'typed/racket #:requires '("utils.rkt"))))
|
||
|
||
@section{Base types}
|
||
|
||
@defidform[Variable]{
|
||
|
||
Any Racket symbol. Designates a variable in a discrete dynamical network.
|
||
|
||
}
|
||
|
||
@defform[(VariableMapping A)]{
|
||
|
||
An immutable mapping (a hash table) assigning elements of type @racket[A] to
|
||
the variables.
|
||
|
||
}
|
||
|
||
@section{Type utilities}
|
||
|
||
Typed Racket's @racket[cast] should only be used as a last resort, because it
|
||
installs two contracts which may have a significant performance penalty.
|
||
See
|
||
@hyperlink["https://racket.discourse.group/t/managing-cast-performance-penalty/905"]{this
|
||
discussion} for more details. The best approach is to use
|
||
@hyperlink["https://docs.racket-lang.org/ts-guide/occurrence-typing.html"]{occurrence
|
||
typing} either via a direct @racket[if] check using a predicate, or using
|
||
@racket[assert].
|
||
|
||
@defform[(assert-type expr type)]{
|
||
|
||
@racket[assert] that the type of @racket[expr] is @racket[type].
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(define some-number : Any 1)
|
||
(assert-type some-number Integer)
|
||
(assert-type some-number Positive-Integer)
|
||
(eval:error (assert-type some-number Zero1))
|
||
]}
|
||
|
||
@section{Hashtable injection}
|
||
|
||
This section defines some utilities to streamline the usage of hash tables
|
||
mapping symbols to values. The goal is essentially to avoid having to write
|
||
explicit @racket[hash-ref] calls.
|
||
|
||
@defproc[(eval-with [ht (VariableMapping Any)] [expr Any]) AnyValues]{
|
||
|
||
Temporarily injects the mappings from the given hash table as bindings in
|
||
a namespace including @racket[racket/base] and then evaluates the expression.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(let ([ht (hash 'a 1 'b 1)])
|
||
(eval-with ht '(+ b a 1)))
|
||
]
|
||
|
||
The local bindings from the current lexical scope are not
|
||
conserved. Therefore, the following outputs an error about a
|
||
missing identifier:
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(eval:error
|
||
(let ([ht (hash 'a 1 'b 1)]
|
||
[z 1])
|
||
(eval-with ht '(+ b z a 1)))
|
||
)]}
|
||
|
||
@defproc[(eval1-with [ht (VariableMapping Any)] [expr Any]) Any]{
|
||
|
||
Like @racket[eval-with], but returns only the first value computed by
|
||
@racket[expr].
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(let ([ht (hash 'a 1 'b 1)])
|
||
(eval1-with ht '(+ b a 1)))
|
||
]}
|
||
|
||
@defform[(auto-hash-ref/explicit stx)
|
||
#:contracts ([stx (VariableMapping A)])]{
|
||
|
||
Given a @racket[VariableMapping] and a sequence of symbols, binds these symbols
|
||
to the values they are associated with in the hash table, then puts the body in
|
||
the context of these bindings.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(define env #hash((a . 1) (b . 2)))
|
||
(auto-hash-ref/explicit (env a b) (+ a (* 2 b)))
|
||
]
|
||
|
||
Note that only one expression can be supplied in the body.
|
||
|
||
}
|
||
|
||
@defform[(auto-hash-ref/: stx)
|
||
#:contracts ([stx (VariableMapping A)])]{
|
||
|
||
Given an expression and a @racket[VariableMapping], looks up the symbols with
|
||
a leading semicolon and binds them to the value they are associated with in the
|
||
hash table.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(define env #hash((a . 1) (b . 2)))
|
||
(auto-hash-ref/: env (+ :a (* 2 :b)))
|
||
]
|
||
|
||
Thus the symbol @racket[:a] is matched to the key @racket['a] in the
|
||
hash table.
|
||
|
||
Note that only one expression can be supplied in the body.
|
||
|
||
}
|
||
|
||
@section{Analysis of quoted expressions}
|
||
|
||
@defproc[(extract-symbols [form Any]) (Listof Symbol)]{
|
||
|
||
Produces a list of symbols appearing in the quoted expression
|
||
passed in the first argument.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(extract-symbols '(1 (2 3) x (y z 3)))
|
||
]
|
||
|
||
}
|
||
|
||
@section{Org-mode interoperability}
|
||
|
||
Org-mode supports laying out the output of code blocks as tables, which is very
|
||
practical for various variable mappings (e.g., states). However, when the hash
|
||
table maps variables to lists, Org-mode will create a column per list element,
|
||
which may or may not be the desired effect. This section defines some
|
||
utilities for nicer interoperation with Org-mode tables. It also defines some
|
||
shortcuts to reduce the number of words to type when using dds with Org-mode.
|
||
See
|
||
@hyperlink["https://git.marvid.fr/scolobb/dds/src/branch/master/example/example.org"]{example.org}
|
||
for examples of usage.
|
||
|
||
@defproc[(any->string [x Any]) String]{
|
||
|
||
Converts any value to string by calling @racket[display] on it and capturing
|
||
the result in a string.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(any->string '(a 1 (x y)))
|
||
]}
|
||
|
||
@defproc[(stringify-variable-mapping [ht (VariableMapping Any)]) (VariableMapping String)]{
|
||
|
||
Converts all the values of a @racket[VariableMapping] to string.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(stringify-variable-mapping (hash 'a '(and a b) 'b '(not b)))
|
||
]}
|
||
|
||
@defproc[(string->any [str String]) Any]{
|
||
|
||
Reads any value from string.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(string->any "(or b (not a))")
|
||
]}
|
||
|
||
@defproc[(map-sexp [func (-> Any Any)] [sexp Any]) Any]{
|
||
|
||
Given a @racket[Sexp], applies the @racket[func] to any object which is not
|
||
a list.
|
||
|
||
@racket[map-sexp] will not check whether @racket[func] is indeed applicable to
|
||
every non-list element of @racket[sexp]. If this is not the case, a contract
|
||
violation for func will be generated.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(map-sexp (λ (x) (add1 (cast x Number))) '(1 2 (4 10) 3))
|
||
]}
|
||
|
||
@defproc*[([(read-org-sexp [str String]) Any]
|
||
[(unorg [str String]) Any])]{
|
||
|
||
Reads a @racket[sexp] from a string produced by Org-mode for a named table.
|
||
|
||
@racket[unorg] is a shortcut for @racket[read-org-sexp].
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(unorg "(#t \"#t\" \"#t \" '(1 2 \"#f\"))")
|
||
]}
|
||
|
||
@defform[(GeneralPair A B)]{
|
||
|
||
A @racket[(Pair A B)] or a @racket[(List A B)].
|
||
|
||
}
|
||
|
||
@defproc[(unstringify-pairs [pairs (Listof (GeneralPair String Any))])
|
||
(Listof (GeneralPair Symbol Any))]{
|
||
|
||
Given a list of pairs of strings and some other values (possibly strings),
|
||
converts the first element of each pair to a string, and reads the second
|
||
element with @racket[string->any] or keeps it as is if it is not a string.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(unstringify-pairs '(("a" . 1) ("b" . "(and a (not b))")))
|
||
]}
|
||
|
||
@defproc*[([(read-org-variable-mapping [str String]) (VariableMapping Any)]
|
||
[(unorgv [str String]) (VariableMapping Any)])]{
|
||
|
||
Reads a @racket[VariableMapping] from a string, such as the one which Org-mode
|
||
produces from tables.
|
||
|
||
@racket[unorgv] is a synonym of @racket[read-org-variable-mapping].
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(read-org-variable-mapping
|
||
"((\"a\" . \"(and a b)\") (\"b\" . \"(or b (not a))\"))")
|
||
]}
|
||
|
||
@defproc[(read-symbol-list (str String)) (Listof Symbol)]{
|
||
|
||
Reads a list of symbols from a string.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(read-symbol-list "a b c")
|
||
]}
|
||
|
||
@defproc[(drop-first-last (str String)) String]{
|
||
|
||
Removes the first and the last symbol of a given string.
|
||
|
||
Useful for removing the parentheses in string representations of lists.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(drop-first-last "(a b)")
|
||
]}
|
||
|
||
@defproc[(list-sets->list-strings (lst (Listof (Setof Any)))) (Listof String)]{
|
||
|
||
Converts a list of sets of symbols to a list of strings containing
|
||
those symbols.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(list-sets->list-strings (list (set 'x 'y) (set 'z) (set) (set 't)))
|
||
]}
|
||
|
||
@section{Pretty printing}
|
||
|
||
@defproc[(pretty-print-set (s (Setof Any))) String]{
|
||
|
||
Pretty prints a set by listing its elements in alphabetic order.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(pretty-print-set (set 'a 'b 1))
|
||
]}
|
||
|
||
@defproc[(pretty-print-set-sets (ms (Setof (Setof Any)))) String]{
|
||
|
||
Pretty-prints a set of sets of symbols.
|
||
|
||
Typically used for pretty-printing the annotations on the edges of
|
||
a state graph.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(pretty-print-set-sets (set (set 'a 'b) (set 'c)))
|
||
]}
|
||
|
||
@section{Additional graph utilities}
|
||
|
||
All examples in this section depend on @racket[typed/graph]:
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(require typed/graph)
|
||
]
|
||
|
||
@defproc[(dotit [graph Graph]) Void]{
|
||
|
||
Typesets the graph via @racket[graphviz] and @racket[display]s it.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(dotit (weighted-graph/directed '((1 a b) (2 b c))))
|
||
]}
|
||
|
||
@defproc[(update-vertices/unweighted [graph Graph] [func (-> Any Any)]) Graph]{
|
||
|
||
Applies a transformation to every vertex in the unweighted graph and returns
|
||
the new graph.
|
||
|
||
If the transformation function maps two vertices to the same values, these
|
||
vertices will be merged in the resulting graph. The transformation function
|
||
may be called multiple times for the same vertex.
|
||
|
||
This function does not rely on @racket[rename-vertex!], so it can be used to
|
||
permute vertex labels.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(define g (directed-graph '((a b) (b c))))
|
||
(define (double-labels [x : Any])
|
||
(define x-str (symbol->string (cast x Symbol)))
|
||
(string->symbol (string-append x-str x-str)))
|
||
(dotit (update-vertices/unweighted g double-labels))
|
||
]}
|
||
|
||
@defproc[(update-graph [graph Graph]
|
||
[#:v-func v-func (-> Any Any) identity]
|
||
[#:e-func e-func (-> Any Any) identity])
|
||
Graph]{
|
||
|
||
Given a (directed) graph, apply the transformation @racket[v-func] to every
|
||
vertex label and, if the graph is a weighted graph, the transformation
|
||
@racket[e-func] to every edge label. Both transformations default to identity
|
||
functions. If @racket[graph] is an weighted graph, the result is a weighted
|
||
graph. If @racket[graph] is an unweighted graph, the result is an
|
||
unweighted graph.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(define g (weighted-graph/directed '((10 a b) (11 b c))))
|
||
(define (double-labels [x : Any])
|
||
(define x-str (symbol->string (cast x Symbol)))
|
||
(string->symbol (string-append x-str x-str)))
|
||
(define (double-edges [x : Any])
|
||
(* 2 (cast x Number)))
|
||
(dotit (update-graph g #:v-func double-labels #:e-func double-edges))
|
||
]}
|
||
|
||
@section{Additional list and hash map utilities}
|
||
|
||
@defproc[(collect-by-key [keys (Listof a)] [vals (Listof b)])
|
||
(Values (Listof a) (Listof (Listof b)))]{
|
||
|
||
Given a list of keys and the corresponding values, collects all the values
|
||
associated to any given key and returns a list of keys without duplicates, and
|
||
a list containing the corresponding list of values.
|
||
|
||
If @racket[keys] can be treated as edges (i.e. pairs of vertices), the results
|
||
produced by this function are suitable for graph constructors.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(collect-by-key '(a b a) '(1 2 3))
|
||
]}
|
||
|
||
@defproc[(collect-by-key/sets [keys (Listof a)] [vals (Listof b)])
|
||
(Values (Listof a) (Listof (Setof b)))]{
|
||
|
||
Like @racket[collect-by-key], but produce a list of sets instead of a list
|
||
of lists.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(collect-by-key/sets '(a b a) '(1 2 3))
|
||
]}
|
||
|
||
@defproc[(ht-values/list->set [ht (HashTable a (Listof b))])
|
||
(HashTable a (Setof b))]{
|
||
|
||
Converts the values of a hash table from lists to sets.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(ht-values/list->set #hash((a . (1 1))))
|
||
]}
|
||
|
||
@defproc[(hash->list/ordered [ht (HashTable a b)])
|
||
(Listof (Pairof a b))]{
|
||
|
||
|
||
Returns the key-value pairs of a given hash table in the order in which the
|
||
hash table orders them for @racket[hash-map].
|
||
|
||
@bold{TODO:} Remove after Typed Racket has caught up with Racket 8.4, in which
|
||
@racket[hash->list] gets a new optional argument @racket[try-order?].
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(hash->list/ordered #hash((b . 1) (a . 1)))
|
||
]}
|
||
|
||
@defproc[(multi-split-at [lists (Listof (Listof a))]
|
||
[pos Integer])
|
||
(Values (Listof (Listof a)) (Listof (Listof a)))]{
|
||
|
||
Given a list of lists, splits every single list at the given position, and then
|
||
returns two lists of lists: one consisting of the first halves, and the one
|
||
consisting of the second halves.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(multi-split-at '((1 2 3) (a b c)) 2)
|
||
]}
|
||
|
||
@defproc[(lists-transpose [lists (List (Listof a) ... a)])
|
||
(Listof (List a ... a))]{
|
||
|
||
Transposes a list of lists. The length of the resulting list is the length of
|
||
the shortest list in @racket[lists].
|
||
|
||
This function is essentially @racket[in-parallel], wrapped in
|
||
a couple conversions.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(lists-transpose '((a b) (1 2)))
|
||
(lists-transpose '((a b) (1 2 3) (#t)))
|
||
]
|
||
|
||
As of 2022-04-07, Typed Racket cannot convert the type of
|
||
@racket[lists-transpose] to a contract. The @seclink["utils/untyped"]{untyped
|
||
submodule} provides a version of this function which can be used in
|
||
untyped code.
|
||
|
||
}
|
||
|
||
@defproc[(append-lists [lsts (Listof (List (Listof a) (Listof a)))])
|
||
(Listof (Listof a))]{
|
||
|
||
@racket[lsts] is a list of rows, in which each row is split in two halves.
|
||
The function returns the list of the same rows, with the two halves appended.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(append-lists '(((1 2) (a b))
|
||
((3 4) (c d))))
|
||
]}
|
||
|
||
@section{Randomness}
|
||
|
||
@defproc*[([(in-random) (Sequenceof Flonum)]
|
||
[(in-random [k Integer]) (Sequenceof Nonnegative-Fixnum)]
|
||
[(in-random [min Integer] [max Integer]) (Sequenceof Nonnegative-Fixnum)])]{
|
||
|
||
Generates a stream of (inexact) random numbers. The meaning of the arguments
|
||
is the same as for the function @racket[random]:
|
||
|
||
@itemlist[
|
||
|
||
@item{@racket[(in-random)] — a stream of random inexact numbers between
|
||
0 and 1,}
|
||
|
||
@item{@racket[(in-random k)] — a stream of random exact integers in the range
|
||
@racket[0] to @racket[k]-1.}
|
||
|
||
@item{@racket[(in-random min max)] — a stream of random exact integers the
|
||
range @racket[min] to @racket[max]-1.}
|
||
|
||
]
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(require typed/racket/stream)
|
||
(stream->list (stream-take (in-random) 5))
|
||
(stream->list (stream-take (in-random 10) 5))
|
||
(stream->list (stream-take (in-random 5 10) 5))
|
||
]}
|
||
|
||
@section{Additional stream utilities}
|
||
|
||
@defproc[(cartesian-product-2/stream [s1 (Sequenceof a)]
|
||
[s2 (Sequenceof b)])
|
||
(Sequenceof (Pair a b))]{
|
||
|
||
Generates a stream containing all the pairs of the elements from @racket[s1]
|
||
and @racket[s2]. The elements of @racket[s2] are enumerated in order for every
|
||
element of the @racket[s1], taken in order as well.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(require typed/racket/stream)
|
||
(stream->list (cartesian-product-2/stream (in-range 1 5) '(a b)))
|
||
]
|
||
|
||
The streams can be infinite. If the second stream is infinite, only the first
|
||
element of @racket[s1] will be enumerated.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(stream->list (stream-take (cartesian-product-2/stream '(a b) (in-naturals)) 10))
|
||
]}
|
||
|
||
@defproc[(cartesian-product/stream [ss (Listof (Sequenceof a))])
|
||
(Sequenceof (Listof a))]{
|
||
|
||
Generates a stream containing all the elements of the Cartesian product between
|
||
the streams of @racket[ss].
|
||
|
||
This function relies on @racket[cartesian-product-2/stream] to build the
|
||
Cartesian product, so it has the same properties with respect to the order in
|
||
which the streams are enumerated.
|
||
|
||
Union types can be used to build the Cartesian product of streams containing
|
||
values of different types.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(stream->list (cartesian-product/stream (list (in-range 3) (in-range 4 6) '(a b))))
|
||
]}
|
||
|
||
@section{Boolean operations}
|
||
|
||
@defproc[(boolean-power [n Integer])
|
||
(Listof (Listof Boolean))]{
|
||
|
||
Returns the @racket[n]-th Cartesian power of the Boolean domain.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(boolean-power 2)
|
||
]}
|
||
|
||
@defproc[(boolean-power/stream [n Integer])
|
||
(Sequenceof (Listof Boolean))]{
|
||
|
||
Like @racket[boolean-power], but returns a stream.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(stream->list (boolean-power/stream 2))
|
||
]}
|
||
|
||
@defproc[(any->01 [x Any]) (U Zero One)]{
|
||
|
||
Converts any non-@racket[#f] value to 1 and @racket[#f] to 0.
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(any->01 #t)
|
||
(any->01 #f)
|
||
(any->01 'hello)
|
||
]}
|
||
|
||
@defproc[(01->boolean [x (U Zero One)]) Boolean]{
|
||
|
||
Converts 0 to @racket[#f] and 1 to @racket[#t].
|
||
|
||
@examples[#:eval utils-evaluator
|
||
(01->boolean 0)
|
||
(01->boolean 1)
|
||
]}
|
||
|
||
@section[#:tag "utils/untyped"]{Untyped definitions}
|
||
|
||
@defmodule[(submod dds/utils untyped)]
|
||
|
||
@(require (for-label (only-in racket/contract/base listof any/c)))
|
||
|
||
This submodule contains some functions whose types cannot be converted to
|
||
contracts by Typed Racket.
|
||
|
||
@(define utils-evaluator/untyped
|
||
(parameterize ([sandbox-output 'string]
|
||
[sandbox-error-output 'string]
|
||
[sandbox-memory-limit 50])
|
||
(make-evaluator 'racket #:requires '((submod "utils.rkt" untyped)))))
|
||
|
||
@defproc[(lists-transpose [lists (listof (listof any/c))])
|
||
(listof (listof any/c))]{
|
||
|
||
Transposes a list of lists. The length of the resulting list is the length of
|
||
the shortest list in @racket[lists].
|
||
|
||
This function is essentially @racket[in-parallel], wrapped in
|
||
a couple conversions.
|
||
|
||
@examples[#:eval utils-evaluator/untyped
|
||
(lists-transpose '((a b) (1 2)))
|
||
(lists-transpose '((a b) (1 2 3) (#t)))
|
||
]}
|