Lean 4(元)编程 Cookbook

处理 Stdin/Stdout/Stderr 流🔗

如何从 Stdin 读取🔗

要从 stdin 读取,可以使用 IO.FS.Stream.getLine 函数,它从标准输入流读取一行输入,并以 IO String 的形式返回。

def readFromStdin : IO Unit := do IO.print "Please enter some input: " ( IO.getStdout).flush let input ( IO.getStdin).getLine IO.println s!"You entered: {input.trimAscii}"

对于更复杂的输入处理,可以直接使用 IO.getStdin 来逐字符读取,或读取直到 EOF 的全部内容。

def readAllFromStdin : IO String := do let stdin IO.getStdin stdin.readToEnd

有趣的例子:交互式的玩家输入🔗

CLI 工具中的一个常见模式是请求特定类型的数据(如数字),并在输入无效时重新提示用户。

/-- Repeatedly prompts the user until a valid natural number within range is provided. -/ partial def getBoundedNat (prompt : String) (low high : Nat) : IO Nat := do IO.print s!"{prompt} ({low}-{high}): " ( IO.getStdout).flush let input ( IO.getStdin).getLine match input.trimAscii.toNat? with | some n => if n >= low && n <= high then return n else IO.println s!"Error: {n} is out of range." getBoundedNat prompt low high | none => IO.println "Error: Please enter a valid number." getBoundedNat prompt low high def playerInput : IO Unit := do IO.print "Enter character name: " ( IO.getStdout).flush let name ( IO.getStdin).getLine let age getBoundedNat "Enter age" 1 150 let level getBoundedNat "Enter starting level" 1 99 IO.println s!"\nWelcome, {name.trimAscii}!" IO.println s!"Stats: Age {age}, Level {level}"

本例中,我们对 getBoundedNat 使用 partial,因为它是一个递归函数,理论上如果用户始终不提供有效输入,它可能永远运行下去。

如何打印到 Stdout 和 Stderr🔗

你可以分别用 IO.printlnIO.eprintln 函数打印到 stdoutstderr。和其他语言一样,ln 用于在输出末尾添加一个换行符。

def printToStdout : IO Unit := do IO.print "This is printed to stdout without a newline." IO.println "This is printed to stdout with a newline." def printToStderr : IO Unit := do IO.eprint "This is printed to stderr without a newline." IO.eprintln "This is printed to stderr with a newline."

有趣的例子:覆盖输出与刷新缓冲🔗

默认情况下,标准输出通常是“行缓冲”的,也就是说,在遇到换行符(\n)或缓冲区被填满之前,Lean 并不会真正把文本显示在你的终端上。如果你想显示一个提示或进度消息而换行,就应当手动刷新(flush)缓冲区。

配合使用回车符(\r)和 flush,我们可以创造出进度条或旋转指示符这类在同一行上不断更新的效果。

def showProgressBar (n: Nat) : IO Unit := do for i in [1:n] do let progress := i * 10 let filled := String.ofList (List.replicate i '#') let empty := String.ofList (List.replicate (10-i) '-') -- \r moves the cursor back to the -- start of the current line IO.print s!"\rProgress: [{filled}{empty}] {progress}%" ( IO.getStdout).flush IO.sleep 200 -- Sleep for 200ms IO.println "\nTask Complete!" def showSpinner (n: Nat) : IO Unit := do let spinChars := ["|", "/", "-", "\\"] for i in [1:n] do let spinChar := spinChars[i % spinChars.length]! IO.print s!"\rProcessing... {spinChar}" ( IO.getStdout).flush IO.sleep 100 -- 100ms is a better speed for a spinner IO.print "\rDone! " IO.println "" -- run `main` to see the progress bar in action like -- `lean --run test.lean` in your terminal. -- def main : IO Unit := do -- showProgressBar 10 -- showSpinner 20

有趣的例子:使用 ANSI 颜色与辅助函数🔗

如果你想给文本加粗、斜体或添加颜色,可以使用 ANSI 转义码来实现。与其每次都手动敲这些码,不如定义一个辅助函数更好。

/-- Wraps a string in ANSI escape codes for coloring. -/ def colorize (msg : String) (colorCode : String) : String := s!"\x1b[{colorCode}m{msg}\x1b[0m" def printStatus : IO Unit := do IO.println s!"Status: {colorize "SUCCESS" "32"}" -- Green IO.println s!"Status: {colorize "WARNING" "33"}" -- Yellow IO.println s!"Status: {colorize "FAILURE" "31"}" -- Red IO.println s!"Status: {colorize "BOLD" "1"}" -- Bold

这些都是标准的 ANSI 序列;完整参考可查看这里