Last active
December 10, 2015 15:19
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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