Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   http://anggtwu.net/LEAN/feline1.lean.html
--   http://anggtwu.net/LEAN/feline1.lean
--          (find-angg "LEAN/feline1.lean")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
--
-- (defun e () (interactive) (find-angg "LEAN/feline1.lean"))
-- (find-fplean4page 72 "the result is pure ()")
-- (find-fplean4text 72 "the result is pure ()")
-- (find-fplean4page 79 "Worked Example: cat")
-- (find-fplean4text 79 "Worked Example: cat")


-- import Init.System.FilePath
-- open        System.FilePath
-- open        System
-- (find-lean4prefile "Init/System/FilePath.lean" "namespace FilePath\n")
-- (find-lean4prefile "Init/System/FilePath.lean" "def mkFilePath")
-- (find-lean4pregrep "grep --color=auto -nRH --null -e 'mkFilePath' *")

-- System.mkFilePath

#check System.mkFilePath
#check System.mkFilePath ["/tmp/o"]
#eval  System.mkFilePath ["/tmp/o"]
#check IO.FS.readFile
#check IO.FS.readFile (System.mkFilePath ["/tmp/o"])
#eval  IO.FS.readFile (System.mkFilePath ["/tmp/o"])
#check IO.FS.writeFile

def mkfilepath (fname : String) : System.FilePath :=
  System.mkFilePath [fname]

def readfile   (fname : String) : IO String :=
  IO.FS.readFile (mkfilepath fname)

def writefile  (fname content : String) : IO Unit :=
  IO.FS.writeFile (mkfilepath fname) content

#check mkfilepath "/tmp/o"
#eval  mkfilepath "/tmp/o"
#check readfile   "/tmp/o"
#eval  readfile   "/tmp/o"
#check writefile  "/tmp/o2" "bar\n"
#eval  writefile  "/tmp/o2" "bar\n"


-- (find-angg "LEAN/ioprocess1.lean")

-- import IO.Process


def main : IO Unit :=
  writefile "/tmp/o2" "bar\n"


#check pure
#check pure ()
#check      ()
#check IO
#check IO.FS.Stream

/-
structure       Stream where
  flush         : IO Unit
  read          : USize → IO ByteArray
  write         : ByteArray → IO Unit
  getLine       : IO String
  putStr        : String → IO Unit
-/

/-

   The Lean compiler contains IO actions (such as IO.getStdout , which is called in dump ) to
   get streams that represent standard input, standard output, and standard error. These are
    IO actions rather than ordinary definitions because Lean allows these standard POSIX
   streams to be replaced in a process, which makes it easier to do things like capturing the
   output from a program into a string by writing a custom IO.FS.Stream .

   The control flow in dump is essentially a while loop. When dump is called, if the stream has
   reached the end of the file, pure () terminates the function by returning the constructor
   for Unit . If the stream has not yet reached the end of the file, one block is read, and its
   contents are written to stdout , after which dump calls itself directly. The recursive calls
   continue until stream.read returns an empty byte array, which indicates that the end of the
   file has been reached.

   When an if expression occurs as a statement in a do , as in dump , each branch of the if
   is implicitly provided with a do . In other words, the sequence of steps following the else
   are treated as a sequence of IO actions to be executed, just as if they had a do at the
   beginning. Names introduced with let in the branches of the if are visible only in their
   own branches, and are not in scope outside of the if .

   There is no danger of running out of stack space while calling dump because the recursive
   call happens as the very last step in the function, and its result is returned directly rather
   than being manipulated or computed with. This kind of recursion is called tail recursion, and
   it is described in more detail later in this book. Because the compiled code does not need to
   retain any state, the Lean compiler can compile the recursive call to a jump.

   If feline only redirected standard input to standard output, then dump would be
   sufficient. However, it also needs to be able to open files that are provided as command-line
   arguments and emit their contents. When its argument is the name of a file that exists,
    fileStream returns a stream that reads the file's contents. When the argument is not a file,
    fileStream emits an error and returns none .

-/


#check IO.FS.readFile
#check IO.FS.writeFile


-- import IO.System.FilePath
-- import IO.FilePath

-- (find-lean4prefile "Init/System/FilePath.lean")

def fileStream (filename : System.FilePath) : IO (Option IO.FS.Stream) := do
  let fileExists ← filename.pathExists
  if not fileExists then
    let stderr ← IO.getStderr
    stderr.putStrLn s!"File not found: {filename}"
    pure none
  else
    let handle ← IO.FS.Handle.mk filename IO.FS.Mode.read
    pure (some (IO.FS.Stream.ofHandle handle))


/-

   Opening a file as a stream takes two steps. First, a file handle is created by opening the file
   in read mode. A Lean file handle tracks an underlying file descriptor. When there are no
   references to the file handle value, a finalizer closes the file descriptor. Second, the file
   handle is given the same interface as a POSIX stream using IO.FS.Stream.ofHandle , which
   fills each field of the Stream structure with the corresponding IO action that works on file
   handles.



   Handling Input

   The main loop of feline is another tail-recursive function, called process . In order to
   return a non-zero exit code if any of the inputs could not be read, process takes an
   argument exitCode that represents the current exit code for the whole program.
   Additionally, it takes a list of input files to be processed.

-/

def process (exitCode : UInt32) (args : List String) : IO UInt32 := do
  match args with
  | [] => pure exitCode
  | "-" :: args =>
    let stdin ← IO.getStdin
    dump stdin
    process exitCode args
  | filename :: args =>
    let stream ← fileStream ⟨filename⟩
    match stream with
    | none =>
      process 1 args
    | some stream =>
      dump stream
      process exitCode args

/-
   Just as with if , each branch of a match that is used as a statement in a do is implicitly
   provided with its own do .

   There are three possibilities. One is that no more files remain to be processed, in which case
    process returns the error code unchanged. Another is that the specified filename is "-" ,
   in which case process dumps the contents of the standard input and then processes the
   remaining filenames. The final possibility is that an actual filename was specified. In this
   case, fileStream is used to attempt to open the file as a POSIX stream. Its argument is
   encased in ⟨ ... ⟩ because a FilePath is a single-field structure that contains a string. If
   the file could not be opened, it is skipped, and the recursive call to process sets the exit
   code to 1 . If it could, then it is dumped, and the recursive call to process leaves the exit
   code unchanged.

    process does not need to be marked partial because it is structurally recursive. Each
   recursive call is provided with the tail of the input list, and all Lean lists are finite. Thus,
    process does not introduce any non-termination.


   Main

   The final step is to write the main action. Unlike prior examples, main in feline is a
   function. In Lean, main can have one of three types:

           main : IO Unit corresponds to programs that cannot read their command-line
          arguments and always indicate success with an exit code of 0 ,
          main : IO UInt32 corresponds to int main(void) in C, for programs without
          arguments that return exit codes, and
          main : List String → IO UInt32 corresponds to int main(int argc, char
          **argv) in C, for programs that take arguments and signal success or failure.

   If no arguments were provided, feline should read from standard input as if it were called
   with a single "-" argument. Otherwise, the arguments should be processed one after the
   other.

-/

    def main (args : List String) : IO UInt32 :=
      match args with
      | [] => process 0 ["-"]
      | _ => process 0 args

/-


   Meow!
   To check whether feline works, the first step is to build it with lake build . First off, when
   called without arguments, it should emit what it receives from standard input. Check that

    echo "It works!" | ./build/bin/feline


   emits It works! .

   Secondly, when called with files as arguments, it should print them. If the file test1.txt
   contains

    It's time to find a warm spot



https://leanprover.github.io/functional_programming_in_lean/print.html                                83/432
02/06/2023, 22:33                                              Functional Programming in Lean

   and test2.txt contains

    and curl up!


   then the command

    ./build/bin/feline test1.txt test2.txt


   should emit

    It's time to find a warm spot
    and curl up!


   Finally, the - argument should be handled appropriately.

    echo "and purr" | ./build/bin/feline test1.txt - test2.txt


   should yield

    It's time to find a warm spot
    and purr
    and curl up!




   Exercise
   Extend feline with support for usage information. The extended version should accept a
   command-line argument --help that causes documentation about the available command-
   line options to be written to standard output.

   Additional Conveniences

   Nested Actions
   Many of the functions in feline exhibit a repetitive pattern in which an IO action's result
   is given a name, and then used immediately and only once. For instance, in dump :

-/

    partial def dump (stream : IO.FS.Stream) : IO Unit := do
      let buf ← stream.read bufsize
      if buf.isEmpty then
        pure ()
      else
        let stdout ← IO.getStdout
        stdout.write buf
        dump stream

/-
   the pattern occurs for stdout :
-/

          let stdout ← IO.getStdout
          stdout.write buf

/-

   Similarly, fileStream contains the following snippet:

-/
       let fileExists ← filename.pathExists
       if not fileExists then


/-
   When Lean is compiling a do block, expressions that consist of a left arrow immediately
   under parentheses are lifted to the nearest enclosing do , and their results are bound to a
   unique name. This unique name replaces the origin of the expression. This means that
   dump can also be written as follows:

-/

    partial def dump (stream : IO.FS.Stream) : IO Unit := do
      let buf ← stream.read bufsize
      if buf.isEmpty then
        pure ()
      else
        (← IO.getStdout).write buf
        dump stream

/-

   This version of dump avoids introducing names that are used only once, which can greatly
   simplify a program. IO actions that Lean lifts from a nested expression context are called
   nested actions.

    fileStream can be simplified using the same technique:

-/

    def fileStream (filename : System.FilePath) : IO (Option IO.FS.Stream) := do
      if not (← filename.pathExists) then
        (← IO.getStderr).putStrLn s!"File not found: {filename}"
        pure none
      else
        let handle ← IO.FS.Handle.mk filename IO.FS.Mode.read
        pure (some (IO.FS.Stream.ofHandle handle))

/-

   In this case, the local name of handle could also have been eliminated using nested
   actions, but the resulting expression would have been long and complicated. Even though
   it's often good style to use nested actions, it can still sometimes be helpful to name
   intermediate results.

   It is important to remember, however, that nested actions are only a shorter notation for
    IO actions that occur in a surrounding do block. The side effects that are involved in
   executing them still occur in the same order, and execution of side effects is not
   interspersed with the evaluation of expressions. For an example of where this might be
   confusing, consider the following helper definitions that return data after announcing to the
   world that they have been executed:

-/

    def getNumA : IO Nat := do
      (← IO.getStdout).putStrLn "A"
      pure 5

    def getNumB : IO Nat := do
      (← IO.getStdout).putStrLn "B"
      pure 7

/-
   These definitions are intended to stand in for more complicated IO code that might
   validate user input, read a database, or open a file.

   A program that prints 0 when number A is five, or number B otherwise, can be written as
   follows:
-/

    def test : IO Unit := do
      let a : Nat := if (← getNumA) == 5 then 0 else (← getNumB)
      (← IO.getStdout).putStrLn s!"The answer is {a}"


/-
   However, this program probably has more side effects (such as prompting for user input or
   reading a database) than was intended. The definition of getNumA makes it clear that it will
   always return 5 , and thus the program should not read number B. However, running the
   program results in the following output:

    A
    B
    The answer is 0


    getNumB was executed because test is equivalent to this definition:
-/

    def test : IO Unit := do
      let x ← getNumA
      let y ← getNumB
      let a : Nat := if x == 5 then 0 else y
      (← IO.getStdout).putStrLn s!"The answer is {a}"

/-
   This is due to the rule that nested actions are lifted to the closest enclosing do block. The
   branches of the if were not implicitly wrapped in do blocks because the if is not itself a
   statement in the do block—the statement is the let that defines a . Indeed, they could
   not be wrapped this way, because the type of the conditional expression is Nat , not IO
   Nat .




   Flexible Layouts for do
   In Lean, do expressions are whitespace-sensitive. Each IO action or local binding in the do
   is expected to start on its own line, and they should all have the same indentation. Almost
   all uses of do should be written this way. In some rare contexts, however, manual control
   over whitespace and indentation may be necessary, or it may be convenient to have
   multiple small actions on a single line. In these cases, newlines can be replaced with a
   semicolon and indentation can be replaced with curly braces.

   For instance, all of the following programs are equivalent:


    -- This version uses only whitespace-sensitive layout
    def main : IO Unit := do
      let stdin ← IO.getStdin
      let stdout ← IO.getStdout

        stdout.putStrLn "How would you like to be addressed?"
        let name := (← stdin.getLine).trim
        stdout.putStrLn s!"Hello, {name}!"

    -- This version is as explicit as possible
    def main : IO Unit := do {
      let stdin ← IO.getStdin;
      let stdout ← IO.getStdout;

        stdout.putStrLn "How would you like to be addressed?";
        let name := (← stdin.getLine).trim;
        stdout.putStrLn s!"Hello, {name}!"
    }

    -- This version uses a semicolon to put two actions on the same line
    def main : IO Unit := do
      let stdin ← IO.getStdin; let stdout ← IO.getStdout

        stdout.putStrLn "How would you like to be addressed?"
        let name := (← stdin.getLine).trim
        stdout.putStrLn s!"Hello, {name}!"


   Idiomatic Lean code uses curly braces with do very rarely.




   Running IO Actions With #eval
   Lean's #eval command can be used to execute IO actions, rather than just evaluating
   them. Normally, adding a #eval command to a Lean file causes Lean to evaluate the
   provided expression, convert the resulting value to a string, and provide that string as a
   tooltip and in the info window. Rather than failing because IO actions can't be converted to
   strings, #eval executes them, carrying out their side effects. If the result of execution is the
    Unit value () , then no result string is shown, but if it is a type that can be converted to a
   string, then Lean displays the resulting value.

   This means that, given the prior definitions of countdown and runActions ,

    #eval runActions (countdown 3)


   displays

    3
    2
    1
    Blast off!

https://leanprover.github.io/functional_programming_in_lean/print.html                          88/432
02/06/2023, 22:33                                              Functional Programming in Lean

   This is the output produced by running the IO action, rather than some opaque
   representation of the action itself. In other words, for IO actions, #eval both evaluates the
   provided expression and executes the resulting action value.

   Quickly testing IO actions with #eval can be much more convenient that compiling and
   running whole programs. However, there are some limitations. For instance, reading from
   standard input simply returns empty input. Additionally, the IO action is re-executed
   whenever Lean needs to update the diagnostic information that it provides to users, and
   this can happen at unpredictable times. An action that reads and writes files, for instance,
   may do so at inconvenient times.




https://leanprover.github.io/functional_programming_in_lean/print.html                          89/432
02/06/2023, 22:33                                              Functional Programming in Lean




   Summary

   Evaluation vs Execution
   Side effects are aspects of program execution that go beyond the evaluation of
   mathematical expressions, such as reading files, throwing exceptions, or triggering
   industrial machinery. While most languages allow side effects to occur during evaluation,
   Lean does not. Instead, Lean has a type called IO that represents descriptions of programs
   that use side effects. These descriptions are then executed by the language's run-time
   system, which invokes the Lean expression evaluator to carry out specific computations.
   Values of type IO α are called IO actions. The simplest is pure , which returns its argument
   and has no actual side effects.

    IO actions can also be understood as functions that take the whole world as an argument
   and return a new world in which the side effect has occurred. Behind the scenes, the IO
   library ensures that the world is never duplicated, created, or destroyed. While this model of
   side effects cannot actually be implemented, as the whole universe is too big to fit in
   memory, the real world can be represented by a token that is passed around through the
   program.

   An IO action main is executed when the program starts. main can have one of three
   types:

           main : IO Unit is used for simple programs that cannot read their command-line
          arguments and always return exit code 0 ,
           main : IO UInt32 is used for programs without arguments that may signal success or
          failure, and
           main : List String → IO UInt32 is used for programs that take command-line
          arguments and signal success or failure.


-/



/-
| (find-elan4leanfile "Lean/")
| (find-lean4file "doc/")
| (find-lean4file "doc/tour.md")
| (find-angg ".emacs.papers" "fplean4")

* (eepitch-shell)
* (eepitch-kill)
* (eepitch-shell)
# (find-fline "/tmp/feline/")
rm -Rv /tmp/feline/
mkdir  /tmp/feline/
cd     /tmp/feline/
lake new feline
laf
find

lean --run feline1.lean



-/



-- Local Variables:
-- coding:  utf-8-unix
-- End: