Search code examples
smlpolyml

Create a list reading a file with SML


I'm trying to create a List reading a text file, for example I have a text file like this "1 5 12 9 2 6" and I want to create a list like this [1,5,12,9,2,6] using SML


Solution

  • You can divide this task into several sub-problems:

    1. Reading a file into a string can be done with

      type filepath = string
      
      (* filepath -> string *)
      fun readFile filePath =
          let val fd = TextIO.openIn filePath
              val s = TextIO.inputAll fd
              val _ = TextIO.closeIn fd
          in s end
      

      See the TextIO library.

    2. Converting a string into a list of strings separated by whitespace can be done with

      (* string -> string list *)
      fun split s =
          String.tokens Char.isSpace s
      

      See the String.tokens function.

    3. Converting a list of strings into a list of integers can be done with

      (* 'a option list -> 'a list option *)
      fun sequence (SOME x :: rest) = Option.map (fn xs => x :: xs) (sequence rest)
        | sequence (NONE :: _) = NONE
        | sequence [] = SOME []
      
      fun convert ss = sequence (List.map Int.fromString ss)
      

      Since any one string-to-integer conversion with Int.fromString may fail and produce a NONE, List.map Int.fromString will produce an "int option list" rather than an "int list". This list of "int option" may be converted to an optional "int list", i.e., remove the SOME of all the "int option", but if there's a single NONE, the entire result is discarded and becomes NONE. This gives the final type "int list option" (either NONE or SOME [1,2,...]).

      See the Option.map function which was useful for this kind of recursion.

    4. Combining these,

      (* filepath -> int list *)
      fun readIntegers filePath =
          convert (split (readFile filePath))
      

    This approach does yield some potentially unwanted behavior:

    • Filesystem errors will make readIntegers throw an Io exception
    • The string ~5 inside the file will be interpreted as negative five
    • The string -5 will produce a failure (NONE)
    • The string 123a will produce the number 123 (Int.toString is a bit too forgiving)

    You may want to address those.