### <== Previous | Contents | Next ==>

## A Two-Page Introduction to the Type System (cont.)

#### More Advanced Stuff

- Unions: you can say an object is one of several different types using the type union operator |. For example, if a method returns either an Integer or a Str, you could say its return type is <Int|Str>.
- Messages/methods can have type parameters, which are just variables that stand for some type that varies for each send of the message, depending on its actual arguments. You have to indicate how the type system can figure out what the type variable's definition is for the actual send. The simplest way to do that is to use the 'def' keyword in the correct place in the type signature. For example, if we want to define an incredibly simple method that takes one argument and returns it, we can say:

      justPassingThrough: anObject <X def> ^<X>
          ^anObject

  Each time we send this message, the type system will define X to be the type of whatever you pass in as the argument anObject. This way, the return type will automatically change for each send, to the type of the argument you pass in. This adds a huge amount of flexibility to the type system.

  Here is a really important example of this construct. Suppose we have a dictionary that maps keys of type K to values of type V; its type would be <Dictionary[K,V]>, according to the way the generic class Dictionary is defined. Here is how we could write the signature of the #at:ifAbsent: method:

      at: key <K> ifAbsent: failBlock <[^FAILVAL def]> ^<V|FAILVAL>

  This says the method takes a key of type K, and a fail block that yields some type we will call FAILVAL that can vary depending on the block we pass in, and the method returns either a value of type V or the result of executing the failBlock, which is of type FAILVAL.

- There is an even more powerful way of telling the system how to figure out what the type variable is for each call. This is called an inference clause, and it comes at the end of the message/method signature, using the syntax {{...}}. It is just explicit instructions on how to figure out the definition of the type variable from the actual message send, in situations where the 'def' construct is not powerful enough. Don't get scared by these; just try to read them like english. Here is another way we could write the same #at:ifAbsent: signature above using the more powerful inference clause syntax (although we're not taking advantage of its power here):

      at: key <K> ifAbsent: failBlock<[^FAILVAL]> ^<V|FAILVAL>
          {{where FAILVAL is returnType of #value message of arg 1}}

  Just read this almost literally: for each send of #at:ifAbsent:, FAILVAL will be defined to be the return type of the #value message of whatever block we pass in for argument 1. That's another way of figuring out what type the block produces. You can do much more sophisticated things with inference clauses. The great thing about them is that although you have to think about them when you write the method, they do all the work automatically when you call the method, and let you write very sophisticated type signatures. Normally users don't pay any attention to them, but they are critical in allowing the libraries to support all the flexible ways Smalltalk methods uses objects.

- The final little trick that make all this stuff work is that any return statement (^) never produces a value, since it instead makes its containing method return, so such statements have the magic type <BottomType>, which is by definition compatible with (a subtype of) all other types (so therefore it is always at the bottom of any subtype hierarchy). This is because its type doesn't matter; it will never get an actual value. This is important because we can usually ignore such types in unions. Consider the method

      getMasterSize: d <Dictionary[Str,Str]> ^<Int>

          ^(d at: 'master' ifAbsent: [^0]) size

  Our #at:ifAbsent: method from before returns either the dictionary value type (<Str>), or the return type of the fail block. But the fail block doesn't ever produce a value, because it aborts the expression and make the method directly return 0 (which matches the method return type, so that's ok). So the failblock's return type is <BottomType>, so the #at:ifAbsent: message returns <Str|BottomType>. The type system ignores the BottomType, so we get <Str>, which supports the size message, which returns an <Int>, which matches the method return type, so the method is typesafe.

  This shows all the type system features we've seen working together on the kind of code Smalltalkers write all the time. Note that when you are writing the method above, the typing looks really simple; all the hard part is done in the libraries when defining the signatures for these very powerful methods like #at:ifAbsent:.

#### Other Things the Type System can Enforce

- In other Smalltalks, the private/public distinction is just a category naming convention, with no way to enforce it. Strongtalk can and does enforce privacy specifications. This is currently specified basically the same way it is done in other systems: a method is private if it is in a category whose name contains the substring 'private' anywhere. If you have methods that are only supposed to be used by closely cooperating external code (like the concept of 'friends' in other languages), the libraries use the convention that the category name contains the word 'restricted', but this is just convention, not part of the type system.
- The type system also understand the concept of abstract and concrete classes, and can ensure that all abstract methods are implemented by concrete classes. An abstract method is defined just the same as in other Smalltalks: you implement a dummy method with the body 'self subclassResponsibility'. The type system looks for these kinds of method bodies, and treats them specially. You will notice that when you enter such a method in a class, a special indicator () will appear in the message header in the browser. When you typecheck any class containing such an abstract method, or if such a method is inherited but not overridden with a real implementation, the typesystem will complain if the class is not abstract.You make a class abstract by using choosing 'Make Abstract' from the context menu for the class header, and henceforth the class header will say 'Abstract Class ...'

### Continue with the Tour ==>