1.7. Characters, Strings, and Slices
In Lean, strings contain Unicode text.
Specifically, they are sequences of characters, and characters are Unicode code points.
Strings are written in double quotes, and individual characters are written in single quotes.
The type of strings is String and the type of characters is Char.
Strings can be appended with the ++ operator:
#eval "Hello, " ++ "world"
A character can be added to the end of a string using String.push:
#eval String.push "Hello" '!'This function can also be called using the dot notation that is used with structure accessors:
#eval "Hello".push '!'1.7.1. Slices
Strings are represented by their UTF-8 encoding as an array of bytes paired with a cached character count. This means that removing even a single character from a string can result in copying the remaining characters to a new string.
To allow string-processing code to be written from small, composable pieces, many string operations return string slices, which are regions of some other string.
String slices have the type String.Slice.
A slice contains a reference to a string along with the start and end positions of the slice, and multiple slices can share the same string.
Operations such as dropping prefixes of strings return slices rather than allocating new strings, and large parts of the string API are also implemented for slices.
Operations that return slices include String.trimAscii, which returns a slice that drops leading and trailing space, tab, newline, and carriage return characters from a string; String.drop and String.dropEnd, which drop the specified number of characters from the start or end of a string; and String.dropWhile and String.dropEndWhile, which respectively remove all the characters that match a pattern from the beginning or end of a string.
The patterns used to search in strings are distinct from those used for pattern matching; in the string API, they are function arguments that specify characters or specific substrings to match.
The string slice API includes all the slice-producing string functions as well, which makes it possible to write string manipulations as a series of incremental steps without risking intermediate string copying.
This code removes characters from the beginning and end of a string without allocating an intermediate string:
#eval (("small tortoiseshell".drop 6).dropEnd 5).copy
The function String.Slice.copy returns a copy of the region of the underlying string that the slice indicates.
The initial call to String.drop returns a slice, and the call to String.Slice.dropEnd returns an adjusted slice.
The final call to copy creates a string once more.
Unlike strings, slices do not cache a character count.
Because the UTF-8 encoding of characters may occupy multiple bytes, there's no efficient way to check the length of a string slice.
However, checking whether it is empty can be accomplished with String.Slice.isEmpty.
1.7.2. Matching
Functions that match parts of strings, such as String.dropWhile and String.dropEndWhile, are overloaded.
They can be called with a variety of different patterns, each of which matches substrings in its own way.
The pattern can be a character, in which case runs of the character are removed:
#eval "red admiral".dropEndWhile 'l'The output is not in quotes because it is a string slice, and string slices are displayed without surrounding quotes. The pattern may also be a string, in which case runs of the complete string are removed:
#eval "the the butterfly".dropWhile "the "Incomplete matches are not removed:
#eval ("a gray grayling".drop 2).dropWhile "gray "
The pattern may also be a function that returns true or false.
Characters are removed until the function returns false.
The slice is converted to a string in order to illustrate that the trailing space remains:
#eval ("red admiral".dropEndWhile Char.isAlpha).copy1.7.3. Messages You May Meet
The overloaded string-matching functions are implemented using features that are explained later in the book, namely type classes and dependent types. There are two error messages in particular that are useful to learn to read before learning about those features of Lean.
Calling the functions without a pattern results in an error:
#eval "red admiral".dropEndWhileThis error is stating that Lean can't determine which pattern type to use, because no pattern was provided. It can be fixed by providing a pattern.
When the functions are called with an argument that isn't a valid pattern, there is a compile-time error:
#eval "12345abcde".dropEndWhile [12]
This error message means that String.dropEndWhile is not overloaded for the pattern [12].
It can be fixed by providing a meaningful pattern, such as a function, character, or string.