The IO Monad for F#

If you have any experience in Haskell, you know the IO monad. Some people think it just makes things ugly – but I think it is good, since we are functional programming and we should know when we are using an impure function.

What we will do is to define an IO<’T> type and an io monad (which returns an IO<’T>). let!, use!, do! and return! will assist us, the rest will be just as usual.

Since most impure functions do not return IO<’T>, we will use the iofy function which “IOfies” functions (“overloaded” for different number of arguments: iofy, iofy2, iofy3, iofy4, iofy5) and hand-IOfying.

This isn’t a hard task at all. Here is IOBuilder.fs:


namespace IOMonad

type IO<'a> = IO of 'a

type IOBuilder() =

    member this.Return value =
        IO value

    member this.Bind ((IO(value)), cexpr) : IO<_> =
        cexpr value

    member this.Using ((value : #System.IDisposable), cexpr) =
        let result = cexpr value
        value.Dispose()
        result

    member this.Zero() = IO ()

    member this.Combine ((_ : IO<_>), cexpr) : IO<_> = cexpr()

    member this.Delay (cexpr : unit -> IO<_>) = cexpr

    member this.For ((values : #seq<_>), (cexpr : (_ -> IO<unit>))) =
        for i in values do
            cexpr i |> ignore
        IO ()

    member this.While (condition, cexpr) =
        while condition() do
            cexpr() |> ignore
        IO ()

    member this.TryWith (cexpr, handler) =
        try
            cexpr()
        with e -> handler e

    member this.TryFinally (cexpr, final) =
        try
            cexpr()
        finally
            final()

module IOUtils =

    let io = IOBuilder()

    let iofy (f : 'a -> 'b) =
        (fun a0 -> IO (f a0))

    let iofy2 (f : 'a -> 'b -> 'c) a0 a1 =
        IO (f a0 a1)

    let iofy3 (f : 'a -> 'b -> 'c -> 'd) a0 a1 a2 =
        IO (f a0 a1 a2)

    let iofy4 (f : 'a -> 'b -> 'c -> 'd -> 'e) a0 a1 a2 a3 =
        IO (f a0 a1 a2 a3)

    let iofy5 (f : 'a -> 'b -> 'c -> 'd -> 'e -> 'f) a0 a1 a2 a3 a4 =
        IO (f a0 a1 a2 a3 a4)

This code is quite simple, almost a disgrace for the monad system.

I think this covers every, here is my test script:


#load "IOBuilder.fs"

open IOMonad.IOUtils

let console_readline = iofy (System.Console.ReadLine)

let fake_readline = io { return "4" }

let fake_wrong_readline = io { return "2.0" }

let console_writeline = iofy (fun (s : string) -> System.Console.WriteLine s)

let main =
    io {
        let! input = fake_readline()
        let int_input = int input
        use! str = io { return new System.IO.StreamReader(@"C:\TheArtofUnitTesting.pdf") } <| ()
        while false do
            do! console_writeline "test is not a number"
        for x in 0 .. int_input do
            do! console_writeline (string x)
        try
            int "test" |> ignore
        with
        | _ -> do! console_writeline "test is not a number"
        try
            try
                let! input2 = fake_wrong_readline()
                let int_input2 = int input2
                do! console_writeline "2.0 is an integer"
            finally
                console_writeline "Did it work?" |> ignore
        with _ -> do! console_writeline "2.0 is not an integer"
        try
            let! input2 = fake_wrong_readline()
            let i = int input2
            do! console_writeline "2.0 is an integer"
        with :? System.FormatException -> do! console_writeline "2.0 is not an integer"
        return 2
    }

let main2 arg =
    io {
        let! main = main()
        do! console_writeline (main.ToString())
        do! console_writeline arg
    } <| ()

main2 "Done here!"

This allows us to mark everything we want as “IO”.

Every function which interacts with the user or affects the environment should return an IO.

But what about impure functions which are not IO, such as random number generators, GET web requests and reading of files? These are neither interacting with the user nor affecting the environment, but they are affected by the environment. I like to mark them with a special ImpureFunction attribute:


[<AttributeUsage(AttributeTargets.Method)>]

type ImpureFunctionAttribute() =
    inherit System.Attribute()

So we’d do this:


let random() =
    4

[<ImpureFunction>]
let random2() =
    System.Random().Next(1, 7)

You can also say that “impure functions are functions that might return different values for the same parameters”.

This entry was posted on Saturday, October 9th, 2010 at 8:43 PM and is filed under Default. You can follow any responses to this entry through the RSS 2.0 feed. You can skip to the end and leave a response. Pinging is currently not allowed.

Leave a Reply