The case for Scala's structural types

Today I saw the following conversation (written in portuguese) on twitter where my friends discussed the possibility to use Scala’s structural typing to emulate duck typing. They also talked about that it is highly dependent on the use of reflection making it super slow.

I totally agree with them that structural typing is slower than the other alternatives. However, I strongly believe that we shouldn’t avoid it completely. Specially because of the advantages like compile time checking. Let’s take the case of using a resource. Assuming an OutputStream (yeah, Java… I know, but it suits the example :)). The general steps to use in a sane environment should be:

  1. Open the stream;
  2. Use it;
  3. Close it.

The Java way of doing these steps is like this:

public class OutputStreamCloser {
    public static void main(String[] args) {
        OutputStream fos = null;
        try {
            fos = new FileOutputStream(new File("output"));
            fos.write("Hello World".getBytes());
        } 
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            if(fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Simple, understandable, and straight forward. If we try to convert it to Scala, bit by bit, it would be like this:

object OutputStreamCloser {
    def main(args: Array[String]): Unit = {
        var fos: OutputStream = null;
        try {
            fos = new FileOutputStream(new File("output"));
            fos.write("Hello World".getBytes());
        } 
        catch {
            case e: FileNotFoundException => e.printStackTrace();
            case e: IOException => e.printStackTrace();
        } 
        finally {
            if(fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

It works, but we are mixing infrastructure code (open and close an OutputStream) with business code (write the hello world to the file). Scala enables us to use closures to make cleanner code. So let’s use them.

object OutputStreamCloser {
    def main(args: Array[String]): Unit = {
        withOutputStream(new FileOutputStream("output")) { stream => 
            stream.write("Hello World".getBytes)
        }
    }

    def withOutpuStream(fos: OutputStream)(f: OutputStream => Unit): Unit = {
        try {
            f(fos);
        } 
        catch {
            case e: IOException => e.printStackTrace();
        } 
        finally {
            if(fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Cleaner, but we Scala programmers hate the use of nulls. And you should too. So let’s improve it and use the Option type. Wrapping an object in an Option enables us to avoid the if null checking because an Option(null) is just None and anything different from it is Some(x), where x is what you just passed. So, this is the final code:

object OutputStreamCloser {
    def main(args: Array[String]): Unit = {
        withOutputStream(new FileOutputStream("output")) { stream => 
            stream.write("Hello World".getBytes)
        }
    }

    def withOutpuStream(fos: OutputStream)(f: OutputStream => Unit): Unit = {
        Option(fos) match {
            case None => throw new IllegalArgumentException("You can't close a null output stream.")
            case Some(output) =>
                try {
                    f(output);
                }
                finally {
                    output.close();
                }
        }
    }
}

Now clearly we separated the plumbing code from what really matters and we guarantee that the output stream will be closed. What happens if I want to read a file instead of write to it? Will I have to write another method equal to the previous one just changing the parameter type? What if I want to use the same basic algorithm with other classes that must be closed?

Using some kind of common interface is not possible because I can’t change the API’s classes. You could say that you can use some implicit magic. But to use it you have to know beforehand the type of parameter. So the best thing should be something like duck typing. That’s where structural typing comes to the rescue.

We will describe a type T such that T.close() is always present, and if not the compiler will show me that I can’t do the operation through a compile time error. Altough slow, because of the use of reflection, its a good strategy to follow.

This is how you describe the structural typing:

object OutputStreamCloser {
    def main(args: Array[String]): Unit = {
        managed(new FileOutputStream("output")) { stream => 
        	stream.write("Hello World".getBytes)
        }
        managed(new FileInputStream("output")) { stream =>
        	println(someReadOperation(stream))
    	}
    }

    def managed[T <: { def close(): Unit }](resource: T)(f: T => Unit): Unit = {
    	Option(resource) match {
            case None => throw new IllegalArgumentException("You can't close a null output resource.")
            case Some(res) =>
                try {
                    f(res);
                }
                finally {
                    res.close();
                }
        }
    }
}

Structural typing is a rich way of ensuring that objects conform to a single interface without making them have a java interface or a scala trait forcing them to implement a method. This is a powerful way of doing duck typing, but must be used wisely.