Friday, February 5, 2010

Introducing Structural Types

Structural types allows one to declare types based on the methods the type has. For example you could define a method that takes a class containing a close method. This is fairy analogous to duck-typing in dynamic languages. Except that it is statically enforced.

The main example used here was from a comment on the Code Monkeyism Blog. The commenter further explains that this example is in fact from Beginning Scala chapter 4 (which I would like to read but have not yet had the time.)
  1. /*
  2. A can be any object that has a close method.  
  3. This is statically typed which makes some restrictions which are explained later
  4. */
  5. scala> def using[A <: {def close(): Unit}, B](param: A)(f: A => B): B = 
  6.      | try {
  7.      |     f(param)
  8.      |   } finally {
  9.      |     try {
  10.      |        param.close()
  11.      |     } catch { case _ => () }
  12.      |   }
  13. using: [A <: AnyRef{def close(): Unit},B](param: => A)(f: (A) => B)B
  14. scala> using(new java.io.ByteArrayInputStream("hello world".getBytes)){ in => 
  15.      | io.Source.fromInputStream(in) foreach (print)                          
  16.      | }
  17. hello world
  18. scala> using(new java.io.ByteArrayInputStream("hello world".getBytes)){ in => 
  19.      | io.Source.fromInputStream(in) mkString ""                              
  20.      | }
  21. res8: String = hello world

That is extremely powerful and the consequences will be visited more in the future. But because structural typing is statically enforced it is not quite as flexible as dynamic language's version of duck typing. For example you cannot do:
  1. scala> val i:Any = new java.io.ByteArrayInputStream("hello world".getBytes)
  2. i: Any = java.io.ByteArrayInputStream@145a25f3
  3. /*
  4. This does not work because 'i' is defined as type Any.  Not a type that is closeable.  
  5. Casting can be used to get around this issue.  I will address that in its own post
  6. */
  7. scala> using(i){ in => 
  8.      | io.Source.fromInputStream(in) mkString ""
  9.      | }
  10. < console>:8: error: inferred type arguments [Any,B] do not conform to method using's type parameter bounds [A <: AnyRef{def close(): Unit},B]
  11.        using(i){ in =>

An alternative to the first using example is to use call by name to construct the closeable. The reason one might want to do that is because it allows currying of the method:
  1. scala> def using[A <: {def close(): Unit}, B](param: =>A)(f: A => B): B = 
  2.      | val closeable = param  // notice param is only referenced once
  3.      | try {
  4.      |     f(closeable)
  5.      |   } finally {
  6.      |     try {
  7.      |        closeable.close()
  8.      |     } catch { case _ => () }
  9.      |   }
  10. using: [A <: AnyRef{def close(): Unit},B](param: => A)(f: (A) => B)B
  11. /*
  12. if this was accessing a database a new connection would be made automatically each time this function was used
  13. */
  14. scala> val usingTheWorld = using[BStream,Int](new java.io.ByteArrayInputStream("hello world".getBytes))_
  15. usingTheWorld: ((java.io.ByteArrayInputStream) => Int) => Int = < function1>
  16. scala> usingTheWorld { s => io.Source.fromInputStream(s).length}  
  17. res3: Int = 11
  18. scala> usingTheWorld { s => io.Source.fromInputStream(s).getLines().length}
  19. res5: Int = 0

6 comments:

  1. Thanks for linking, really enjoying Daily Scala, learned a lot.

    Cheers
    Stephan
    http://codemonkeyism.com

    ReplyDelete
  2. Hello

    I think I found a drawback at your example.

    Since using's 'param' is a call-by-name parameter its body will be executed for every call.

    So when you call "using(new java.io.ByteArrayInputStream("hello world".getBytes))" one ByteArrayInputStream will be created for 'f(param)' and another for 'param.close()', and I think it was not desireable

    ReplyDelete
  3. The pass by name really is incorrect, I'll agree with Fabio.

    Also, please enclose "close()" in a try/catch block too, since it may throw an exception as well, which should be disregarded, so that the original exception doesn't get lost. I know this is a small example, but I hope you know it will find its way into many, many programs. :-)

    ReplyDelete
  4. Thanks for catching that. I have updated that example and added a new example with the call-by-name because that form is useful as well.

    ReplyDelete
  5. Hi Jesse,

    Thanks for this post.
    I just want to point a small code issue (?) that I noticed: shouldn't the last "finally" be in fact a "catch" ?

    ReplyDelete
  6. I like what structural typing achieves, but I think a caller-side hack would be a better way to go about it as then method signatures would not just take Object.

    Granted, I'm an amateur, but I posted some examples of Scala/Whiteoak/Heron/caller-side structural typing approaches at:

    http://draconianoverlord.com/2010/01/17/caller-side-structural-typing.html

    ReplyDelete