Wednesday, 13 November 2013

Scala’s Type System – A Mid-Learning Checkpoint

Update (21st January, 2014): There is a subtle error in the section on “Enumerations (by extending Enumeration)”.  It’s fixed now.

WARNING: THIS POST CONTAINS WAY MORE QUESTIONS THAN ANSWERS

ALSO, APOLOGIES IF THE SCOPE OF THIS POST RIDES ROUGH-SHOD OVER THE CANONICAL DEFINITION OF A “TYPE-SYSTEM”.  IT SIMPLY CONTAINS EVERYTHING I BRING TO MIND WHEN I THINK ABOUT TYPES IN SCALA OR ANY LANGUAGE.  PLEASE FEEL FREE TO POINT OUT MY IDIOSYNCRACIES IN THE COMMENTS

You hear statements like “the best thing about Scala is it’s type system” and “Scala has a powerful type system" being flung around a lot.  For a long time, it’s been my aim to get a deep enough understanding of this aspect, as the people making this statement (in various forms) are people I respect a great deal.

I’ve made some brief forays into this territory before. Option Types is something I’ve heard Dick Wall talk about (and also something I think I understand despite not having covered it in my reading yet; thanks Dick) and the Fast Track to Scala course introduced me to the core class hierarchy (which seemed infinitely sensible – anything which includes “Any” as a type is clearly trying to strive for something I can relate to).  But these and other small aspects I’ve come across haven’t yet enabled me to honestly say I could defend the title of this post.

However, this isn’t to say I doubt I will get to this point eventually.  Just not yet.

To this end, I’ve just been went back over what I’ve learned so far about the type system.  It boils down to a set of facts. Lets begin with the ones which shouldn’t be surprising at all to a Java developer, plus a few little bits (signposted with italics) which might raise an eyebrow or produce a smile:

  • Classes and Objects – instantiate a class to create an object instance. Classes have fields and operations/methods
  • Creating Classes – class bodies are executed when the classes are created
  • Methods inside classes – methods have special access to other class elements (other methods and fields)
  • Fields – Always objects. Can be vals (immutable) or vars (mutable), or functions
  • Class Arguments – like constructors, but a list placed after the class name. Add a val/var to each definition to encapsulate it. You can have varargs too (but remember to put the definition last in the argument list)
  • Named and Default Arguments – you can specify the names of, and defaults for, Class arguments. If all args have defaults you can call “new *” without using any parentheses
  • Overloading – methods can be overloaded, as long as the argument lists differ (i.e. the signatures)
  • Constructors – automatically generated for us if we do nothing.  The expressions they contain are treated as statements, that is considered purely for their side effects. The result of the final expression is ignored, and the constructed object is returned instead. You can't use return half way through a set of constructor expressions either
  • Auxiliary Constructors – constructor overloading is possible, by defining a method called “this”. All must first call the Primary constructor (which is the constructor produced by the class argument list together with the class body) again using “this”. This means that ultimately, the primary constructor is always called first. Also you can’t use val or var defined arguments, as that would mean the field was only generated by that auxiliary constructor.  This guarantees all classes have the same structure
  • Case Classes – automatically creates all the fields for you as if you put the val keyword in front of each of them. (You can make the field a var if you like by pre-pending “var” to the definition.) You create them without having to use the “new” keyword. They also provide a nice default toString implementation. You cannot (from Scala 2.10 onwards) inherit from case classes 
  • Parameterized Types – at initialisation time, tells the compiler what type of object the container holds
  • Type Inference – don’t bother specifying the type explicitly, you don’t need to
  • Inheritance – inherit from another class using the extends keyword.  A derived class can only extend one base class, but a base class can be extended by any number of derived classes
  • Base Class Initialization – Scala guarantees all constructors are called within a class hierarchy during initialisation. If a base class has constructor arguments, then any class that inherits from that base must provide those arguments during construction. Derived-class primary constructors can call any of the overloaded, auxiliary constructors in the base class by providing the necessary constructor arguments in the call. You can’t call base-class constructors inside of overloaded derived-class constructors. The primary constructor is the “gateway” for all the overloaded constructors
  • Overriding Methods – provide an implementation in a derived class of a method in the base class (distinguished by the method signature).  The “override” keyword must be provided so Scala knows you intended to override. This gives us polymorphism just as you’d expect from Java. If you want to invoke the base-class version of a method, use the keyword “super”
  • Abstract Classes – like an ordinary class, except that one or more methods or fields is incomplete (i.e. without a definition or initialisation). Signified by the keyword “abstract” before the “class” keyword.  Use of “overrides” keyword is optional in definition of abstract methods in concrete subclasses
  • Polymorphism - If we create a class extending another abstract class A along with traits B and C, we can choose to treat that class as if it were only an A or only a B or only a C
  • Composition – just put something inside (i.e. as a field). Typically this is done with one (abstract) class and many traits with definitions but not implementations, deferring this implementation until the concrete base classes are created (aka “delay concreteness”)
  • Type Parameters – like Java Generics, from the perspective of the user 
  • Type Parameter Constraints – impose constraints on type parameters (again c.f. Java Generics)

So far, so (mostly predictable. Admittedly, there’s some syntactic sugar in it all (another way of thinking about the bits in italics) but there’s nothing to stretch the Java mind too much.  But there’s a lot more. The bits which go much further off-piste from a Java perspective (that I’ve come across so far) are as follows:

  • Functions as Objects – pass them around like any other object, and define methods to take functions as arguments
  • Function Literals – anonymous Functions, frequently only used once. defined by the ‘=>’ symbol. You can even assign an anonymous function to a var or val
  • Pattern Matching with Types – as well as pattern matching against values, you can pattern match against types
  • “Any” Typeany type, including functions
  • Pattern Matching with Case Classes – case classes were originally designed for this purpose.  When working with case classes, a match expression can even extract the argument fields
  • Enumerations (by extending Enumeration) – a collection of names. Enumeration is typically extended into an object. Within this object we need to define the set of vals assigned to Value (part of Enumeration) that the enumeration represents, enumeration fields and initialize each of them by calling the Enumeration.Value method.  Each call to Enumeration.Value returns a new instance of an inner class, also called Value. Furthermore you can and then alias the new enumeration object to the type Value (using the “type” keyword) to allow us to treat  as a type. For more information see Cay Hostmann’s “Scala for the Impatient”, pp. 65-66.
  • Enumerations (as a Subtype of a Trait) – we can also create something like an Enumeration by using a Tagging Trait.  Having taken this leap it doesn’t seem too great a leap to want to do other OO-type things with our Enumeration
  • Tuples – you can return more than one thing from a method, using a nice syntactic sugar to create and access and unpack them.  The same unpacking idiom is also accessible to case classes
  • Objects – by using the “object” keyword creates something you can’t create instances of – it already is an instance.  That means that “this” still works
  • Companion Objects – associated by having the same name as a class. If you create a field in the companion object, it produces a single piece of data for that field no matter how many instances of the associated class you make. Under the covers, creating a case class automatically creates a companion object with a factory method called “apply”. When you “construct” a new case class without using the “new” keyword Scala is actually calling “apply” (passing in the parameters you provided) and returning a new instance of the class
  • Traits – for assembling small, logical concepts, letting you acquire capabilities piecemeal rather than inheriting as a clump.  Ideally they represent a single concept. Defined using the “trait” keyword.  Mixed-in to a class by “extends” / “with” keywords (the former if it is the first, and there is no inheritance, the latter for all others).  You can mixin as many traits as you like, into concrete or abstract classes. Traits cannot be instantiated on their own. Traits can have values and functions, and be wholly or partially abstract if required (though they don’t have to be). Concrete classes which mixin traits must provide implementations of the abstract elements.  Abstract classes which mixin traits need not implement the abstract elements, but the concrete subclasses of them must.  Traits can extend from other traits, as well as abstract and concrete classes. If, when you mixin more than one trait, you combine two methods with the same signature (the name plus the type) you can (must) resolve these collisions by hand using super[Type].methodName
  • Tagging Traits – a way of grouping classes or objects together. Sometimes an alternative to Enumerations (combined with case objects). Indicated by the keyword “sealed” which indicates no more subtypes than the ones you see in the current source file
  • Case Objects – like a case class, but an object
  • Type Hierarchy – this is pretty fundamentally different from the one you’ll expect if coming from Java

There’s already a lot in what I’ve listed here, and from having used these elements, I can testify to the solid, expressive-but-terse code it lets you write.  But from reading ahead a little, and having listened in on conversations between people far brighter than myself, I know this is just the basics.  I’ll post back on those more in-depth topics once I come across them.

Onward!