Comparison patterns, and decimal and bigint literals
Part I's pattern matching chapter gets a section on (:at-least lo), (:at-most hi), (:between lo hi), (:greater-than lo) and (:less-than hi): what each matches, that they nest like any pattern, that the bounds are literals, and that the ranges of integers and characters are added up by the exhaustiveness check while those of doubles are not. The section on exhaustiveness shows a counterexample in a gap between ranges. The table of literals lists decimal and bigint, and the comment in the inference example says which binding is pinned. 🤖 Generated with [ECA](https://eca.dev) Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
parent
fb90f971b4
commit
a4c27bbf7c
1 changed files with 63 additions and 1 deletions
|
|
@ -87,6 +87,8 @@ These are the bjolang literals:
|
|||
| long | -5000000000L |
|
||||
| ulong | 50000000000UL |
|
||||
| double | 1.234 |
|
||||
| decimal | 1.0m |
|
||||
| bigint | 1234567890123456789012345I |
|
||||
| string | "hej" |
|
||||
| symbol | 'hej |
|
||||
| keyword | #:keyword :keyword (these are equal) |
|
||||
|
|
@ -155,7 +157,7 @@ Everything at the toplevel needs a signature, even values. In a function body, n
|
|||
@codeblock[#:lang "bjolang"]{
|
||||
;; pretend we are in a function body
|
||||
(def a (transientmap-empty))
|
||||
(add! a (:key . 5)) ;; transientmap is pinned to (Transientmap Keyword int).
|
||||
(add! a (:key . 5)) ;; a is pinned to (Transientmap Keyword int).
|
||||
(add! a (:key2 . "hej")) ;; fails
|
||||
}
|
||||
|
||||
|
|
@ -743,6 +745,54 @@ A clause may have a guard, written @code{#:when expr} after the pattern. The cla
|
|||
(None "none"))
|
||||
}|
|
||||
|
||||
@subsubsection{Comparisons}
|
||||
|
||||
Five patterns compare a number or a character with bounds:
|
||||
|
||||
@read-table{
|
||||
| Pattern | Matches a value that is |
|
||||
|----------------------------+--------------------------------------|
|
||||
| @code{(:at-least lo)} | @code{lo} or more |
|
||||
| @code{(:at-most hi)} | @code{hi} or less |
|
||||
| @code{(:between lo hi)} | from @code{lo} to @code{hi} |
|
||||
| @code{(:greater-than lo)} | more than @code{lo} |
|
||||
| @code{(:less-than hi)} | less than @code{hi} |
|
||||
}
|
||||
|
||||
@code{:between} includes both of its ends: it is @code{:at-least} and @code{:at-most} together, as @code{version-between} is in a manifest. Use @code{:greater-than} and @code{:less-than} when an end must not be included, which a @code{double} needs, because it has no next value below or above a bound.
|
||||
|
||||
A comparison is a pattern like any other, so you can put it inside other patterns: in a constructor, in a field of a record, in a list, vec or array pattern, and in @code{or} and @code{and}:
|
||||
|
||||
@codeblock[#:lang "bjolang"]|{
|
||||
(: grade (-> int string))
|
||||
(defun (grade points)
|
||||
(match points
|
||||
((:less-than 0) "invalid")
|
||||
((:between 0 59) "fail")
|
||||
((:at-least 60) "pass")))
|
||||
|
||||
(: letter (-> Char string))
|
||||
(defun (letter c)
|
||||
(match c
|
||||
((:between #\a #\z) "lower case")
|
||||
((:between #\A #\Z) "upper case")
|
||||
(_ "something else")))
|
||||
|
||||
(match card
|
||||
((Card (rank (Pip (:at-least 7)))) "a high pip card")
|
||||
(_ "something else"))
|
||||
|
||||
(match reading
|
||||
((Some (and n (:greater-than 100))) #"too high: ${n}")
|
||||
((Some (or (:less-than 0) (:greater-than 50))) "odd")
|
||||
((Some n) #"ok: ${n}")
|
||||
(None "no reading"))
|
||||
}|
|
||||
|
||||
The bounds must be number or character literals, because the comparison is compiled to a constant pattern. To compare with a value that is computed at run time, use a @code{#:when} guard.
|
||||
|
||||
The checker adds up the ranges of an integer or a character, so @code{grade} above needs no catch-all: its three ranges include every @code{int}. A clause whose range is already matched by the clauses above it is an error, as any clause that can never run is. The checker does not add up the ranges of a @code{double} or a @code{decimal}. A @code{double} can be NaN, which no comparison matches, so a match on one must end in a catch-all.
|
||||
|
||||
@subsubsection{Views, type tests and Has}
|
||||
|
||||
@code{(:view f pattern)} calls @code{f} on the value and matches the result against @code{pattern}. That lets a match look at something that is not directly in the value, like whether a string parses as a number:
|
||||
|
|
@ -826,6 +876,18 @@ The checker does not run your code, so a clause with a @code{#:when} guard, a @c
|
|||
(_ "large")) ;; without this line:"_ reaches no clause"
|
||||
}|
|
||||
|
||||
Comparisons of integers and characters are different: the checker knows which values they match. The counterexample is then a value in a gap between the ranges:
|
||||
|
||||
@codeblock[#:lang "bjolang"]|{
|
||||
(match n
|
||||
((:less-than 0) "negative")
|
||||
((:at-least 1) "positive"))
|
||||
}|
|
||||
|
||||
@codeblock[#:lang "text"]|{
|
||||
Pattern Error at example.bjo:2: this match does not cover every value. 0 reaches no clause.
|
||||
}|
|
||||
|
||||
@subsubsection{case}
|
||||
|
||||
@code{case} is a short form for the common match against literals. Each clause is a list of values, and @code{else} is the catch-all:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue