dds/scribblings/utils.scrbl

258 lines
7.3 KiB
Racket

#lang scribble/manual
@(require scribble/example racket/sandbox
(for-label racket/base graph "../utils.rkt"
(only-in typed/racket/base
Any AnyValues Listof String Sexp Number Pair
List -> compose
cast)))
@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/base #: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
(let ([ht #hash((a . 1) (b . 2))])
(auto-hash-ref/explicit (ht 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
(let ([ht #hash((a . 1) (b . 2))])
(auto-hash-ref/: ht (+ :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{Composing typed functions}
Typed Racket's @racket[compose] only takes two arguments, because in general it
is difficult to specify that the return types and the argument types should be
the same for two successive functions in the argument list. This section
defines some further utilities which make using @racket[compose]
more comfortable.
@defproc[(compose-n [proc (-> a a)] ...) (-> a a)]{
Compose an arbitrary number of functions of type @racket[(-> a a)].
@examples[#:eval utils-evaluator
((compose-n add1 add1 add1) 3)
]}
@defproc*[([(compose-3 [proc1 (-> c d)] [proc2 (-> b c)] [proc3 (-> a b)]) (-> a d)]
[(compose-4 [proc1 (-> d e)] [proc2 (-> c d)] [proc3 (-> b c)] [proc4 (-> a b)]) (-> a e)])]{
@racket[compose-i] composes @racket[i] functions. The rightmost function is
applied first.
@examples[#:eval utils-evaluator
(define (s->n [x : String]) (cast (string->number x) Number))
(define fancy-add1 (compose-3 print add1 s->n))
fancy-add1
(fancy-add1 "1")
]}
@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))\"))")
]}
@section{Additional graph utilities}
@section{Pretty printing}
@section{Additional list and hash map utilities}
@section{Functions and procedures}
@section{Randomness}
@section{Additional stream utilities}
@section{Boolean operations}