dds/scribblings/utils.scrbl
2022-04-07 00:41:35 +02:00

547 lines
16 KiB
Racket
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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{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)))
]}