Skip to content

Instantly share code, notes, and snippets.

@sam
Last active December 10, 2015 15:19
Show Gist options
  • Save sam/4453953 to your computer and use it in GitHub Desktop.
Save sam/4453953 to your computer and use it in GitHub Desktop.
Scala script (with sh shebang so it's self-executing) demonstrating Scala 2.10 String Interpolation, class-name reflection, method overriding, import of another scope into a nested scope, a symbol, a tuple2 (aka Pair) and use of the cons operator.
#!/bin/sh
exec scala "$0" "$@"
!#
// The above prologue simply lets us make this script self-executable, as long as you have scala installed.
// As far as I can tell, a "main class" file *MUST* declare only one object in the top-level scope.
// So our other objects need to be in other files/packages, or defined in an inner-scope. Which is
// why we have a Lunch.Pizza object instead of declaring the Pizza object at the top-level scope.
object Lunch {
// We're not creating instances of Pizza, so declaring it as a singleton object makes sense.
object Pizza {
// You can't modify this trait, except in this same file since we've "sealed" it.
// Here's a nice clear explanation of the pattern you see below:
// http://stackoverflow.com/a/11203867
sealed trait Topping {
// We use "case objects" below. This commented override below just demonstrates what
// that gets you. A case class/object gets you:
// * A nice/pretty toString of the class and any values it has
// * apply() and unapply() methods
// * A Constructor (in the case of a case class)
//
// Since we *do* use "case object" below, we get our nice toString automatically,
// and don't actually need to write it ourselves. If we did, this is one way
// to do it though: Using reflective methods, and String#split.
// split expects something (a String or Char) it can translate to a Regex. Since
// the class-name is going to be '$' delimited and includes the package/class scope,
// we want to split on '$' and get back just the name of our actual class (ie: "mushroom").
// Since the argument to split is converted to a Regex, and "$" means end-of-line, we'd
// have to pass "\\$" to ensure "$" is properly escaped, meaning we want to match the
// actual character and not the Regex reserved symbol $. Alternatively we can do what
// I demonstrate below, which is pass a Character instance instead, and the escaping will
// be done for us automatically.
// override def toString = this.getClass.getName.split('$').last
}
object Toppings {
// Extend our Topping Trait here with actual named toppings.
// This pattern is one of the common ways to write what's in-effect an Enum.
// Scala also has an actual Enumeration class you can explore.
// For further reading: http://stackoverflow.com/a/1322828
case object sausage extends Topping
case object pepperoni extends Topping
case object mushroom extends Topping
}
}
// We want to be able to type:
// pepperoni
// To get a reference to the pepperoni Topping instead of having to type:
// Pizza.Toppings.pepperoni
// We can do that by importing "all values under..." Pizza.Toppings with the
// underscore placeholder.
// It's also worth noting that we're currently inside the Lunch object.
// The import doesn't need to pollute the whole file. We can limit it to
// nested scopes, including methods and anonymous functions!
import Pizza.Toppings._
// Just for goofs I'm creating a List of Toppings here.
// Let's break this down into two operations. First we'll make our list:
//
// val toppings = sausage :: pepperoni :: mushroom :: Nil
//
// Two things to understand here: Nil is an empty list. It doesn't mean "null"
// as in Ruby. It means literally: EmptyList. It can't be used as Ruby's "nil" or
// Java's "null" because those mean "not present". Nil in Scala is very specifically
// an actual instance of a List. It's never "null". It just doesn't contain any items.
// It may be jarring at first to untrain your brain from equating Nil with Null,
// but you get used to it really quickly. Just try to remember to substitute Nil with
// EmptyList mentally whenever you see it.
//
// So what's the point of having Nil on the end of our List there? We're building a
// List. We specifically don't want an empty one.
//
// This is a great example (IMO) of why Scala is awesome. Check this out:
//
// 1 :: 2 :: 3
//
// That looks like we're using some sort of List Literal syntax to build a List.
// Scala's version of Ruby's [ 1, 2, 3 ] Array Literal right?
//
// Not so my friend. Scala's methods signatures are much more flexible than Ruby
// (for the most part). "::" in our example above is an actual method call.
// Generally called the "cons" operator, but don't let the word "operator" fool you,
// it's not language syntax. It's just a regular method. Almost...
//
// One rule about method signatures in Scala is that if a method ends in a colon,
// as "::" obviously does, then the Receiver and Arguments are flopped when called.
// So in the "1 :: 2 :: 3" example we're not calling:
//
// 1.::(2.::(3))
//
// We're calling:
//
// 3.::(2).::(1)
//
// And since Lists are immutable, this is efficient since we're progressively building our
// final List by adding the previously created one as the tail of a new List.
//
// So back to Nil. Looking at the above for a minute you can probably guess that since "::"
// is a method for building Lists, it's probably not on Int. Because then to define it
// practically everywhere to make it useful. On Floats, Doubles, Longs, Strings, Tuples, etc etc.
//
// It'd be crazy.
//
// So it isn't defined there. The "1 :: 2 :: 3" example above won't compile because it can't
// find the "::" method. But since Nil is an empty List, it *is* defined on Nil!
// If you keep in mind the ends-with-colon rule for method names, and go all the way back
// to our example list:
//
// val toppings = sausage :: pepperoni :: mushroom :: Nil
//
// Now you know how to deciper that line. In your brain what you're actually looking at
// is something like this:
//
// val toppings = Nil.::(mushroom).::(pepperoni).::(sausage)
//
// And since the result of each "List::(value)" call is a new List, you can just keep chaining
// objects.
//
// Moving on:
val pizza = ('toppings -> (sausage :: pepperoni :: mushroom :: Nil))
//
// Two last things to explain there. Symbols in Ruby are written:
//
// :bob
//
// Symbols in Scala are written:
//
// 'bob
//
// So 'toppings above is a Symbol. The parenthesis around our List is
// just to bracket precedence so we get:
//
// ('Symbol -> List(sausage, pepperoni, mushroom))
//
// Instead of:
//
// List((Symbol -> sausage), pepperoni, mushroom)
//
// Finally, the last piece you may not have seen before: A -> B
// The stabby-arrow operator in Scala is used as a shortcut to create
// Pairs. Which are just Tuple2 instances. You can have a Tuple with 5, 6 or 14
// fields if you want:
//
// ('one, 'two, 'three, 'four, 'five)
//
// But if you just need a key/value pair, then the
// stabby-arrow is a convenient syntax for creating a Tuple2.
// Lastly, here's our main method. If you have a script with only one object
// (or you've explicitly passed the name of the main-class), and a method
// with the signature:
//
// def main(args: Array[String]):Unit
//
// Then that method will automatically be called by your script.
def main(args: Array[String]) = println(s"Please for to a pizza with $pizza.\n\nThanks!")
// Our methods calls println() which returns Unit aka void, so Scala's type-inference has us
// sorted on the method signature and we don't have to explicitly say that the return type of
// our method is Unit. The last bit of syntax to demonstrate is the new string-interpolation
// support added to Scala 2.10 (just released officially today!). To use it, begin your String
// with "s" before the opening quotes:
//
// s"My String"
//
// And refer to variables with s"$myvar" or s"${some.expression(42) == true}".
// Further reading on 2.10's string-interpolation:
// http://docs.scala-lang.org/overviews/core/string-interpolation.html
}
// That's all folks!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment