| Recently, reader feedback has led me to 
     realize that I have left behind an important aspect of 
     Scala's language while crafting this series: Scala's package 
     and access modifier facilities. So I'm going to take a 
     moment and cover this before diving into one of the more 
     functional elements of the language, the applymechanism.Packaging To help segregate code in such a way that it doesn't 
     conflict with one another, Java™ code provides the 
     packagekeyword, creating a lexical namespace in 
     which classes are declared. In essence, putting a classFooin a package named com.tedneward.util 
     modifies the formal class name to
     com.tedneward.util.Foo; it must be referenced as 
     such. Java programmers will be quick to point out that they 
     don't do this, theyimportthe package and thus 
     save themselves from having to type the formal name out. 
     This is true, but it merely means that the work of 
     referencing the class by its formal name falls to the 
     compiler and bytecode. A quick glance at javap output 
     reveals this to be the case. Packages in the Java language have a few quirks to them, 
     however: The package declaration must appear at the top of 
     the .java file in which the package-scoped classes appear 
     (which causes some serious havoc with the language when 
     trying to apply annotations to the package); the declaration 
     holds scope across the entire file. This means that the rare 
     case in which two classes are tightly-coupled across package 
     boundaries has to be split across files, leading the unwary 
     to not recognize the tight coupling between the two. Scala takes a slightly different approach in respect to 
     packaging, treating it as a combination of the Java 
     language's declarationapproach and C#'s
     scoped approach. With that in mind, a Java developer 
     can do the traditional Java approach and put apackagedeclaration at the top of a .scala file just as normal Java 
     classes do; the package declaration applies across the 
     entire file scope just as it does in Java code. 
     Alternatively, a Scala developer can use Scala's package 
     "scoping" approach in which curly braces delimit the scope 
     of thepackagestatement, as in Listing 1: Listing 1. Packaging made simple
 
 
      
       |                 
package com
{
  package tedneward
  {
    package scala
    {
      package demonstration
      {
        object App
        {
          def main(args : Array[String]) : Unit =
          {
            System.out.println("Howdy, from packaged code!")
            args.foreach((i) => System.out.println("Got " + i) )
          }
        }
      }
    }
  }
}
 |  
 Effectively, this code declares one class, App, or to be 
     more precise, a single class called
     com.tedneward.scala.demonstration.App. Note that 
     Scala also permits the package names to be dot-separated, so 
     Listing 1 could be written more tersely as shown in Listing 
     2: Listing 2. Packaging made simple 
     (redux)
 
 
      
       |                 
package com.tedneward.scala.demonstration
{
  object App
  {
      def main(args : Array[String]) : Unit =
      {
        System.out.println("Howdy, from packaged code!")
        args.foreach((i) => System.out.println("Got " + i) )
      }
  }
}
 |  
 Use whichever style seems more appropriate because they each compile 
     into exactly the same code constructs. (The scalac compile 
     goes ahead and generates the .class files in 
     package-declared subdirectories as javac does.) Import Of course, the logical flip-side of packaging is 
     import, Scala's mechanism for bringing names into the 
     current lexical namespace. Readers of this series have 
     already seenimportin a couple of samples 
     before now, but it's time for me to point out some of
     import's features that will come as a surprise to 
     Java developers. First of all, you can use importanywhere 
     inside the client Scala file, not just at the top of the 
     file and correspondingly, will have scoped relevance. Thus, 
     in Listing 3, thejava.math.BigIntegerimport 
     is scoped entirely to the methods defined inside the objectAppand nowhere else. If another class or 
     object inside ofmathfunwanted to use
     java.math.BigInteger, it would need to import the 
     class just asAppdid. Or if several classes inmathfunall wanted to use
     java.math.BigInteger, the import could occur at the 
     package level outside the definition ofAppand 
     all of the classes in this package scope will have
     BigIntegerimported. Listing 3. Import scoping
 
 
      
       |                 
package com
{
  package tedneward
  {
    package scala
    {
        // ...
      
      package mathfun
      {
        object App
        {
          import java.math.BigInteger
        
          def factorial(arg : BigInteger) : BigInteger =
          {
            if (arg == BigInteger.ZERO) BigInteger.ONE
            else arg multiply (factorial (arg subtract BigInteger.ONE))
          }
        
          def main(args : Array[String]) : Unit =
          {
            if (args.length > 0)
              System.out.println("factorial " + args(0) +
                " = " + factorial(new BigInteger(args(0))))
            else
              System.out.println("factorial 0 = 1")
          }
        }
      }
    }
  }
}
 |  
 Importing doesn't stop there, however. Scala sees no real reason to 
     differentiate between top-level members and nested ones, so 
     you can use importto bring not just nested 
     types into lexical scope, but any member; by importing all 
     of the names insidejava.math.BigInteger, for 
     example, you can drop the scoped references to ZERO and ONE 
     to just name references as in Listing 4: Listing 4. Static imports ... without 
     the static
 
 
      
       |                 
package com
{
  package tedneward
  {
    package scala
    {
        // ...
 
      package mathfun
      {
        object App
        {
          import java.math.BigInteger
          import BigInteger._
        
          def factorial(arg : BigInteger) : BigInteger =
          {
            if (arg == ZERO) ONE
            else arg multiply (factorial (arg subtract ONE))
          }
        
          def main(args : Array[String]) : Unit =
          {
            if (args.length > 0)
              System.out.println("factorial " + args(0) +
                " = " + factorial(new BigInteger(args(0))))
            else
              System.out.println("factorial 0 = 1")
          }
        }
      }
    }
  }
}
 |  
 By using the underscore (remember the wildcard character in Scala?), 
     you effectively tell the Scala compiler that all of the 
     members inside BigIntegershould be brought 
     into scope. And becauseBigIntegerhas already 
     been put into scope by the previous import statement, 
     there's no need to explicitly package-qualify the class 
     name. In fact, these could even be combined into a single 
     statement becauseimportcan take multiple, 
     comma-separated targets to import (shown in Listing 5): Listing 5. Bulk imports
 
 
      
       |                 
package com
{
  package tedneward
  {
    package scala
    {
        // ...
 
      package mathfun
      {
        object App
        {
          import java.math.BigInteger, BigInteger._
        
          def factorial(arg : BigInteger) : BigInteger =
          {
            if (arg == ZERO) ONE
            else arg multiply (factorial (arg subtract ONE))
          }
        
          def main(args : Array[String]) : Unit =
          {
            if (args.length > 0)
              System.out.println("factorial " + args(0) +
                " = " + factorial(new BigInteger(args(0))))
            else
              System.out.println("factorial 0 = 1")
          }
        }
      }
    }
  }
}
 |  
 This saves you a line or two. Note that the two cannot be combined: the 
     first imports the BigIntegerclass itself and 
     the second, the various members inside that first class. You can also use importto introduce other 
     non-constant members as well. For example, consider a math 
     utility library (of questionable value perhaps, but 
     still...) in Listing 6: Listing 6. Enron's accounting code
 
 
      
       |                 
package com
{
  package tedneward
  {
    package scala
    {
        // ...
      
      package mathfun
      {
        object BizarroMath
        {
          def bizplus(a : Int, b : Int) = { a - b }
          def bizminus(a : Int, b : Int) = { a + b }
          def bizmultiply(a : Int, b : Int) = { a / b }
          def bizdivide(a : Int, b : Int) = { a * b }
        }
      }
    }
  }
}
 |  
 Using this library could get quite annoying over time, having to type
     BizarroMathevery time one of its members was 
     requested, but Scala allows for each of the members of
     BizarroMathto be imported into the top-level lexical 
     namespace, almost as if they were global functions (shown in 
     Listing 7): Listing 7. Calculating Enron's 
     expenses
 
 
      
       |                 
package com
{
  package tedneward
  {
    package scala
    {
      package demonstration
      {
        object App2
        {
          def main(args : Array[String]) : Unit =
          {
            import com.tedneward.scala.mathfun.BizarroMath._
            
            System.out.println("2 + 2 = " + bizplus(2,2))
          }
        }
      }
    }
  }
}
 |  
 There are other interesting constructs that would allow a Scala 
     developer to write the more natural 2 bizplus 2, 
     but that will have to wait for another day. (Readers curious 
     about a potentially heavily-abusable Scala feature can look 
     at the Scalaimplicitconstruct as covered in
     Programming in Scala by Odersky, Spoon, and Venners.) Access While packaging (and importing) are part of the 
     encapsulation and packaging story in Scala, a large part of 
     it, as with Java code, lies in its ability to restrict 
     access to certain members in a selective way — in other 
     words, in Scala's ability to mark certain members "public," 
     "private," or somewhere in-between. The Java language has four levels of access: public, 
     private, protected, and package-level access (frustratingly 
     applied by leaving out any keyword). Scala:  
      Does away with package-level qualification (in a 
      way) 
      Uses "public" by default 
      Specifies "private" to mean "accessible only to this 
      scope"  By contrast, "protected" is definitely different from its 
     counterpart in Java code; where a Java protected member is 
     accessible to both subclasses and the package in which the 
     member is defined, Scala chooses to grant access only to 
     subclasses. This means that Scala's version of protected is 
     more restrictive (although arguably more intuitively so) 
     than the Java version. Where Scala truly steps away from Java code, however, is 
     that access modifiers in Scala can be "qualified" with a 
     package name, indicating a level of access up to 
     which the member may be accessed. For example, if the 
     BizarroMathpackage wants to grant member access to 
     other members of the same package (but not subclasses), it 
     can use the code in Listing 8 to do so: Listing 8. Enron's accounting code
 
 
      
       |                 
package com
{
  package tedneward
  {
    package scala
    {
        // ...
      
      package mathfun
      {
        object BizarroMath
        {
          def bizplus(a : Int, b : Int) = { a - b }
          def bizminus(a : Int, b : Int) = { a + b }
          def bizmultiply(a : Int, b : Int) = { a / b }
          def bizdivide(a : Int, b : Int) = { a * b }
    
              private[mathfun] def bizexp(a : Int, b: Int) = 0
        }
      }
    }
  }
}
 |  
 Note the private[mathfun]expression here. In essence, the 
     access modifier is saying that this member is private up 
     to the packagemathfun; this means that any 
     member of the packagemathfunhas access tobizexpbut nothing outside of that package 
     does, including subclasses. The powerful meaning of this is that any package can be 
     declared in the "private" or "protected" declaration all the 
     way up to com(or even_root_which is an alias for the root namespace, thus essentially 
     makingprivate[_root_]the same thing as 
     "public"). This provides a degree of flexibility in access 
     specification far beyond what the Java language provides. In fact, Scala offers one more degree of access 
     specification: the object-private specification, 
     illustrated by private[this], which stipulates 
     that the member in question can only be seen by members 
     called on that same object, not from different objects, even 
     if they are of the same type. (This closes a small hole in 
     the Java access specification system that was useful for 
     Java programming interview questions and not much more.) Note that the access modifiers will have to map on top of 
     the JVM at some level and as a result, some of the 
     subtleties in their definition will be lost when compiled or 
     called from regular Java code. For example, the BizarroMath 
     example above (with the private[mathfun]-declared 
     memberbizexp) will generate the class 
     definition in Listing 9 (when viewed with javap): Listing 9. Enron's accounting library, 
     JVM view
 
 
      
       |                 
Compiled from "packaging.scala"
public final class com.tedneward.scala.mathfun.BizarroMath
   extends java.lang.Object
{
    public static final int $tag();
    public static final int bizexp(int, int);
    public static final int bizdivide(int, int);
    public static final int bizmultiply(int, int);
    public static final int bizminus(int, int);
    public static final int bizplus(int, int);
}
 |  
 As is obvious from the second line of the compiled BizarroMathclass, thebizexp()method was given a 
     JVM-level access specifier ofpublicwhich 
     means that the subtleprivate[mathfun]distinction was lost once the Scala compiler was finished 
     with its access checks. As a result, for Scala code that is 
     intended to be used from Java code, I'd prefer to stick with 
     the traditional "private" and "public" definitions. (Even 
     "protected" will sometimes end up mapping to JVM-level 
     "public," so when in doubt, consult javap against the actual 
     compiled bytecode to be certain of its access level.) Application In the preceding article in the series ("Collection 
     types"), when talking about arrays in Scala (Array[T]s 
     to be exact) I said, "obtaining the i'th element of the 
     array" was in fact "another one of those methods with funny 
     names...." As it turns out, although I didn't want to get 
     into the details then, this wasn't exactly true. OK, I admit it, I lied. Technically, the use of the parentheses against the 
     Array[T]class is a tad bit more complicated than 
     simply a "method with a funny name"; Scala reserves a 
     particular nomenclature association for that particular 
     sequence of characters (that being the 
     left-parens-right-parens sequence) because that is so often 
     used with a particular intent in mind: that of "doing" 
     something (or in functionalspeak, "applying" something to 
     something). In other words, Scala has a special syntax (more 
     accurately, a special syntactic relationship) in place for 
     the "application" operator "()". To be precise, Scala 
     recognizes the method called apply()as the 
     method to invoke when said object is invoked using()as the method call. For example, a class that wants to 
     behave as a functor (an object that acts as a 
     function) can define anapplymethod to provide 
     function- or method-like semantics: Listing 10. Play that Functor music, 
     code boy!
 
 
      
       |                 
class ApplyTest
{
  import org.junit._, Assert._  
  
  @Test def simpleApply =
  {
    class Functor
    {
      def apply() : String =
      {
        "Doing something without arguments"
      }
      
      def apply(i : Int) : String =
      {
        if (i == 0)
          "Done"
        else
          "Applying... " + apply(i - 1)
      }
    }
    val f = new Functor
    assertEquals("Doing something without arguments", f() )
    assertEquals("Applying... Applying... Applying... Done", f(3))
  }
}
 |  
 Curious readers will be wondering what makes a functor different from 
     an anonymous function or closure. As it turns out, the 
     relationship is fairly obvious: The Function1 type in the 
     standard Scala library (meaning a function that takes one 
     parameter) has an applymethod on its 
     definition. A quick glance through some of the generated 
     Scala anonymous classes for Scala anonymous functions will 
     reveal that the generated classes are descendants of 
     Function1 (or Function2 or Function3, depending on how many 
     parameters the function takes). This means that where anonymous or named functions don't 
     necessarily fit the design approach desired, a Scala 
     developer can create a functorclass, provide 
     it with some initialization data stored in fields, and then 
     execute it via()without any common base class 
     required (as would be the case for a traditional Strategy 
     pattern implementation): Listing 11. I said 'play that Functor 
     music, code boy!'
 
 
      
       |                 
class ApplyTest
{
  import org.junit._, Assert._  
  // ...
  
  @Test def functorStrategy =
  {
    class GoodAdder
    {
      def apply(lhs : Int, rhs : Int) : Int = lhs + rhs
    }
    class BadAdder(inflateResults : Int)
    {
      def apply(lhs : Int, rhs : Int) : Int = lhs + rhs * inflateResults
    }
    val calculator = new GoodAdder
    assertEquals(4, calculator(2, 2))
    val enronAccountant = new BadAdder(50)
    assertEquals(102, enronAccountant(2, 2))
  }
}
 |  
 Any class that provides the appropriately-argumented applymethod will work when called as long as the arguments line 
     up in number and in type. Conclusion Scala's packaging, import, and access modifier mechanisms 
     provide the fine degree of control and encapsulation that a 
     traditional Java programmer has never enjoyed. For example, 
     they offer the ability to import select methods of an 
     object, making them appear as global methods, without the 
     traditional drawbacks of global methods; they make working 
     with those methods exceedingly easy, particularly if those 
     methods are methods that provide higher-order functionality, 
     such as the fictitious tryWithLoggingfunction 
     introduced earlier in this series ("Don't 
     get thrown for a loop!"). Similarly, the "application" mechanism allows Scala to 
     hide execution details behind a functional facade such that 
     programmers may not even know (or care) that the thing 
     they are invoking isn't actually a function but an object 
     imbued with significant complexity. The mechanism provides 
     another dimension to Scala's functional nature, one which 
     can certainly be done from the Java language (or C# or C++ 
     for that matter) but not with the degree of syntactic purity 
     Scala provides. That's it for this installment; until next time, enjoy! | 
   
No comments:
Post a Comment