dds/scribblings/functions.scrbl

732 lines
21 KiB
Racket

#lang scribble/manual
@(require scribble/example racket/sandbox
(for-label typed/racket/base "../functions.rkt" dds/utils
typed/racket/unsafe
(only-in racket stream->list stream-first)))
@(define functions-evaluator
(parameterize ([sandbox-output 'string]
[sandbox-error-output 'string]
[sandbox-memory-limit 500])
(make-evaluator 'typed/racket #:requires '("functions.rkt"))))
@(define-syntax-rule (ex . args)
(examples #:eval functions-evaluator . args))
@(define-syntax-rule (deftype . args)
(defidform #:kind "type" . args))
@title[#:tag "functions"]{dds/functions: Formal Functions}
@defmodule[dds/functions]
This modules provides some definitions for working with functions: tabulating,
(re)constructing from tables, generating random functions, etc.
Some definitions of particular kinds of functions are also provided (threshold
Boolean functions, etc.).
@section[#:tag "pseudovariadic"]{Pseudovariadic functions}
Functions for @seclink["tabulating"]{tabulating functions} take as an argument
a function to tabulate or a list of functions to tabulate. Writing the type of
such functions in Typed Racket and generalizing on the number of the arguments
is hard, and using functions with such types seems even harder.
The @seclink["tabulating"]{following section} contains some examples,
illustrating among other things the difficulties of typing
tabulating functions.
The type of @racket[apply] does not help in this situation, because Typed
Racket treats @racket[apply] in
@hyperlink["https://racket.discourse.group/t/replicating-the-type-of-apply/770/3"]{a
special way}. This means that a user-defined function with the same type as
@racket[apply] and directly calling it will not work in the same way.
@ex[
apply
(define myapply apply)
myapply
(apply (λ (x y) (and x y)) '(#t #f))
(eval:error (myapply (λ (x y) (and x y)) '(#t #f)))
]
One way to work around this issue is to write functions which disguise as
variadic functions of type @racket[(-> a * b)], but which throw an exception
when they receive a number of arguments different from a given constant value.
Such functions are called @italic{pseudovariadic functions} in
this documentation.
@deftogether[(@defform[(pseudovariadic-lambda (id ...+) body ...+)]
@defform[(pvλ (id ...+) body ...+)])]{
Define a pseudovariadic anonymous function.
@ex[
(: f (-> Boolean * Boolean))
(define f (pseudovariadic-lambda (x y) (and x y)))
(f #t #f)
(eval:error (f #t #f #t))
]}
@deftogether[(@defform[(pseudovariadic-define (name id ...+) body ...+)]
@defform[(pvdefine (id ...+) body ...+)])]{
Define a pseudovariadic function called @racket[name].
@ex[
(: g (-> Boolean * Boolean))
(pseudovariadic-define (g x y) (and x y))
(g #t #f)
(eval:error (g #t #f #t))
]}
@section[#:tag "tabulating"]{Tabulating functions}
@defproc[(tabulate [func (-> a ... b)]
[doms (List (Listof a) ... a)])
(Listof (Listof (U Any b)))]{
Given a function @racket[func] and a list of domains @racket[doms] for each of
its arguments, in order, produces a list of lists giving the values of
arguments and the value of the functions for these inputs.
@ex[
(tabulate (λ (x y) (and x y)) '((#f #t) (#f #t)))
]}
@defproc[(tabulate/strict [func (-> a ... b)]
[doms (List (Listof a) ... a)])
(Listof (List (List a ...) (Listof b)))]{
Like @racket[tabulate], but the types of the arguments of @racket[func]
explicitly appear in the return type.
As of 2022-03-06, I am not able to write the type of a list first containing
elements of types @racket[a ...], followed by an element of type @racket[b].
This is why this function returns a list of lists, each containing first a list
of inputs, and then the output of @racket[func].
@ex[
(tabulate/strict (λ (x y) (and x y)) '((#f #t) (#f #t)))
]}
@defproc[(tabulate/pv [func (-> a * b)]
[doms (Listof (Listof a))])
(Listof (Listof (U a b)))]{
Like @racket[tabulate], but @racket[func]
@seclink["pseudovariadic"]{pseudovariadic}.
@ex[
(tabulate/pv (pvλ (x y) (and x y)) '((#f #t) (#f #t)))
]}
@defproc[(tabulate/list [func (-> (Listof a) b)]
[doms (Listof (Listof a))])
(Listof (Listof (U a b)))]{
Like @racket[tabulate/list], but @racket[func] takes its arguments as a list.
@ex[
(tabulate/list (λ ([xs : (Listof Boolean)])
(and (car xs) (car xs)))
'((#f #t) (#f #t)))
]}
@defproc[(tabulate* [funcs (Listof (-> a ... b))]
[doms (List (Listof a) ... a)])
(Listof (Listof (U Any b)))]{
Like @racket[tabulate], but @racket[funcs] is a list of functions taking the
same arguments over the same domains.
@ex[
(tabulate* (list (λ (x y) (and x y))
(λ (x y) (or x y)))
'((#f #t) (#f #t)))
]}
@defproc[(tabulate*/strict [funcs (Listof (-> a ... b))]
[doms (List (Listof a) ... a)])
(Listof (List (List a ...) (Listof b)))]{
Like @racket[tabulate*], but the types of the arguments of the functions
explicitly appear in the return type.
As of 2022-03-06, I am not able to write the type of a list first containing
elements of types @racket[a ...], followed by a list of elements of type
@racket[b]. This is why this function returns a list of lists, each containing
first a list of inputs, and then the list of outputs of @racket[funcs].
@ex[
(tabulate*/strict (list (λ (x y) (and x y))
(λ (x y) (or x y)))
'((#f #t) (#f #t)))
]
The result of @racket[tabulate*] can be obtained by applying
@racket[append-lists]:
@ex[
(require (only-in "utils.rkt" append-lists))
(append-lists (tabulate*/strict (list (λ (x y) (and x y))
(λ (x y) (or x y)))
'((#f #t) (#f #t))))
]}
@defproc[(tabulate*/pv [funcs (Listof (-> a * b))]
[doms (Listof (Listof a))])
(Listof (Listof (U a b)))]{
Like @racket[tabulate*], but the functions in @racket[funcs]
are @seclink["pseudovariadic"]{pseudovariadic}.
@ex[
(tabulate*/pv (list (pvλ (x y) (and x y))
(pvλ (x y) (or x y)))
'((#f #t) (#f #t)))
]}
@defproc[(tabulate*/list [funcs (Listof (-> (Listof a) b))]
[doms (Listof (Listof a))])
(Listof (Listof (U a b)))]{
Like @racket[tabulate*], but the functions in @racket[funcs] take their
arguments as a list.
@ex[
(tabulate*/list (list (λ ([xs : (Listof Boolean)])
(and (car xs) (cadr xs)))
(λ ([xs : (Listof Boolean)])
(or (car xs) (cadr xs))))
'((#f #t) (#f #t)))
]}
@defproc[(tabulate/pv/boolean [arity Positive-Integer] [func (-> Boolean * Boolean)])
(Listof (Listof Boolean))]{
Like @racket[tabulate/pv], but assumes the domains of all variables of the
function are Boolean. The arity of @racket[func] must be explicitly supplied.
@ex[
(tabulate/pv/boolean 2 (pvλ (x y) (and x y)))
]
Explicitly supplying the arity is necessary because the actual arity of
a pseudovariadic function cannot be determined programmatically. Note that
@racket[tabulate] can be applied directly to a function, but the type of
@racket[tabulate] requires a cast is required the domains argument
@racket[doms].
@ex[
(tabulate (λ (x y) (and x y))
(cast (make-list 2 '(#f #t))
(List (Listof Boolean) (Listof Boolean))))
]
This cast is what makes it necessary to resort to pseudovariadic functions and
explicit @racket[arity] to be able to write a type for
@racket[tabulate/pv/boolean].
See also @secref{fuctions/untyped} for simpler, but untyped version of
this function.
}
@defproc[(tabulate*/pv/boolean [arity Positive-Integer]
[func (Listof (-> Boolean * Boolean))])
(Listof (Listof Boolean))]{
Like @racket[tabulate/pv/boolean], but takes a list of functions of the
same arity.
@ex[
(tabulate*/pv/boolean 2 (list (pvλ (x y) (and x y))
(pvλ (x y) (or x y))))
]}
@defproc[(tabulate/pv/01 [arity Positive-Integer] [func (-> (U Zero One) * (U Zero One))])
(Listof (Listof (U Zero One)))]{
Like @racket[tabulate/pv], but assumes the domains of all variables of the
function are @tt{{0,1}}. The arity of @racket[func] must be
explicitly supplied.
@ex[
(tabulate/pv/01 2 (pvλ (x y)
(cast (modulo (+ x y) 2)
(U Zero One))))
]
See @racket[tabulate/pv/boolean] for an explanation of the explicit
@racket[arity] argument.
}
@defproc[(tabulate*/pv/01 [arity Positive-Integer]
[func (Listof (-> (U Zero One) * (U Zero One)))])
(Listof (Listof (U Zero One)))]{
Like @racket[tabulate/pv/01], but takes a list of functions of the same arity.
@ex[
(tabulate*/pv/01 2 `(,(pvλ (x y) (cast (min x y) (U Zero One)))
,(pvλ (x y) (cast (max x y) (U Zero One)))))
]}
@defproc[(tabulate/list/boolean [arity Positive-Integer]
[func (-> (Listof Boolean) Boolean)])
(Listof (Listof Boolean))]{
Like @racket[tabulate/list], but assumes the domains of all variables of the
function are Boolean.
@ex[
(tabulate/list/boolean 2 (λ (xs) (and (car xs) (cadr xs))))
]}
@defproc[(tabulate*/list/boolean [arity Positive-Integer]
[funcs (Listof (-> (Listof Boolean) Boolean))])
(Listof (Listof Boolean))]{
Like @racket[tabulate*/list], but assumes the domains of all variables of the
function are Boolean.
@ex[
(tabulate*/list/boolean 2 (list (λ (xs) (and (car xs) (cadr xs)))
(λ (xs) (or (car xs) (cadr xs)))))
]}
@defproc[(tabulate/list/01 [arity Positive-Integer]
[func (-> (Listof (U Zero One)) (U Zero One))])
(Listof (Listof (U Zero One)))]{
Like @racket[tabulate/list], but assumes the domains of all variables of the
function are @tt{{0,1}}.
@ex[
(tabulate/list/01 2 (λ (xs)
(cast (modulo (+ (car xs) (cadr xs)) 2) (U Zero One))))
]}
@defproc[(tabulate*/list/01 [arity Positive-Integer]
[funcs (Listof (-> (Listof (U Zero One)) (U Zero One)))])
(Listof (Listof (U Zero One)))]{
Like @racket[tabulate*/list], but assumes the domains of all variables of the
function are @tt{{0,1}}.
@ex[
(tabulate*/list/01
2
`(,(λ (xs) (cast (min (car xs) (cadr xs)) (U Zero One)))
,(λ (xs) (cast (max (car xs) (cadr xs)) (U Zero One)))))
]}
@section{Constructing functions}
@defproc[(table->function/list [table (Listof (Listof a))])
(-> (Listof a) a)]{
Given a table like the one produced by the functions of the @racket[tabulate]
family, creates a function which has this behaviour.
More precisely, given a line of @racket[table] without its last element, the
function returned by @racket[table->function/list] produces the corresponding
last element.
@ex[
(define tab : (Listof (Listof Boolean))
'((#f #f #f)
(#f #t #f)
(#t #f #f)
(#t #t #t)))
(define and/list (table->function/list tab))
(and/list '(#f #t))
(and/list '(#t #t))
]}
@defproc[(table->unary-function [table (Listof (List a b))])
(-> a b)]{
Like @racket[table->function/list], but the @racket[table] contains
exactly 2 columns: one column for the inputs and one column for the
outputs, and the result is a unary function.
@ex[
(let ([unary-negation (table->unary-function '((#t #f) (#f #t)))])
(unary-negation #t))
]}
@defproc[(table->function [table (Listof (Listof a))])
(-> a * a)]{
Like @racket[table->function/list], but the resulting function takes a variable
number of arguments rather than a list of values.
@ex[
(define my-and (table->function tab))
(my-and #f #t)
(my-and #t #t)
]}
@defproc[(table->function/pv [table (Listof (Listof a))])
(-> a * a)]{
Like @racket[table->function], but the resulting function raises an explicit
error about invalid arity, instead of the @racket[hash-ref]-related error
raised by the function returned by @racket[table->function]. In other words,
the returned by @racket[table->function/pv] is
@seclink["pseudovariadic"]{pseudovariadic}.
@ex[
(define my-and/pv (table->function/pv tab))
(my-and/pv #f #t)
(eval:error (my-and/pv #f))
(eval:error (my-and #f))
]}
@defproc[(enumerate-boolean-tables [n Positive-Integer])
(Sequenceof (Listof (Listof Boolean)))]{
Returns the stream of the truth tables of all Boolean functions of
arity @racket[n].
There are @tt{2^(2^n)} Boolean functions of arity @racket[n].
@ex[
(require typed/racket/stream)
(stream->list (enumerate-boolean-tables 1))
]}
@defproc[(enumerate-boolean-functions [n Positive-Integer])
(Sequenceof (-> Boolean * Boolean))]{
Returns the stream of all Boolean functions of a given arity @racket[n].
There are @tt{2^(2^n)} Boolean functions of arity @racket[n].
@ex[
(length (stream->list (enumerate-boolean-functions 2)))
]}
@defproc[(enumerate-boolean-functions/pv [n Positive-Integer])
(Sequenceof (-> Boolean * Boolean))]{
Like @racket[enumerate-boolean-functions], but the returned functions are
@seclink["pseudovariadic"]{pseudovariadic}.
@ex[
(define bool-f1/pv (stream-first (enumerate-boolean-functions/pv 2)))
(bool-f1/pv #f #f)
(eval:error (bool-f1/pv #f))
]}
@defproc[(enumerate-boolean-functions/list
[n Positive-Integer])
(Sequenceof (-> (Listof Boolean) Boolean))]{
Like @racket[enumerate-boolean-functions], but the returned functions take
their arguments as a single list.
@ex[
(define bool-f1/list (stream-first (enumerate-boolean-functions/list 2)))
(bool-f1/list '(#f #f))
]}
@section{Random functions}
@defproc[(random-boolean-table [n Positive-Integer]) (Listof (Listof Boolean))]{
Generates a random truth table for a Boolean function of arity @racket[n].
@ex[
(random-boolean-table 2)
]}
@defproc[(random-boolean-function [n Positive-Integer]) (-> Boolean * Boolean)]{
Generates a random Boolean function of arity @racket[n].
@ex[
(define random-bool-f (random-boolean-function 2))
(random-bool-f #t #f)
]}
@defproc[(random-boolean-function/list [n Positive-Integer]) (-> (Listof Boolean) Boolean)]{
Like @racket[random-boolean-function], but the constructed function takes
a list of arguments.
@ex[
(define random-bool-f/list (random-boolean-function/list 2))
(random-bool-f/list '(#t #f))
]}
@section[#:tag "tbf"]{Threshold Boolean functions}
@defstruct*[tbf ([weights (Vectorof Real)] [threshold Real])]{
A threshold Boolean function (TBF) is a pair @tt{(w, θ)}, where @tt{w} is
a vector of weights and @tt{θ} is the threshold.
Instances of @racket[tbf] have the type @racket[TBF].
}
@deftype[TBF]{
The type of the instances of @racket[tbf]:
@ex[
(tbf #(1 2) 3)
]}
@deftogether[(@defproc[(tbf-w [t TBF]) (Vectorof Real)]
@defproc[(tbf-θ [t TBF]) Real])]{
Shortcuts for @racket[tbf-weights] and @racket[tbf-threshold].
}
@defproc[(boolean->01/vector [bool-v (Vectorof Boolean)])
(Vectorof (U Zero One))]{
Converts a Boolean vector to a vector of zeros and ones.
@ex[
(boolean->01/vector #(#t #f #f))
]}
@defproc[(apply-tbf [t TBF] [inputs (Vectorof (U Zero One))]) (U Zero One)]{
Applies the TBF to its inputs.
Applying a TBF consists in multiplying the weights by the corresponding inputs
and comparing the sum of the products to the threshold. If the product is
above the threshold, the function is 1, otherwise it is 0.
@ex[
(define simple-tbf (tbf #(2 -2) 1))
(tabulate/pv/01 2 (pvλ (x y) (apply-tbf simple-tbf (vector x y))))
]}
@defproc[(apply-tbf/boolean [t TBF] [inputs (Vectorof Boolean)]) Boolean]{
Like @racket[apply-tbf], but takes Boolean values as inputs and outputs
a Boolean value.
@ex[
(define simple-tbf (tbf #(2 -2) 1))
(tabulate/pv/boolean 2 (pvλ (x y) (apply-tbf/boolean simple-tbf (vector x y))))
]}
@defproc[(list->tbf [lst (Listof Real)]) TBF]{
Converts a list of numbers to a TBF. The last element of the list is taken to
be the threshold, while the other elements are taken to be the weights.
@ex[
(list->tbf '(1 2 3))
]}
@defproc[(lists->tbfs [lsts (Listof (Listof Real))]) (Listof TBF)]{
Converts multiple lists of numbers to a list of TBFs.
The main use is for reading TBFs from Org-mode tables read by
@racket[read-org-sexp].
@ex[
(lists->tbfs '((1 2 3) (2 3 4)))
]}
@defproc[(read-org-tbfs [str String] [#:headers headers Boolean #f])
(Listof TBF)]{
Reads a list of TBF from an Org-mode string containing a sexp, containing
a list of lists of numbers. If headers is @racket[#t], drops the first list,
supposing that it contains the headers of the table.
The input is typically what @racket[read-org-sexp] reads.
@ex[
(read-org-tbfs "((1 2 1) (1 0 1))")
(read-org-tbfs "((x y f) (1 2 1) (1 0 1))" #:headers #t)
]}
@defproc[(tbf-tabulate* [tbfs (Listof TBF)])
(Listof (Listof (U Zero One)))]{
Tabulates a list of TBFs.
The result is a list of lists describing the truth table of the given TBFs.
The first elements of each line give the values of the inputs, while the last
elements give the values of each the functions corresponding to the input.
All the TBFs in @racket[tbfs] must have the same number of inputs as the first
TBF in the list.
@ex[
(tbf-tabulate* (list (tbf #(2 2) 1) (tbf #(1 1) 1)))
]}
@defproc[(tbf-tabulate [t TBF])
(Listof (Listof (U Zero One)))]{
Tabulates a single TBF.
@ex[
(tbf-tabulate (tbf #(1 2) 1))
]}
@defproc[(tbf-tabulate*/boolean [tbfs (Listof TBF)])
(Listof (Listof Boolean))]{
Tabulates a list of TBFs like @racket[tbf-tabulate*], but uses Boolean values
@racket[#f] and @racket[#t] instead of 0 and 1.
All the TBFs in @racket[tbfs] must have the same number of inputs as the first
TBF in the list.
@ex[
(tbf-tabulate*/boolean (list (tbf #(1 2) 1)))
]}
@defproc[(sbf? [t TBF]) Boolean]{
A sign Boolean function (SBF) is a TBF whose threshold is 0.
@ex[
(sbf? (tbf #(1 2) 3))
(sbf? (tbf #(1 2) 0))
]}
@defproc[(sbf [w (Vectorof Real)]) TBF]{
Creates a TBF which is an SBF from a vector of weights.
@ex[
(sbf #(1 -1))
]}
@defproc[(list->sbf [lst (Listof Real)])
TBF]{
Converts a list of numbers to an SBF. The elements of the list are taken to be
the weights of the SBF.
@ex[
(list->sbf '(1 -1))
]}
@defproc[(read-org-sbfs [str String] [#:headers headers Boolean #f])
(Listof TBF)]{
Reads a list of SBF from an Org-mode string containing a sexp, containing
a list of lists of numbers. If headers is @racket[#t], drops the first list,
supposing that it contains the headers of the table.
The input is typically what @racket[read-org-sexp] reads.
@ex[
(read-org-sbfs "((1 1) (1 -1))")
]
See also @racket[read-org-tbfs].
}
@section[#:tag "fuctions/untyped"]{Untyped definitions}
@defmodule[(submod dds/functions untyped)]
@(require (for-label (only-in racket/contract/base listof any/c)
(for-label (only-in (submod "../functions.rkt" untyped)
tabulate/boolean tabulate*/boolean
tabulate/01 tabulate*/01))))
This submodule contains some functions which cannot be typed or some functions
for which Typed Racket cannot produce contracts, i.e. polymorphic functions of
variable arity. The definitions in this submodule specifically target untyped
user code.
Since the names of some of the definitions in this submodule are the same in
the main module, and since they are imported in the same namespace for
rendering this document, some references to untyped definitions may wrongfully
point to typed definitions. As a tentative fix, all such references are
accompanied by the explicit mention "untyped".
@(define functions-evaluator/untyped
(parameterize ([sandbox-output 'string]
[sandbox-error-output 'string]
[sandbox-memory-limit 50])
(make-evaluator 'racket #:requires '((submod "functions.rkt" untyped)))))
@(define-syntax-rule (ex/untyped . args)
(examples #:eval functions-evaluator/untyped . args))
@defproc[(tabulate [funcs procedure?]
[doms (listof list?)])
(listof list?)]{
Given a function @racket[func] and a list of domains @racket[doms] for each of
its arguments, in order, produces a list of lists giving the values of
arguments and the value of the functions for these inputs.
@ex/untyped[
(tabulate (λ (x y) (and x y)) '((#f #t) (#f #t)))
]}
@defproc[(tabulate* [funcs (listof procedure?)]
[doms (listof list?)])
(listof list?)]{
Like @racket[tabulate] (untyped), but @racket[funcs] is a list of functions
taking the same arguments over the same domains.
@ex/untyped[
(tabulate* (list (λ (x y) (and x y))
(λ (x y) (or x y)))
'((#f #t) (#f #t)))
]}
@defproc[(tabulate/boolean [func procedure?]) (listof (listof boolean?))]{
Like @racket[tabulate] (untyped), but assumes the domains of all variables of
the function are Boolean. @racket[func] must have a fixed arity. It is an
error to supply a function of variable arity.
@ex/untyped[
(tabulate/boolean (lambda (x y) (and x y)))
]}
@defproc[(tabulate*/boolean [funcs (non-empty-listof procedure?)])
(listof (listof boolean?))]{
Like @racket[tabulate/boolean], but takes a list of functions of the
same arity.
@ex/untyped[
(tabulate*/boolean `(,(λ (x y) (and x y))
,(λ (x y) (or x y))))
]}
@defproc[(tabulate/01 [func procedure?]) (listof (listof (or/c 0 1)))]{
Like @racket[tabulate] (untyped), but assumes the domains of all variables of
the function are @tt{{0,1}}. @racket[func] must have a fixed arity. It is an
error to supply a function of variable arity.
@ex/untyped[
(tabulate/01 (λ (x y) (modulo (+ x y) 2)))
]
The same remarks apply as for @racket[tabulate/boolean] (untyped).
}
@defproc[(tabulate*/01 [funcs (listof procedure?)]) (listof (listof (or/c 0 1)))]{
Like @racket[tabulate/01], but takes a list of functions of the same arity.
@ex/untyped[
(tabulate*/01 `(,(λ (x y) (min x y)) ,(λ (x y) (max x y))))
]}