Skip to content
Reading Input and printfn
step 1/7

Reading — step 1 of 7

Learn

~3 min readLists, Recursion, IO

F# uses .NET's I/O primitives. The printfn family handles formatted output; System.Console.ReadLine() reads input. The Microsoft F# docs cover this thoroughly.

printfn — formatted output

printfn "hello"                    // hello
printfn "name: %s, age: %d" "Ada" 36   // name: Ada, age: 36
printfn "%.2f" 3.14159              // 3.14
printfn "%A" [1; 2; 3]              // [1; 2; 3] — generic pretty-print
printfn "%O" obj                   // calls .ToString()

Format specifiers:

  • %s — string
  • %d — int
  • %f — float
  • %b — bool
  • %c — char
  • %A — generic pretty (works on any type)
  • %O — uses .ToString()

TYPE-CHECKED format strings

Like OCaml, format strings are checked at compile time:

printfn "%d" "hello"
// Compile error: This expression was expected to have type int but has type string

Far safer than C's printf. Mismatches don't compile.

sprintf — to a string

let greeting = sprintf "Hello, %s, age %d" "Ada" 36

Returns a string. Same format syntax as printfn.

printf vs printfn

  • printfn — adds a newline
  • printf — no newline

For output that's eventually displayed, prefer printfn.

Reading input

let line = System.Console.ReadLine()
let n = int line
let f = float line

System.Console.ReadLine() returns the next line as a string, or null at end-of-input. F#'s int and float are conversion functions — not just types.

Reading multiple lines

let readN n =
    [for _ in 1..n -> System.Console.ReadLine()]

let n = int (System.Console.ReadLine())
let lines = readN n

List comprehension [for x in xs -> expr] is F#'s way of building lists by iteration.

Reading until EOF

let readAllLines () =
    let lines = ResizeArray<string>()
    let mutable line = System.Console.ReadLine()
    while line <> null do
        lines.Add(line)
        line <- System.Console.ReadLine()
    List.ofSeq lines

null is the EOF signal in .NET I/O. F# generally avoids null but here it's necessary for interop.

Or more functionally:

let readAllLines () =
    let rec loop acc =
        match System.Console.ReadLine() with
        | null -> List.rev acc
        | line -> loop (line :: acc)
    loop []

Standard streams

printfn "to stdout"
eprintfn "to stderr"                  // ePrintfn — error stream

System.Console.Out.WriteLine("to stdout")
System.Console.Error.WriteLine("to stderr")

eprintfn is the stderr equivalent.

File I/O

open System.IO

let content = File.ReadAllText("data.txt")
let lines = File.ReadAllLines("data.txt")
File.WriteAllText("out.txt", "hello world")
File.WriteAllLines("out.txt", [|"line1"; "line2"|])

For large files, use streams:

use reader = new StreamReader("big.log")
while not reader.EndOfStream do
    let line = reader.ReadLine()
    process line

The use keyword auto-closes the resource (like using in C#).

Common mistakes

  • Forgetting null for EOF — System.Console.ReadLine returns null at end-of-input, not raising. F# doesn't have automatic null safety; check explicitly.
  • Using int confused with the typeint x is a CONVERSION; int as a type means System.Int32. Context distinguishes.
  • printfn vs Console.WriteLine — both work. printfn has type-checked format strings; Console.WriteLine is plain. Use printfn for formatted output.
  • Forgetting eprintfn for errors — output to stderr matters in CLI tools.
  • Buffered output not flushingConsole.Out.Flush() if needed; print*n usually handles this.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…