diff --git a/benchmarking/README.md b/benchmarking/README.md index 02c5a48e4..d79b7d762 100644 --- a/benchmarking/README.md +++ b/benchmarking/README.md @@ -56,6 +56,37 @@ Benchmarks can be defined in any Coalton package (that imports or nicknames `#:c Unit))) ``` +Parameterized benchmarks allow for multiple inputs to the same function: + +``` +(define-parameterized-benchmark rec-fib 1000 + (fn (x) + (fib x) + Unit) + (seq:make 10 15 20 25)) + +``` +Using `:detect-convergence?` will add standard deviation information to your run, as well as dynamic sample scaling. + +### Dynamic sample scaling + + +With `:detect-convergence?` selected, benchmarks will run one sample at a time, collecting live standard deviation information for timings: + +``` +(define-parameterized-benchmark rec-fib-generic 1000 + (fn (x) + (fib-generic-wrapped x) + Unit) + (seq:make 10 15 20 25) + :detect-convergence? cl:t) + +``` + +When running this mode, the `samples` input is interpreted as the minimum samples, and the benchmark will continue running until the standard deviation of time converges. This ensures the minimum amount of samples before a reliable average. + +This mode is most effective on benchmarks with significant individual sample times. + ## Running individual benchmarks Individual benchmarks can be run with `#'run-benchmark`, as long as the benchmark is defined in the current package. diff --git a/benchmarking/benchmarking.lisp b/benchmarking/benchmarking.lisp index e488ff0d1..58a2107f6 100644 --- a/benchmarking/benchmarking.lisp +++ b/benchmarking/benchmarking.lisp @@ -12,7 +12,8 @@ (#:list #:coalton-library/list) (#:state #:coalton-library/monad/state) (#:math #:coalton-library/math) - (#:seq #:coalton-library/seq)) + (#:seq #:coalton-library/seq) + (#:str #:coalton-library/string)) (:export ;; settings/options @@ -37,6 +38,9 @@ #:find-benchmark #:define-benchmark + #:ParameterizedBenchmark + #:define-parameterized-benchmark + #:BenchmarkResults #:BenchmarkSystem #:benchmark-system-info @@ -69,7 +73,7 @@ Is `*verbose-benchmarking*` set to `True`?" (declare *benchmark-width* (Cell UFix)) (define *benchmark-width* "This is the printed width of the benchmark table output." - (cell:new 80)) + (cell:new 90)) (declare benchmark-width (Unit -> UFix)) (define (benchmark-width) @@ -134,12 +138,19 @@ Is `*benchmark-sci-notation*` set to `True`?" (name "The name of the `Benchmark`, interned as a symbol in the local package." BenchmarkName) - (iterations + (samples "The number of times the code will be run." UFix) (code "A function to be benchmarked." - (Unit -> Unit))) + (Unit -> Unit)) + (Detect-convergence? + "Should this benchmark detect convergence? + +This enambles standard deviation information and dynamic sample scaling, which treats the number of samples as a minimum number, then checks for a convergence of standard deviation before finishing the run. + +This mode sacrifices time efficiency." + Boolean)) (define-struct BenchmarkSuite "A suite of benchmarks, associated with a Coalton package." @@ -200,7 +211,12 @@ Key is package name." (declare package-benchmarks (String -> (Iterator Benchmark))) (define (package-benchmarks package-name) "Returns an `Iterator` of all benchmarks contained within a specified package." - (hash:values (.benchmarks (unwrap (find-benchmark-suite package-name))))) + (unwrap-or-else + (fn (suite) + (hash:values (.benchmarks suite))) + (fn () + (error "Benchmark suite not found.")) + (find-benchmark-suite package-name))) (declare local-benchmarks (Unit -> (Iterator Benchmark))) (define (local-benchmarks) @@ -214,20 +230,68 @@ Key is package name." (== (.name b) name)) (local-benchmarks))) - (declare %define-benchmark (String -> UFix -> (Unit -> Unit) -> Unit)) - (define (%define-benchmark name iterations fn) + (declare %define-benchmark (String -> UFix -> (Unit -> Unit) -> Boolean -> Unit)) + (define (%define-benchmark name samples fn detect-convergence?) "Define a Coalton `Benchmark` in the local package." (add-benchmark (Benchmark (into name) - iterations - fn)))) + samples + fn + detect-convergence?)))) -(cl:defmacro define-benchmark (name iterations func) +(cl:defmacro define-benchmark (name samples func cl:&key (detect-convergence? cl:nil)) "Define a Coalton `Benchmark` in the local package- called outside of Coalton." (cl:let* ((name (cl:string name))) - `(coalton (%define-benchmark ,name ,iterations ,func)))) + `(coalton (%define-benchmark ,name ,samples ,func (lisp Boolean () ,detect-convergence?))))) + +;;; +;;; Parameterized Benchmarking +;;; + +(coalton-toplevel + + (define-struct (ParameterizedBenchmark :a) + "Parameterized benchmarks allow for multiple inputs." + (name + "The name of the benchmark. This will be combined with an index for each element in the series." + String) + (samples + "The number of samples to take of each benchmark. +If comprehensive, this is the minimum number of samples." + UFix) + (code + "The function to benchmark." + (:a -> Unit)) + (inputs + "A sequence of inputs to run this benchmark on." + (seq:Seq :a)) + (Detect-Convergence? + "Should this be run comprehensively?" + Boolean)) + (define (%add-parameterized-benchmark (ParameterizedBenchmark name samples func inputs detect-convergence?)) + "Adds a benchmark for each ParameterizedBenchmark input." + (for i in (iter:zip! (iter:into-iter inputs) + (iter:range-increasing 1 0 (seq:size inputs))) + (add-benchmark + (Benchmark + (BenchmarkName (str:concat name + (into (snd i)))) + samples + (fn () (func (fst i))) + detect-convergence?))))) + +(cl:defmacro define-parameterized-benchmark (name samples func inputs cl:&key (detect-convergence? cl:nil)) + "Defines a series of benchmarks on a function taking a series of inputs." + (cl:let ((name (cl:string name))) + `(coalton (%add-parameterized-benchmark + (ParameterizedBenchmark + ,name + ,samples + ,func + ,inputs + (lisp Boolean () ,detect-convergence?)))))) ;;; ;;; Benchmark Results ;;; @@ -240,12 +304,18 @@ Key is package name." (name "The name of the `Benchmark`, interned as a symbol in the local package." BenchmarkName) - (iterations + (samples "The number of times the benchmarked function was run." UFix) - (time-elapsed - "The amount of time in internal time units that all iterations took to run." + (total-time-elapsed + "The amount of time in internal time units that all samples took to run." + Integer) + (average-time-elapsed + "The amount of time in internal time units that the average sample took to run." Integer) + (time-std-dev + "The standard deviation of time." + (Optional Double-Float)) (bytes-consed "The amount of space used during the benchmark run." (Optional Integer))) @@ -328,50 +398,62 @@ Key is package name." "Converts time from microseconds to seconds then prunes down to a 10 characters." (if (sci-notation?) (%format-time-scientific rtime) - (%format-time-microseconds rtime)))) + (%format-time-microseconds rtime))) -;;; -;;; Table gathering -;;; - -(coalton-toplevel + (declare %format-time-std-dev (Double-Float -> String)) + (define (%format-time-std-dev sdev) + (let ms = (/ (/ (* 1000000 sdev) + (fromint sys:internal-time-units-per-second)) + 1000)) + (into ms)) - (declare system-header-text (BenchmarkSystem -> (Tuple String String))) + (declare system-header-text (BenchmarkSystem -> (Tuple TableCell TableCell))) (define (system-header-text (BenchmarkSystem architecture os lisp-impl lisp-version release inlining)) "Returns formatted system information for printing purposes." - (Tuple (lisp String (architecture os lisp-impl lisp-version) - (cl:format cl:nil "System: ~a ~a ~a~a" - architecture - os - lisp-impl - lisp-version)) - (lisp String (release inlining) - (cl:format cl:nil "Coalton ~a mode ~a heuristic inlining" - (cl:if release - "release" - "development") - (cl:if inlining - "with" - "without"))))) - - (declare benchmark-column-names (seq:Seq String)) + (Tuple (TableCell (lisp String (architecture os lisp-impl lisp-version) + (cl:format cl:nil "System: ~a ~a ~a~a" + architecture + os + lisp-impl + lisp-version)) + Center) + (TableCell (lisp String (release inlining) + (cl:format cl:nil "Coalton ~a mode ~a heuristic inlining" + (cl:if release + "release" + "development") + (cl:if inlining + "with" + "without"))) + Center))) + + (declare benchmark-column-names (seq:Seq TableCell)) (define benchmark-column-names "The column headers for benchmark table printing." - (seq:make "Benchmark" - "Time (ms)" - "Space (B)" - "# Iterations")) - - (declare column-values (BenchmarkResults -> (seq:Seq String))) - (define (column-values (BenchmarkResults name iterations time-elapsed bytes-consed)) + (seq:make (TableCell "Benchmark" Center) + (TableCell "Time (ms)" Center) + (TableCell "Avg Time (ms)" Center) + (TableCell "Time std dev" Center) + (TableCell "Space (B)" Center) + (TableCell "# Samples" Center))) + + (declare column-values (BenchmarkResults -> (seq:Seq TableCell))) + (define (column-values (BenchmarkResults name samples time-elapsed avg-time time-std-dev bytes-consed)) "Returns the column values for a row of the benchmark table." - (seq:make (the String (into name)) - (format-time time-elapsed) - (unwrap-or-else (fn (x) - (into x)) - (fn () "n/a") - bytes-consed) - (the String (into iterations)))) + (seq:make (TableCell (the String (into name)) Left) + (TableCell (format-time time-elapsed) Right) + (TableCell (format-time avg-time) Right) + (TableCell (unwrap-or-else %format-time-std-dev + (fn () "n/a") + time-std-dev) + Right) + (TableCell (unwrap-or-else (fn (x) + (into x)) + (fn () "n/a") + bytes-consed) + Right) + (TableCell (the String (into samples)) + Right))) (declare package-header (String -> BenchmarkSystem -> String)) (define (package-header name system) @@ -385,6 +467,133 @@ Key is package name." (SecondaryHeader (snd sys)) (TopRow benchmark-column-names)))) +;;; +;;; Detecting convergence? +;;; + +(coalton-toplevel + + (define *benchmarking-convergence-threshold* 0.01d0) + + (define-struct BenchState + "State for a Benchmark Run." + (samples (Cell Integer)) + (times (Vector Integer)) + (total-time (Cell Integer)) + (bytes-consed (Vector (Optional Integer))) + (std-dev (Cell Double-Float))) + + (define-instance (Default BenchState) + (define (default) + (BenchState (cell:new 0) + (vec:new) + (cell:new 0) + (vec:new) + (cell:new 0.0d0)))) + + (declare increment-samples (Unit -> (state:ST BenchState Unit))) + (define (increment-samples) + "Increment the number of samples." + (do + (bench <- state:get) + (pure (cell:increment! (.samples bench))) + (state:put bench))) + + (declare add-time (Integer -> (state:ST BenchState Unit))) + (define (add-time t) + "Add a profiled time to both the vector of times and the total-time." + (do + (bench <- state:get) + (pure (vec:push! t (.times bench))) + (pure (cell:update! (fn (x) (+ t x)) (.total-time bench))) + (state:put bench))) + + (declare add-bytes ((Optional Integer) -> (state:ST BenchState Unit))) + (define (add-bytes b) + (do + (bench <- state:get) + (pure (vec:push! b (.bytes-consed bench))) + (state:put bench))) + + (declare mean-time (Unit -> (state:ST BenchState Double-Float))) + (define (mean-time) + (do + (bench <- state:get) + (pure (math:inexact/ + (cell:read (.total-time bench)) + (cell:read (.samples bench)))))) + + (declare total-bytes-consed (BenchState -> (Optional Integer))) + (define (total-bytes-consed (BenchState _ _ _ bytes-consed _)) + (fold + (Some 0) bytes-consed)) + + (declare mean-bytes-consed (BenchState -> (Optional Double-Float))) + (define (mean-bytes-consed (BenchState samples _ _ bytes-consed _)) + (match (vec:index-unsafe 0 bytes-consed) + ((Some _x) + (Some (math:inexact/ (fold + 0 (map unwrap bytes-consed)) + (cell:read samples)))) + ((None) + None))) + + (declare std-deviation-time (Unit -> (state:ST BenchState Double-Float))) + (define (std-deviation-time) + (do + (bench <- state:get) + (mean <- (mean-time)) + (pure (math:sqrt (math:general/ (fold + 0 (map (fn (x) + (^ (- (fromint x) mean) 2)) + (.times bench))) + (1- (fromint (cell:read (.samples bench))))))))) + + (declare std-deviation-convergence? (Double-Float -> (state:ST BenchState Boolean))) + (define (std-deviation-convergence? new-std-dev) + (do + (bench <- state:get) + (pure (< (abs (- new-std-dev (cell:read (.std-dev bench)))) *benchmarking-convergence-threshold*)))) + + (declare update-std-deviation (Double-Float -> (state:ST BenchState Unit))) + (define (update-std-deviation new-std-dev) + (do + (bench <- state:get) + (pure (cell:write! (.std-dev bench) new-std-dev)) + (state:put bench))) + + (declare profile-benchmark ((Unit -> Unit) -> Integer -> (state:ST BenchState Unit))) + (define (profile-benchmark func min-samples) + (do + (let profile = (sys:spacetime func)) + (bench <- state:get) + (increment-samples) + (add-time (.time-elapsed profile)) + (add-bytes (.bytes-consed profile)) + (cond ((> (cell:read (.samples bench)) min-samples) + (do + (std-dev <- (std-deviation-time)) + (converges? <- (std-deviation-convergence? std-dev)) + (if converges? + (pure Unit) + (do + (update-std-deviation std-dev) + (profile-benchmark func min-samples))))) + (True + (profile-benchmark func min-samples))))) + + (declare %run-comprehensive-benchmark (Benchmark -> BenchmarkResults)) + (define (%run-comprehensive-benchmark (Benchmark name samples code _)) + (let results = (fst (state:run (profile-benchmark code (into samples)) (default)))) + results + + (BenchmarkResults + name + (unwrap (tryinto (cell:read (.samples results)))) + (cell:read (.total-time results)) + (math:round/ (cell:read (.total-time results)) + (cell:read (.samples results))) + (Some (cell:read (.std-dev results))) + (total-bytes-consed results)))) + + ;;; ;;; Running Benchmarks ;;; @@ -392,22 +601,28 @@ Key is package name." (coalton-toplevel (declare %run-benchmark (Benchmark -> BenchmarkResults)) - (define (%run-benchmark (Benchmark name iterations func)) + (define (%run-benchmark (Benchmark name samples func _)) "Runs a `Benchmark`." (let profile = (sys:spacetime (fn () - (for i in (iter:up-to iterations) + (for i in (iter:up-to samples) (func) Unit)))) (BenchmarkResults name - iterations + (into samples) (.time-elapsed profile) + (math:round/ (.time-elapsed profile) + (into samples)) + None (.bytes-consed profile))) (declare run-benchmark (BenchmarkName -> BenchmarkResults)) (define (run-benchmark name) "Runs a `Benchmark` in the current package." - (let ((results (unwrap-or-else %run-benchmark + (let ((results (unwrap-or-else (fn (b) + (if (.detect-convergence? b) + (%run-comprehensive-benchmark b) + (%run-benchmark b))) (fn () (error (lisp String (name) (cl:format cl:nil "No benchmark defined by this name: ~a" name)))) (find-benchmark name))) @@ -433,7 +648,9 @@ Key is package name." (print-item (package-header name system))) (for b in (package-benchmarks name) - (let res = (%run-benchmark b)) + (let res = (if (.detect-convergence? b) + (%run-comprehensive-benchmark b) + (%run-benchmark b))) (when (verbose?) (print-item (coalton-table (benchmark-width) diff --git a/benchmarking/benchmarks/big-float.lisp b/benchmarking/benchmarks/big-float.lisp index 146757bb1..e87a11d0e 100644 --- a/benchmarking/benchmarks/big-float.lisp +++ b/benchmarking/benchmarks/big-float.lisp @@ -88,7 +88,8 @@ (fn () (,func (big-float-bench-precision) ,rand) - Unit))))) + Unit) + False)))) (define-big-float-benchmark big-trig) diff --git a/benchmarking/benchmarks/fibonacci.lisp b/benchmarking/benchmarks/fibonacci.lisp index 1540a22a7..9c21d9dc2 100644 --- a/benchmarking/benchmarks/fibonacci.lisp +++ b/benchmarking/benchmarks/fibonacci.lisp @@ -7,6 +7,8 @@ #:coalton #:coalton-prelude #:coalton-benchmarking) + (:local-nicknames + (#:seq #:coalton-library/seq)) (:export #:lisp-fib #:fib @@ -84,15 +86,18 @@ ;;; Benchmarks ;;; -(define-benchmark rec-fib 1000 - (fn () - (fib 20) - Unit)) - -(define-benchmark rec-fib-generic 1000 - (fn () - (fib-generic-wrapped 20) - Unit)) +(define-parameterized-benchmark rec-fib 1000 + (fn (x) + (fib x) + Unit) + (seq:make 10 15 20 25)) + +(define-parameterized-benchmark rec-fib-generic 1000 + (fn (x) + (fib-generic-wrapped x) + Unit) + (seq:make 10 15 20 25) + :detect-convergence? cl:t) (define-benchmark rec-fib-lisp 1000 (fn () diff --git a/benchmarking/benchmarks/gabriel-benchmarks/takl.lisp b/benchmarking/benchmarks/gabriel-benchmarks/takl.lisp index 8dfe0ae25..f50d9a771 100644 --- a/benchmarking/benchmarks/gabriel-benchmarks/takl.lisp +++ b/benchmarking/benchmarks/gabriel-benchmarks/takl.lisp @@ -77,12 +77,14 @@ (define (takl x y z) (mas (listn x) (listn y) (listn z)))) -(define-benchmark takl 2000 +(define-benchmark takl 500 (fn () (takl 18 12 6) - Unit)) + Unit) + :detect-convergence? cl:t) -(define-benchmark lisp-takl 2000 +(define-benchmark lisp-takl 500 (fn () (takl 18 12 6) - Unit)) + Unit) + :detect-convergence? cl:t) diff --git a/benchmarking/printing.lisp b/benchmarking/printing.lisp index b89be8a44..81879139a 100644 --- a/benchmarking/printing.lisp +++ b/benchmarking/printing.lisp @@ -28,6 +28,11 @@ #:Cross #:Newline + #:Alignment + #:Right + #:Left + #:Center + #:TableCell #:TableComponent #:TopEdge #:TopInternalEdge @@ -82,9 +87,7 @@ (InternalEdge UFix UFix) "An internal edge between rows." (BottomEdge UFix UFix) - "The bottom edge of the table." - (TCell String UFix) - "A Table cell with text and width.") + "The bottom edge of the table.") (declare %column-spacing (UFix -> UFix -> (Tuple UFix UFix))) (define (%column-spacing width columns) @@ -135,55 +138,83 @@ ;; Writing text, cells, headers ;; + (define-type Alignment + "The direction the cell is aligned." + Right + Left + Center) + + (define-struct TableCell + "A table cell with text and alignment." + (text String) + (alignment Alignment)) + (declare %whitespace (UFix -> String)) (define (%whitespace width) "Generates whitespace with a given width." (mconcat (vec:with-initial-element width " "))) - (declare %write-cell (String -> UFix -> String)) - (define (%write-cell cell-text width) + (declare %right-cell (String -> UFix -> String)) + (define (%right-cell text blank) + "Writes text as if to a cell, with appropriate whitespace" + (mconcat (vec:make (%whitespace (1- blank)) text " "))) + + (declare %left-cell (String -> UFix -> String)) + (define (%left-cell text blank) + "Writes text as if to a cell, with appropriate whitespace" + (mconcat (vec:make " " text (%whitespace (1- blank))))) + + (declare %center-cell (String -> UFix -> String)) + (define (%center-cell text blank) + "Writes text as if to a cell, with appropriate whitespace" + (let ((offsets (Tuple (%whitespace (fromint (math:floor/ (into blank) 2))) + (%whitespace (fromint (math:ceiling/ (into blank) 2)))))) + (mconcat (vec:make (fst offsets) text (snd offsets))))) + + (declare %write-cell (TableCell -> UFix -> String)) + (define (%write-cell (TableCell cell-text alignment) width) "Writes text as if to a cell, with appropriate whitespace" ;; this handles text too long for a table cell (let ((text (if (>= (str:length cell-text) width) - (str:substring cell-text 0 (1- width)) + (str:substring cell-text 0 (- width 2)) cell-text)) - (blank (- width (str:length text))) - (offsets (Tuple (%whitespace (fromint (math:floor/ (into blank) 2))) - (%whitespace (fromint (math:ceiling/ (into blank) 2))))) - (out (the (vec:Vector String) (vec:new)))) - (vec:push! (fst offsets) out) - (vec:push! text out) - (vec:push! (snd offsets) out) - (mconcat out))) + (blank (- width (str:length text)))) + (match alignment + ((Right) + (%right-cell text blank)) + ((Left) + (%left-cell text blank)) + ((Center) + (%center-cell text blank))))) ;; ;; ;; - (declare %write-row-component (UFix -> (seq:Seq String) -> TableComponent -> String)) - (define (%write-row-component width column-texts top-edge) - "Writes a full table row of width `width` containing `column-texts`." - (let ((columns (seq:size column-texts)) + (declare %write-row-component (UFix -> (seq:Seq TableCell) -> TableComponent -> String)) + (define (%write-row-component width cells top-edge) + "Writes a full table row of width `width` containing `cells`." + (let ((columns (seq:size cells)) (spacing (%column-spacing width columns)) (out (the (vec:Vector String) (vec:new)))) (vec:push! (render top-edge) out) (vec:push! (render Vertical) out) (vec:push! (%whitespace (snd spacing)) out) - (for txt in column-texts - (vec:push! (%write-cell txt (fst spacing)) out) + (for cell in cells + (vec:push! (%write-cell cell (fst spacing)) out) (vec:push! (render Vertical) out)) (vec:push! (render NewLine) out) (mconcat out))) - (declare %write-top-row (UFix -> (seq:Seq String) -> String)) - (define (%write-top-row width column-texts) + (declare %write-top-row (UFix -> (seq:Seq TableCell) -> String)) + (define (%write-top-row width cells) "Writes the top-row of a table- has no lines crossing above the top." - (%write-row-component width column-texts (TopInternalEdge width (seq:size column-texts)))) + (%write-row-component width cells (TopInternalEdge width (seq:size cells)))) - (declare %write-row (UFix -> (seq:Seq String) -> String)) - (define (%write-row width column-texts) + (declare %write-row (UFix -> (seq:Seq TableCell) -> String)) + (define (%write-row width cells) "Writes a row of a table." - (%write-row-component width column-texts (InternalEdge width (seq:size column-texts)))) + (%write-row-component width cells (InternalEdge width (seq:size cells)))) (define-instance (Render TableComponent) (define (render tc) @@ -195,9 +226,7 @@ ((InternalEdge width columns) (%internal-edge width columns)) ((BottomEdge width columns) - (%bottom-edge width columns)) - ((TCell text width) - (%write-cell text width))))) + (%bottom-edge width columns))))) (define-instance (Render BoxChar) (define (render bc) @@ -233,12 +262,12 @@ (define-struct TableRow "A struct that can be used to generate a printed table row." (width "The width of the table row." UFix) - (column-contents "A vector of column contents." (seq:Seq String))) + (cells "A vector of column contents." (seq:Seq TableCell))) (define-struct TopTableRow "A struct that can be used to generate a printed table row with no row above." (width UFix) - (column-contents (seq:Seq String))) + (column-contents (seq:Seq TableCell))) (define-instance (Render TableRow) (define (render (TableRow width contents)) @@ -250,16 +279,13 @@ (define-instance (Render TableHeader) (define (render (TableHeader width text)) - (let ((blank (the Integer (into (- width (str:length text))))) - (offsets (Tuple (%whitespace (fromint (math:floor/ blank 2))) - (%whitespace (fromint (math:ceiling/ blank 2))))) - (out (the (vec:Vector String) (vec:new)))) - (vec:push! (%top-edge (math:1- width)) out) - (vec:push! (render Vertical) out) - (vec:push! (render (TCell text (math:1- width))) out) - (vec:push! (render Vertical) out) - (vec:push! (render NewLine) out) - (mconcat (as (List String) out)))))) + (mconcat + (vec:make + (%top-edge (math:1- width)) + (render Vertical) + (%write-cell (TableCell text Center) (math:1- width)) + (render Vertical) + (render NewLine)))))) ;;; ;;; Monadic table building @@ -304,19 +330,19 @@ (table <- state:get) (%add-component (TableRow (1- (.width table)) (seq:make text))))) - (declare Row ((seq:Seq String) -> (state:ST TableState Unit))) - (define (Row texts) + (declare Row ((seq:Seq TableCell) -> (state:ST TableState Unit))) + (define (Row cells) "Add a row to the table printout." (do (table <- state:get) - (%add-component (TableRow (.width table) texts)))) + (%add-component (TableRow (.width table) cells)))) - (declare TopRow ((seq:Seq String) -> (state:ST TableState Unit))) - (define (TopRow texts) + (declare TopRow ((seq:Seq TableCell) -> (state:ST TableState Unit))) + (define (TopRow cells) "Add a top row to the table printout (no upward cross characters)." (do (table <- state:get) - (%add-component (TopTableRow (.width table) texts)))) + (%add-component (TopTableRow (.width table) cells)))) (declare Bottom (UFix -> (state:ST TableState Unit))) (define (Bottom columns)