#lang scribble/manual @(require scribble/example racket/sandbox (for-label typed/racket/base "../functions.rkt" dds/utils typed/racket/unsafe)) @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.). @(define functions-evaluator (parameterize ([sandbox-output 'string] [sandbox-error-output 'string] [sandbox-memory-limit 50]) (make-evaluator 'typed/racket #:requires '((submod "functions.rkt" typed))))) @section{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. @examples[#:eval functions-evaluator 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. @examples[#:eval functions-evaluator (: 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]. @examples[#:eval functions-evaluator (: 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. @examples[#:eval functions-evaluator (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]. @examples[#:eval functions-evaluator (tabulate/strict (λ (x y) (and x y)) '((#f #t) (#f #t))) ]} @defproc[(tabulate/untyped [funcs procedure?] [doms (listof list?)]) (listof list?)]{ A version of @racket[tabulate] without type checking. As of 2022-03-06, Typed Racket cannot generate contracts for polymorphic variable-arity functions. This means that @racket[tabulate] cannot be used directly in untyped code and should be replaced by @racket[tabulate/untyped], which is simply an @racket[unsafe-provide] of @racket[tabulate]. @examples[#:eval functions-evaluator (tabulate/untyped (λ (x y) (and x y)) '((#f #t) (#f #t))) ] The contracts in the documentation entry are provided for explanatory purposes and are not actually enforced. Some contracts on the functions @racket[tabulate] uses internally are still checked. For example, trying to tabulate a function of wrong arity will still raise an error. @examples[#:eval functions-evaluator (module tabulate/untyped-test racket/base (require "functions.rkt") (tabulate/untyped (λ (x y z) (and x y z)) '((#f #t) (#f #t)))) (eval:error (require 'tabulate/untyped-test)) ]} @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. @examples[#:eval functions-evaluator (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]. @examples[#:eval functions-evaluator (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]: @examples[#:eval functions-evaluator (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*/untyped [funcs (listof procedure?)] [doms (listof list?)]) (listof list?)]{ A version of @racket[tabulate*] without type checking. As of 2022-03-06, Typed Racket cannot generate contracts for polymorphic variable-arity functions. This means that @racket[tabulate*] cannot be used directly in untyped code and should be replaced by @racket[tabulate*/untyped], which is simply an @racket[unsafe-provide] of @racket[tabulate*]. @examples[#:eval functions-evaluator (tabulate*/untyped (list (λ (x y) (and x y)) (λ (x y) (or x y))) '((#f #t) (#f #t))) ] The contracts in the documentation entry are provided for explanatory purposes and are not actually enforced. Some contracts on the functions @racket[tabulate*] uses internally are still checked. For example, trying to tabulate a function of wrong arity will still raise an error. @examples[#:eval functions-evaluator (module tabulate*/untyped-test racket/base (require "functions.rkt") (tabulate*/untyped (list (λ (x y z) (and x y z))) '((#f #t) (#f #t)))) (eval:error (require 'tabulate*/untyped-test)) ]} @section{Constructing functions} @section{Random functions} @section{Threshold Boolean functions}