Duck typing

A significant part of the work of a developer is to minimize the amount of code duplication. There are multiple different approaches to do this, including inheritance, abstraction, generics, type classes, and so on. There are cases, however, where strongly typed languages will require some extra work in order to minimize some of the duplication. Let's imagine that we have a method that can read and print the contents of a file. If we have two different libraries that allow us to read a file, in order to use our method, we will have to make sure the methods that read the file somehow become the same type. One way would be by wrapping them in a class that implements a specific interface. Provided that in both the libraries the read method has the same signature, which could easily happen, Scala can use duck typing instead, and this way it will minimize the extra work we will have to do.

Note

Duck typing is a term that comes from dynamic languages and it allows us to treat different types of objects in a similar manner based on a common method they have.

Another name for duck typing is structural typing.

Duck typing example

Everything becomes clear with an example. Let's imagine that we want to have a method, which can take a parser and print every word that the parser detects. Our parsers will have a method with the following signature:

def parse(sentence: String): Array[String]

A good way of doing this would be to have a common interface and make all the parsers implement it. However, let's set a condition that we cannot do this. The parsers could be coming from two different libraries that we cannot modify or connect in any way.

We have defined two different parser implementations for this project. The first one is as follows:

class SentenceParserTokenize {
  
  def parse(sentence: String): Array[String] = {
    val tokenizer = new StringTokenizer(sentence)
    Iterator.continually({
      val hasMore = tokenizer.hasMoreTokens
      if (hasMore) {
        (hasMore, tokenizer.nextToken())
      } else {
        (hasMore, null)
      }
    }).takeWhile(_._1).map(_._2).toArray
  }
    
}

This parser makes use of the StringTokenizer class and returns an array of all the words separated by spaces. Another implementation that does exactly the same is shown here:

class SentenceParserSplit {

  def parse(sentence: String): Array[String] = sentence.split("\s")
}

Here we just split the sentence using a regular expression for space.

As you can see, both the classes have a parse method with the same signature but they have no connection with each other. We, however, want to be able to use them in a method and avoid code duplication. Here is how we can do this:

object DuckTypingExample {

  def printSentenceParts(sentence: String, parser: {def parse(sentence: String): Array[String]}) =
    parser.parse(sentence).foreach(println)
  
  def main(args: Array[String]): Unit = {
    val tokenizerParser = new SentenceParserTokenize
    val splitParser = new SentenceParserSplit
    
    val sentence = "This is the sentence we will be splitting."
    
    System.out.println("Using the tokenize parser: ")
    printSentenceParts(sentence, tokenizerParser)
    
    System.out.println("Using the split parser: ")
    printSentenceParts(sentence, splitParser)
  }
}

In the preceding code, we passed both the parsers to the printSentenceParts method and everything compiles and works fine. The reason things work is because of duck typing and this can be seen in the highlighted part of our example. The output of our application is the following:

Duck typing example

We can use duck typing for requiring even more methods to be available for an object by just expanding the parameter signature.

Alternatives

As you can see from the preceding code, duck typing saves us from some extra code writing and the need to define common interfaces. Other ways to achieve the same would involve creating wrappers, which implement a common interface.

When to use duck typing

Overusing duck typing can negatively affect the code quality and application performance. You should not avoid creating common interfaces in favor of duck typing. It should be really used in cases when we cannot implement a common interface between different types. The argument about limiting the use of duck typing is further enhanced by the fact that, under the hood, they use reflection, which is slower and negatively impacts performance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.251.128