Zulip Chat Archive

Stream: lean4

Topic: missing `IO String` function


Asei Inoue (Jan 16 2025 at 16:14):

Why is it that there is IO.print, a function of type IO Unit that prints a string to the terminal, but there is no function of type IO String that receives keyboard input characters?

Asei Inoue (Jan 16 2025 at 16:15):

As far as I know, the only function in the standard library that accepts keyboard input is one that takes FS.Stream as an argument.

Asei Inoue (Jan 16 2025 at 16:16):

Why are the following function of type IO String not provided in the standard library? Would it be a good idea to add them?

def IO.getln : IO String := do
  let input  ( IO.getStdin).getLine
  return input.trim

Asei Inoue (Jan 16 2025 at 16:17):

As for me, I would like to have such a function, because it is a hassle to prepare a Stream every time....

Eric Wieser (Jan 16 2025 at 17:31):

I don't think it should be calling trim

Chris Bailey (Jan 16 2025 at 17:42):

Rust communities pretty frequently get "why is this python code faster than this rust code" type questions from users reaching for conveniently packaged fire and forget IO functions, but under the hood end up reacquiring locks/handles needlessly or allocating a bunch of stuff that doesn't need to be allocated when they're put under a heavy work load.

I don't know how the codegen/compilation works for IO in Lean so this is totally speculative, but maybe something to think about.

Asei Inoue (Jan 17 2025 at 13:12):

@Eric Wieser

You're right. Indeed, I shouldn't call trim. How about this?

/-- Accepts a string input from the keyboard, removing newline characters. -/
def IO.getInput : IO String := do
  let mut input  ( IO.getStdin).getLine
  input := input.replace "\n" "" |>.replace "\r" ""
  return input

/-- Accepts a string input from the keyboard, including the trailing newline as-is. -/
def IO.getInputln : IO String := do
  let input  ( IO.getStdin).getLine
  return input

/-  example usage -/

/-- Reads a string from the keyboard and displays its length. -/
def main : IO Unit := do
  IO.print "Enter a string: "
  let input  IO.getInput
  IO.println s!"The entered string is '{input}'."
  IO.println s!"The string has {input.trim.length} characters."

Eric Wieser (Jan 17 2025 at 13:17):

If nothing else I would at least avoid duplication there

/-- Accepts a string input from the keyboard, including the trailing newline as-is. -/
def IO.getInputln : IO String := return ( IO.getStdin).getLine

/-- Accepts a string input from the keyboard, removing newline characters. -/
def IO.getInput : IO String := return ( IO.getInputln).replace "\n" "" |>.replace "\r" ""

but I think you need to be careful about the line-stripping logic; on Linux, I don't think stripping \r is expected behavior

Asei Inoue (Jan 17 2025 at 13:34):

@Eric Wieser

You are right. how about this?

/-- Accepts a string input from the keyboard, removing newline characters. -/
def IO.getInput : IO String := do
  let input  ( IO.getStdin).getLine
  return removeBr input
where
  removeBr : String  String :=
    if System.Platform.isWindows then
      (String.replace · "\r" "")  (String.replace · "\n" "")
    else
      (String.replace · "\n" "")

/-- Accepts a string input from the keyboard, including the trailing newline as-is. -/
def IO.getInputln : IO String := do
  ( IO.getStdin).getLine

Eric Wieser (Jan 17 2025 at 13:44):

You should write those in the other order so that one can use the other

Eric Wieser (Jan 17 2025 at 13:46):

I still think that trying to emulate python's "universal newlines" feature is a bad idea without a larger design for how it is expected to work

Asei Inoue (Jan 17 2025 at 13:48):

@Eric Wieser

how about this?

/-- Accepts a string input from the keyboard, including the trailing newline as-is. -/
def IO.getInputln : IO String := do
  ( IO.getStdin).getLine

/-- Accepts a string input from the keyboard, removing newline characters. -/
def IO.getInput : IO String := do
  return removeBr ( getInputln)
where
  removeBr : String  String :=
    if System.Platform.isWindows then
      (String.replace · "\r" "")  (String.replace · "\n" "")
    else
      (String.replace · "\n" "")

Asei Inoue (Jan 17 2025 at 13:49):

What exactly do you mean by "larger design for how it is expected to work"?

Eric Wieser (Jan 17 2025 at 14:05):

I mean questions like:

  • What if I want to read a text file with windows newlines? Should getLine do the right thing for me?
  • What if I read the whole file / stdin at once; should I see \r\n or just \n?|
  • Should the user be able to opt in / out, in case they're reading a windows file on linux?

Asei Inoue (Jan 17 2025 at 14:23):

The file was not supposed to be read. I assumed only keyboard input from the user.

Eric Wieser (Jan 17 2025 at 14:24):

I can always write cat some_file.txt | lake exe your_program though

Asei Inoue (Jan 17 2025 at 14:49):

Perhaps only the line breaks at the end should be removed. hmm. I understand the problem, but I don't know how to solve it.

Asei Inoue (Jan 17 2025 at 17:09):

@Eric Wieser

How about just adding this function only?

def IO.getInputln : IO String := do
  ( IO.getStdin).getLine

Eric Wieser (Jan 17 2025 at 17:18):

For what it's worth, Rust does not have such a function

Asei Inoue (Jan 17 2025 at 17:19):

Interesting. Why is there no such function in Rust?

François G. Dorais (Jan 17 2025 at 23:45):

String.replace will scan the entire string but in this case it is known exactly where the final newline is.

François G. Dorais (Jan 17 2025 at 23:46):

FWIW, it's better to just let the user deal with newlines.

Eric Wieser (Jan 18 2025 at 00:00):

I think python's TextIOWrapper that wraps a stream object and tidies newlines is a nice way to design the feature

François G. Dorais (Jan 18 2025 at 02:05):

I've used that idea in UnicodeBasic: https://github.com/fgdorais/lean4-unicode-basic/blob/main/UnicodeBasic/CharacterDatabase.lean

The line parsing version is simpler since it doesn't have to delete comments and split into fields.

Asei Inoue (Jan 18 2025 at 02:51):

hmm... request for IO.getInputln is not accepted?

François G. Dorais (Jan 18 2025 at 04:26):

How about implementing something more generic like this:

def ByLine (σ) [Stream σ Char] : Type _ := ...

instance [Stream σ Char] : Stream (ByLine σ) String := ...

Last updated: May 02 2025 at 03:31 UTC