Search code examples
f#functional-programmingconventionsstatements

F# wrapping statements in a do block


I have a question regarding conventions on using do blocks in F#. Primarily this comes up when working with the .NET library classes and other .NET code.

Let me give you an example.

1. With a do block wrapped around statements:

let drawHelloName (width:int, height:int) name = 
    let bmp = new Bitmap(width, height)
    use fnt = new Font("Arial", 12.0f)
    use gr = Graphics.FromImage(bmp)
    do
        gr.Clear(Color.White)
        gr.DrawString(name, fnt, Brushes.Black, PointF(10.0f, 10.0f))
    bmp

2. Without a do block:

let drawHelloName (width:int, height:int) name = 
    let bmp = new Bitmap(width, height)
    use fnt = new Font("Arial", 12.0f)
    use gr = Graphics.FromImage(bmp)
    gr.Clear(Color.White)
    gr.DrawString(name, fnt, Brushes.Black, PointF(10.0f, 10.0f))
    bmp

Now in my mind I think example 1 is clearer and more to the point and style of F#. Since it's not really "natural" to work with statements in functional programming, we explicitly wrap the statements in a do block to show that they are side-effects. But I am wondering, what is the convention regarding this?


Solution

  • Since it's not really "natural" to work with statements in functional programming, we explicitly wrap the statements in a do block to show that they are side-effects.

    I agree with you. However, if you look into F# code in the wild, they tend to be loose in that matter. There is no strict convention, just follow what you think is the best suitable for you.

    Another point is that do blocks create new scopes for values that we would like to explicitly control their lifetimes. For example, if you would like to dispose gr earlier and continue to use fnt, your first function can be written to:

    let drawHelloName (width:int, height:int) name = 
        let bmp = new Bitmap(width, height)
        use fnt = new Font("Arial", 12.0f)
        do
            use gr = Graphics.FromImage(bmp)
            gr.Clear(Color.White)
            gr.DrawString(name, fnt, Brushes.Black, PointF(10.0f, 10.0f))
        (* Continue to do something with 'fnt' *)
        bmp
    

    Another place where you have to use do blocks is inside implicit constructors e.g.

    type T(width, height) =
        let bmp = new Bitmap(width, height)
        use fnt = new Font("Arial", 12.0f)
        use gr = Graphics.FromImage(bmp)
        do
            gr.Clear(Color.White)
            gr.DrawString(name, fnt, Brushes.Black, PointF(10.0f, 10.0f))