diff --git a/_02_.Writing-programs.sz b/_02_.Writing-programs.sz index cb3f203..292041f 100644 --- a/_02_.Writing-programs.sz +++ b/_02_.Writing-programs.sz @@ -612,61 +612,38 @@ For one line of output, an interpolated string or @code{println*} is enough. @co When the shape of the output matters, such as columns that line up, padding, a paragraph wrapped to a width or a table, use the layout part of @code{(std fmt)}. This section is a guide to it. The module's page has the reference. -@subsubsection{A block and a vec of blocks} +@subsubsection{Blocks} -Layout is made of two types, and nearly every type error in layout code is one of them where the other is wanted: +Layout is made of blocks. A @code{Block} is a piece of output, and every function that lays something out takes its content as a block and gives back a block. These are blocks: @read-list{ - - A @code{Block} is one piece of output: a string, an int, a char, or a combinator such as @code{(padded/left 5 ["x"])}. - - A @code{(Vec Block)} is pieces of output in order, written as a vec literal: @code{["x = " 7 nl]}. + - A string, an int or a char. Written out, or the value of a variable or a call: @code{"tea"}, @code{name}, @code{(string-upcase name)}. + - A vec of blocks: the blocks one after another. @code{["x = " n nl]} is a string, an int and a newline. + - What a function in @code{(std fmt)} gives back: @code{(padded/left 5 "x")}, @code{nl}, @code{nothing}. + - An @code{if}, @code{cond}, @code{match} or @code{let} whose results are blocks. } -There is one rule. Every combinator takes its content as a @code{(Vec Block)} and gives back one @code{Block}. @code{show}, @code{render} and @code{render->string}, which write the output, take a @code{(Vec Block)}. So the brackets go around content, and a combinator call is one element in them: - -@codeblock[#:lang "bjolang"]|{ -(show [(padded/left 5 ["x"])]) ;; " x" -(show [(padded/left 5 ["x"]) (padded/left 5 ["y"]) nl]) ;; " x y", then a newline -(show ["Total:" (padded/left 8 [42]) nl]) ;; "Total: 42" -}| - -Read @code{(padded/left 5 ["x"])} as "pad this content to 5 columns". The content is a vec even when it is one thing, as the body of a @code{begin} is a body even when it is one form. These are refused: - -@codeblock[#:lang "bjolang"]|{ -(show (padded/left 5 "x")) ;; "x" is not content, and show wants a vec -(show (padded/left 5 ["x"])) ;; show wants a vec, and this is one block -(show [["x"]]) ;; a vec in a vec: a vec is not a block -}| - -More brackets do not help. A vec of vecs is right in one place only: a table, which is a vec of rows or of columns, each of them a @code{(Vec Block)}. - -Nothing is written until @code{show} runs the blocks. A block is a value: you can bind it, pass it to a function and return it. - -@subsubsection{What goes in the brackets} - -A vec literal is read element by element when it stands where a @code{(Vec Block)} is wanted: as an argument of a function that takes one, as the return value of a function declared to return one, or as the value of a binding whose type is written. Each element is one of these: - -@read-table{ -| Element | Is read as | -|--------------------------------------------------------------------------------+---------------------------------------------| -| A string, an int or a char: a literal, a variable or a call | @code{(BStr s)}, @code{(BInt n)}, @code{(BChar c)} | -| A @code{Block}: @code{nl}, @code{nothing}, a combinator call, a variable of type @code{Block} | itself | -| An @code{if}, @code{cond}, @code{match} or @code{let} whose results are any of these | the result it gives | -} - -So variables and calls go in as they are: +So one thing needs no brackets, and several things are a vec: @codeblock[#:lang "bjolang"]|{ (def name "Ada") (: n int) (def n 3) + +(show "Hello\n") ;; Hello +(show (padded/left 6 name)) ;; " Ada" +(show (padded/left 8 ["x = " n])) ;; " x = 3" +(show [(padded/left 5 "x") (padded/left 5 "y") nl]) ;; " x y", then a newline (show ["Hello, " name "! You have " n " new messages" #\. nl]) ;; Hello, Ada! You have 3 new messages. - -(show [(string-upcase name) " " (vec-length [1 2 3]) nl]) ;; ADA 3 -(show ["Paid: " (if paid? "yes" "no") nl]) ;; Paid: yes -(show ["Title: " (match title ((Some t) t) (None "-")) nl]) ;; Title: Dr }| +Read @code{(padded/left 6 name)} as "pad @code{name} to 6 columns", and @code{(padded/left 8 ["x = " n])} as "pad these pieces, together, to 8 columns". A vec in a vec is a block in a block, so extra brackets change nothing. + +Nothing is written until @code{show} runs the block. A block is a value: you can bind it, pass it to a function and return it. + +@subsubsection{What is not a block} + Anything else is converted first: @read-table{ @@ -675,40 +652,19 @@ Anything else is converted first: | A double @code{x} | @code{(numeric x)}, or @code{(numeric x #:precision 2)} | | A bool @code{b} | @code{(if b "yes" "no")} | | A long @code{n} | @code{(long->string n)}, or @code{(bytes n)} for a size | -| A @code{(Vec Block)} @code{bs} | @code{(each bs)} | -| A vec literal | @code{(each [...])}, or its elements in the outer vec | -| A @code{(Vec string)} @code{xs} | @code{(each (vec-map BStr xs))} | -| A @code{(List %a)} @code{xs} | @code{(joined f xs [", "])}, where @code{f} makes the block of one element | +| A @code{(Vec string)} @code{xs} | @code{(vec-map BStr xs)} | +| A @code{(List %a)} @code{xs} | @code{(joined f xs ", ")}, where @code{f} makes the block of one element | } -@strong{A number bound by @code{def}.} The @code{(: n int)} above is needed. @code{(def n 3)} alone does not say which kind of number @code{n} is, as it could still become a long or a double, so @code{[n]} cannot read it as a block, and the compiler says so at @code{n}. The same holds for @code{(def xs [1 2 3])} and @code{(def total (+ 1 2))}. A number that comes from a typed parameter, a record field or a function such as @code{vec-length} has its type already, and a number written in the vec itself, as in @code{["n = " 3]}, is an int. +@code{BStr}, @code{BInt}, @code{BChar} and @code{BMany} are the cases of @code{Block}. You rarely write them, but like any constructor they are functions, so @code{(vec-map BStr names)} is a vec of blocks. -@strong{A vec bound by @code{def}.} @code{(def parts ["a" "b"])} is a @code{(Vec string)}, which is not a @code{(Vec Block)}: write its type, and it is read as blocks. +@strong{A number bound by @code{def}.} The @code{(: n int)} above is needed. @code{(def n 3)} alone does not say which kind of number @code{n} is, as it could still become a long or a double, so it cannot be read as a block, and the compiler says so at @code{n}. The same holds for @code{(def xs [1 2 3])} and @code{(def total (+ 1 2))}. A number that comes from a typed parameter, a record field or a function such as @code{vec-length} has its type already, and a number written in the vec itself, as in @code{["n = " 3]}, is an int. -@codeblock[#:lang "bjolang"]|{ -(: parts (Vec Block)) -(def parts ["a" 1 #\b]) -(show parts) ;; a1b -(show ["<" (each parts) ">" nl]) ;; -}| - -@subsubsection{From one type to the other} - -@read-table{ -| You have | You want a @code{Block} | You want a @code{(Vec Block)} | -|-------------------------+-------------------------------------+-------------------------------| -| a string @code{s} | @code{(BStr s)} | @code{[s]} | -| an int @code{n} | @code{(BInt n)} | @code{[n]} | -| a block @code{b} | @code{b} | @code{[b]} | -| blocks @code{bs} | @code{(each bs)} | @code{bs} | -| pieces | @code{(each [name ": " n])} | @code{[name ": " n]} | -} - -Outside a vec literal nothing is converted: a function declared to return a @code{Block} returns @code{(BStr s)} or @code{(each [s])}, not @code{s}. @code{BStr}, @code{BInt} and @code{BChar} are the cases of @code{Block}, and like any constructor they are functions, so @code{(vec-map BStr names)} is a @code{(Vec Block)}. +@strong{Where a block is wanted.} A string or a vec is read as a block where the compiler knows that a block is wanted: as an argument, as what a function declared to return a @code{Block} returns, and as the value of a binding whose type is written, such as @code{(: parts Block)}. A @code{def} without a type is what its value is: @code{(def parts ["a" "b"])} is a @code{(Vec string)}, and @code{(show parts)} is refused. Write @code{(: parts Block)} above it. @subsubsection{Blocks from your data} -Most layout code is a function from a value of your own to a block. Give it a signature: the signature is what tells the compiler that the literal inside is blocks. +Most layout code is a function from a value of your own to a block. Give it a signature: the signature is what tells the compiler that what it returns is a block. @codeblock[#:lang "bjolang"]|{ (type (: Item (Record (: name string) (: price double) (: stock int)))) @@ -721,12 +677,12 @@ Most layout code is a function from a value of your own to a block. Give it a si (: item-line (-> Item Block)) (defun (item-line i) - (each [(padded/right 10 [(record-ref i name)]) - (padded/left 9 [(numeric (record-ref i price) #:precision 2 #:comma 3)]) - (if (= (record-ref i stock) 0) " sold out" nothing) - nl])) + [(padded/right 10 (record-ref i name)) + (padded/left 9 (numeric (record-ref i price) #:precision 2 #:comma 3)) + (if (= (record-ref i stock) 0) " sold out" nothing) + nl]) -(show [(each (vec-map item-line stock))]) +(show (vec-map item-line stock)) }| @codeblock[#:lang "text"]|{ @@ -735,56 +691,48 @@ Gaiwan 1,240.00 Kettle 89.90 sold out }| -@code{item-line} returns one block, so its pieces are under @code{each}. @code{vec-map} gives a vec of blocks, and @code{each} makes that one block too. A function can return content instead, a @code{(Vec Block)}, and the caller decides where it goes: +@code{vec-map} gives a vec of blocks, which is a block. A lambda given to @code{vec-map} is different from a function with a signature: @code{vec-map} does not say what its function returns, so a vec of mixed pieces that the lambda returns, such as a string and an int, is not read as blocks and is refused. Give it to @code{each}, which says that it is one: @codeblock[#:lang "bjolang"]|{ -(: labelled (-> string int (Vec Block))) -(defun (labelled label n) - [label ": " n nl]) - -(show (labelled "cups" 3)) ;; cups: 3 -(show [">> " (each (labelled "pots" 1))]) ;; >> pots: 1 -}| - -A lambda has no signature, so a vec literal returned from one is not read as blocks: in @code{(vec-map (fun (i) [(record-ref i name) (record-ref i stock)]) stock)} the vec has a string and an int in it, and is refused. Put the literal under @code{each}, which says that it is content, or write a function with a signature: - -@codeblock[#:lang "bjolang"]|{ -(show [(each (vec-map (fun (i) (each [(record-ref i name) ": " (record-ref i stock) nl])) - stock))]) +(show (vec-map (fun (i) (each [(record-ref i name) ": " (record-ref i stock) nl])) + stock)) ;; Green tea: 12 ;; Gaiwan: 2 ;; Kettle: 0 }| -@code{nothing} is the block that writes nothing, for the branch that has nothing to say. In a vec literal, what an @code{if}, a @code{match} or a @code{cond} gives is read as any element is, so an arm can be a string. In a function declared to return a @code{Block}, nothing is converted, so there the arms are blocks: +A lambda given to a function in @code{(std fmt)}, such as @code{joined}, needs no @code{each}: their signatures say that the lambda returns a block. + +@code{nothing} is the block that writes nothing, for the branch that has nothing to say: @codeblock[#:lang "bjolang"]|{ (: greeting (-> string (Option string) Block)) (defun (greeting name title) - (each ["Hello, " - (match title - ((Some t) (each [t " "])) - (None nothing)) - name "!"])) + ["Hello, " + (match title + ((Some t) [t " "]) + (None nothing)) + name "!"]) (: medal (-> int Block)) (defun (medal place) - (cond ((= place 1) (BStr "gold")) - ((= place 2) (BStr "silver")) + (cond ((= place 1) "gold") + ((= place 2) "silver") (else nothing))) (show [(greeting "Ada" (Some "Dr")) nl (greeting "Bob" None) nl]) ;; Hello, Dr Ada! ;; Hello, Bob! +(show [(medal 1) "/" (medal 2) "/" (medal 3) nl]) ;; gold/silver/ }| -A list is written with @code{joined}, which takes a function that makes each element's block and a separator, which is content too: +A list is written with @code{joined}, which takes a function that makes each element's block, and a separator: @codeblock[#:lang "bjolang"]|{ -(show [(joined BStr (list "tea" "milk" "honey") [", "]) nl]) ;; tea, milk, honey -(show [(joined/last BStr (fun (s) (each ["and " s])) - (list "tea" "milk" "honey") [", "]) nl]) ;; tea, milk, and honey -(show [(joined (fun (s) (each ["* " s])) (list "tea" "milk") [nl]) nl]) +(show [(joined BStr (list "tea" "milk" "honey") ", ") nl]) ;; tea, milk, honey +(show [(joined/last BStr (fun (s) ["and " s]) + (list "tea" "milk" "honey") ", ") nl]) ;; tea, milk, and honey +(show [(joined (fun (s) ["* " s]) (list "tea" "milk") nl) nl]) ;; * tea ;; * milk }| @@ -794,13 +742,13 @@ A list is written with @code{joined}, which takes a function that makes each ele @code{padded/left}, @code{padded/right} and @code{padded/both} pad content to a width: aligned right, left, or centred. Padding never cuts. @code{trimmed/left}, @code{trimmed/right} and @code{trimmed/both} cut content to a width, and keep its end, its start or its middle. @code{fitted/left}, @code{fitted/right} and @code{fitted/both} do both, so the content is exactly the width. @codeblock[#:lang "bjolang"]|{ -(show [(padded/right 8 [who]) (padded/left 4 [points]) nl]) ;; ada 31 -(show [(with-pad #\0 [(padded/left 4 [42])]) nl]) ;; 0042 -(show [(with-pad #\= [(padded/both 20 [" Receipt "])]) nl]) ;; ===== Receipt ====== -(show [(trimmed/left 6 ["/home/ada/scores.txt"]) nl]) ;; es.txt +(show [(padded/right 8 who) (padded/left 4 points) nl]) ;; ada 31 +(show [(with-pad #\0 (padded/left 4 42)) nl]) ;; 0042 +(show [(with-pad #\= (padded/both 20 " Receipt ")) nl]) ;; ===== Receipt ====== +(show [(trimmed/left 6 "/home/ada/scores.txt") nl]) ;; es.txt (show [(with-fmt current-ellipsis "…" - [(trimmed/left 12 ["/home/ada/scores.txt"])]) nl]) ;; …/scores.txt -(show ["[" (fitted/both 6 ["ab"]) "]" nl]) ;; [ ab ] + (trimmed/left 12 "/home/ada/scores.txt")) nl]) ;; …/scores.txt +(show ["[" (fitted/both 6 "ab") "]" nl]) ;; [ ab ] }| @code{space-to} pads to a column, whatever was written before it, and @code{with-pad} changes what the padding is made of. Together they make a leader: @@ -835,7 +783,7 @@ The digits are always the invariant culture's, so the machine's locale never sho @codeblock[#:lang "bjolang"]|{ (show [(with-fmt current-width 20 - [(justified ["Bjolang lays text out with blocks, and blocks nest."])]) + (justified "Bjolang lays text out with blocks, and blocks nest.")) nl]) ;; Bjolang lays text ;; out with blocks, and @@ -846,15 +794,15 @@ The first line starts where the cursor is, so after a label it has less room. Th @subsubsection{Tables} -A table is the one place where a vec of vecs is right. @code{tabular} takes a vec of columns, and @code{tabular/rows} a vec of rows. Each column or row is a @code{(Vec Block)}, so the inner vecs are read as blocks as any other content is: +A table is the place where the brackets matter. @code{tabular} takes a vec of columns, and @code{tabular/rows} a vec of rows. Each column or row is a @code{(Vec Block)}, with a block for each cell: @codeblock[#:lang "bjolang"]|{ -(show [(tabular [["tea" nl "gaiwan" nl "kettle"] - [3 nl 1240 nl 90]] - #:sep " " #:align [AlignLeft AlignRight])]) +(show (tabular [["tea" nl "gaiwan" nl "kettle"] + [3 nl 1240 nl 90]] + #:sep " " #:align [AlignLeft AlignRight])) -(show [(tabular/rows [["tea" 3] ["gaiwan" 1240] ["kettle" 90]] - #:sep " " #:align [AlignLeft AlignRight])]) +(show (tabular/rows [["tea" 3] ["gaiwan" 1240] ["kettle" 90]] + #:sep " " #:align [AlignLeft AlignRight])) }| Both write the same table: @@ -865,12 +813,12 @@ gaiwan 1240 kettle 90 }| -In @code{tabular} a column's cells are separated by @code{nl}. In @code{tabular/rows} each element of a row is one cell, so a cell of several pieces is under @code{each}, and a newline in a cell gives the row another line: +In @code{tabular} a column's cells are separated by @code{nl}. In @code{tabular/rows} each element of a row is one cell, so a cell of several pieces is a vec in the row, and a newline in a cell gives the row another line: @codeblock[#:lang "bjolang"]|{ -(show [(tabular/rows [["tea" (each [3 " cups"])] - ["kettle" (each ["boils in" nl "4 minutes"])]] - #:style StyleAscii)]) +(show (tabular/rows [["tea" [3 " cups"]] + ["kettle" ["boils in" nl "4 minutes"]]] + #:style StyleAscii)) }| @codeblock[#:lang "text"]|{ @@ -881,7 +829,7 @@ In @code{tabular} a column's cells are separated by @code{nl}. In @code{tabular/ +--------+-----------+ }| -Rows from data come from a function with a signature, for the reason given above. Here is the high-score ledger from "Files and I/O" as a table: +Rows from data come from a function with a signature, for the reason given above: a row of a string and an int has no element type of its own. Here is the high-score ledger from "Files and I/O" as a table: @codeblock[#:lang "bjolang"]|{ (: score-row (-> (Tuple string int) (Vec Block))) @@ -889,10 +837,10 @@ Rows from data come from a function with a signature, for the reason given above (def (Tuple name points) s) [name points]) -(show [(tabular/rows (vec-map score-row (read-scores)) - #:titles ["Player" "Score"] - #:align [AlignLeft AlignRight] - #:style StyleAscii)]) +(show (tabular/rows (vec-map score-row (read-scores)) + #:titles ["Player" "Score"] + #:align [AlignLeft AlignRight] + #:style StyleAscii)) }| @codeblock[#:lang "text"]|{ @@ -910,9 +858,9 @@ A table whose rows are not all known yet, such as results that come one at a tim @codeblock[#:lang "bjolang"]|{ (def layout (table-layout [8 6] #:sep " " #:align [AlignLeft AlignRight])) -(show [(table-row layout ["Player" "Score"])]) +(show (table-row layout ["Player" "Score"])) (loop (:for (Tuple name points) (read-scores)) - (:do (show [(table-row layout [name points])]))) + (:do (show (table-row layout [name points])))) }| @codeblock[#:lang "text"]|{ @@ -949,9 +897,9 @@ The pad character, the right margin, the ellipsis and the number formats are par @codeblock[#:lang "bjolang"]|{ (show [(with-fmt current-comma 3 - [(with-fmt current-comma-sep #\space - [(with-fmt current-decimal-sep #\, - [(numeric 1234567.5)])])]) + (with-fmt current-comma-sep #\space + (with-fmt current-decimal-sep #\, + (numeric 1234567.5)))) nl]) ;; 1 234 567,5 }| @@ -965,32 +913,27 @@ The messages for the common mistakes, and what to write instead: @read-table{ | The compiler says | Write | |----------------------------------------------------------------------+-----------------------------------------------------| -| expected @code{(Vec fmt/Block)}, found @code{string} | @code{["x"]} for @code{"x"} | -| expected @code{(Vec fmt/Block)}, found @code{fmt/Block} | @code{[b]} for @code{b} | -| expected @code{fmt/Block}, found @code{(Vec ...)} | @code{(each bs)} for @code{bs} | -| They differ inside the Vec: @code{fmt/Block} against @code{string} | @code{(: bs (Vec Block))} above @code{(def bs ...)} | -| Block: expected a string, an int, a char ...; found a vec | @code{(each [...])}, or no inner brackets | +| Block: ... found a double | @code{(numeric x)} | | Block: ... found the number 1.5 | @code{(numeric 1.5)} | -| Block: ... found a boolean | @code{(if b "yes" "no")} | +| Block: ... found a boolean, or found a bool | @code{(if b "yes" "no")} | +| Block: ... found a (Vec string) | @code{(vec-map BStr xs)}, or @code{(: xs Block)} above @code{(def xs ...)} | | Block: 'n' is a number, and nothing says yet whether it is an int ... | @code{(: n int)} above @code{(def n 3)} | -| what @code{f} returns has the wrong type: expected @code{fmt/Block}, found @code{string} | @code{(BStr s)} or @code{(each [s])} | +| Block: ... found a (Vec (Vec ...)), at a lambda that returns a vec | @code{(each [...])} around the vec the lambda returns | } -When none of these fits, find the place where a @code{Block} and a @code{(Vec Block)} meet, and check it against the rule: content in brackets, one block out. - @subsubsection{A block of your own} @code{captured} renders content to a string and gives it to a function, which returns the block to write in its place. It is how a block that needs the whole of its content is made, and how padding and trimming are written: @codeblock[#:lang "bjolang"]|{ -(: boxed (-> (Vec Block) Block)) -(defun (boxed bs) - (captured (fun (s) (each ["[" s "]"])) bs)) +(: boxed (-> Block Block)) +(defun (boxed b) + (captured (fun (s) ["[" s "]"]) b)) (show [(boxed ["x = " 7]) nl]) ;; [x = 7] }| -A block that writes as it goes is a @code{BRun}: a function given the sink to write to and the column the cursor is at, which returns the column it ends at. @code{run-blocks} runs content inside one. The module's page describes both. +A block that writes as it goes is a @code{BRun}: a function given the sink to write to and the column the cursor is at, which returns the column it ends at. @code{run-block} runs a block inside one. The module's page describes both. @subsection{Concurrency}