Seven Languages In Seven Weeks: Scala - Day 2
I just finished day 2 of Scala from my Seven Languages in Seven Weeks book by Bruce Tate. While feeling the pain of syntax rules that are beyond my current understanding, Scala is proving to be a fun language. And, after several people have questioned my style (perhaps rightly so), I have really made it a point to start going with the flow and embracing the idioms of the various languages. That means not using semi-colons (be still, my beating heart), not using braces, and not using any syntax that the languages in this book have consciously tried to shed. If these languages are trying to be more friendly and readable, then I owe it to myself to see if that is true.
Once again, I'd like to give a huge shout-out to Sean Corfield who took the time last night to help me get past some huge syntactic hurdles. I was having a lot of trouble with homework problem #2 and Sean was able to clear up some misconceptions that I had about value deconstruction. Namely, you can't deconstruct tuples as part of a closure signature. Sean, you rock!
That said, let's get to the homework! Woohooo! Homework!
HW1: Use foldLeft to compute the total size of a list of strings.
// Use foldLeft to compute the total size of a list of strings.
// Define our list of strings.
val women = List(
"Katie",
"Sarah",
"Joanna",
"Libby"
)
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// Now, use foldLeft, to get sum the length of each string.
val charLength = (0 /: women){ ( sum, woman ) =>
// Add the length of the woman to the current sum of
// character lengths. This result will be passed in as
// the "sum" of the next block invocation (as it is applied
// to the next woman).
(sum + woman.size)
}
// Output the result.
println( "Total Length: " + charLength )
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// Now, we are going to use the alternate foldLeft notation to
// accomplish the same thing. This approach using currying - the
// process by creating a function that implicity incorprates other
// parameters. In our case, we are currying the foldLeft concept,
// seeding it with the parameter "0".
val charLength2 = women.foldLeft( 0 )( ( sum, woman ) =>
// The same exact code block logic aplied as above.
(sum + woman.size)
)
// Output the result.
println( "Total Length: " + charLength2 )
By the time I was done with day two's homework problems, I absolutely fell in love with the foldeLeft() concept! There's just something so pleasing about it. And, as I told Sean Corfield over IM last night, I am quite sure that I will quickly come to abuse it.
That said, the above code demonstrates two approaches to applying foldLeft() on a list. One uses the foldLeft operator "/:"; the other uses the foldLeft() class method. As far as I know, the operator is nothing more than a short-hand for the foldLeft() class method. The foldLeft() class method creates and returns a curried function. If you've never heard of that term, I'd recommend reading Stoyan Stefanov's Javascript Patterns book (that's where I first heard of it). A curried function is one that implies some part of a method signature, presenting a new method with a reduced argument list. Stefanov has a wonderful explanation of this including the way in which closures really make it possible.
When we run the above code, we get the following console output:
Total Length: 21
Total Length: 21
As you can see, both foldLeft approaches properly sum the lengths of each word contained within the list.
HW2: Write a Censor trait with a method that will replace the curse words Shoot and Darn with Pucky and Beans alternatives. Use a map to store the curse words and their alternatives.
A "trait" in Scala is like an Interface that can be partially implemented. Or, would it be more like an abstract class with some defined methods? I don't know the OO terminology well enough to be able to relate this back to Java; but, a trait is basically a partial class definition that can be used to extend other classes.
Now, in the book, Tate uses the "with" operator to apply a trait to a given class. I, however, could not get that to work. I had to end up using the "extends" operator in order to mix the Censor trait into my target Class. From what I read online, it looks like the "with" operator can only be used in conjunction with the "extends" operator for multi-class inheritance. But, don't take my word for that!
// Write a Censor trait with a method that will replace the curse
// words Shoot and Darn with Pucky and Beans alternatives. Use a map
// to store the curse words and their alternatives.
// Define our Censor trait that can be used with any class. This
// contains a list of bad words, the good words they map to, and a
// censor() method which will sanitize any given input.
trait Censor {
// Define our list of dirty words and the nice alternative
// words that they map to.
val censoredWords = Map(
"Shoot" -> "Pucky",
"Darn" -> "Beans"
)
// Define the method that will take a string and replace it with
// the censored words. NOTE: This method will be a class method
// of the class that inclused is.
def censor( content: String ) = {
// We can sanitize the content by folding it left accross the
// the set of dirty words. Each given word will receive the
// stantization of the word before it.
censoredWords.foldLeft( content )(
// Define the block signature. Note that in this parameter
// list, mapping is a tuple ( goodWord, badWord ).
( censoredContent, mapping ) =>
// Clearn this bad word by replacing all word-boundary
// instances with the good word. This result will be
// passed to the next foldLeft operation.
censoredContent.replaceAll(
("(?i)\\b" + mapping._1 + "\\b"),
mapping._2
)
)
}
}
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// Define the book class and extend our Censor trait.
class Book( val content: String ) extends Censor {
// Define a generic getter for our content.
def getContent() = this.content
// Define a censored getter for our content. This will pass
// the content through our censor trait method.
def getCensoredContent() = this.censor( this.getContent )
}
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// Create an instance of the book. Notice that this book has a
// few racy words within its content.
val book = new Book(
"Well Shoot! If you're not the prettiest woman I've ever " +
"seen, you can call me a monkey's uncle. Shoot! I wish " +
"I had my camera handy."
)
// Print the content.
println( "Content:" )
println( book.getContent )
// Print the censored content.
println( "\nCensored Content:" )
println( book.getCensoredContent )
As you can see, here we are again using the foldLeft approach. This time, in order to sanitize the given text, I am folding the content over the Map of dirty words. For each word mapping, I take the current content and swap out the dirty word with the clean word. The result of said swapping then becomes the product of the foldLeft iteration, which is passed as the current content to the next iteration.
FoldLeft for the win!
When we run the above code, we get the following output:
Content:
Well Shoot! If you're not the prettiest woman I've ever seen, you can call me a monkey's uncle. Shoot! I wish I had my camera handy.
Censored Content:
Well Pucky! If you're not the prettiest woman I've ever seen, you can call me a monkey's uncle. Pucky! I wish I had my camera handy.
As you can see, each dirty word was replaced with its mapped clean word.
Before we move on, I just wanted to show you were Sean Corfield really saved the day. If you look at the foldLeft method call, I have the following code:
.foldLeft( content )( ( censoredContent, mapping ) => ....
For a Map instance, the foldLeft() class method expects a closure with a two-argument signature. In my case, I used "censoredContent" and "mapping." That second argument - mapping - is a tuple containing the key-value pair in the current map iteration. Now, when I first tried to solve this problem, I was attempting to deconstruct that tuple as part of the closure signature:
.foldLeft( content )( ( censoredContent, ( goodWord, badWord ) ) => ....
For some reason, I though that the incoming values could be unified with the complex argument list. In Prolog, list "arguments" could be deconstructed in to "[Head|Tail]." In Scala, however, this turned out to not be true. Hence, why my solution defines the tuple argument and then uses its members ._1 ._2 within the closure body.
HW3: Load the curse words and alternatives from a file.
In this case, since the word mappings were loaded from a file, I was no longer comfortable treating Censor as a trait. Once we crossed over into a configuration with external dependencies, I felt more emotionally comfortable treating Censor as a stand-alone class that could be passed around as a behavior.
Here is the data file that contained the curse word mappings. Each line has a single mapping, separated by a double-colon:
Shoot::Pucky
Darn::Beans
As you'll see in the following solution, I might be trying to wedge the foldLeft approach into too many situations. Once I read in the file of dirty words, I am trying to map each line of the file into the Map of dirty/clean words. Since Maps in Scala are immutable, I can't add keys to the collection once it has been defined. Using foldLeft to build up the map felt like a good solution because each iteration of the foldLeft could return a completely new map with the augmented key-value pair.
// Load the curse words and alternatives from a file.
// Since we are loading the curse words from a file, I no longer
// want to treat the Censor concept as a trait. Rather, I'd like to
// define it as a class that can be configured and passed around as
// a behavior.
class Censor( val definitionFilePath: String ){
// Get a handle on the file that contains are our mappings.
val definitions = io.Source.fromFile( definitionFilePath )
// Define our list of dirty words and the nice alternative
// words that they map to. This will start out empty and then
// get populated by the definition file.
var censoredWords = Map[ String, String ]()
// Now, overwrite the censored map with the mappings contained
// within the lines of the dictionary file.
censoredWords = definitions.getLines.foldLeft( censoredWords ){
// Define the signature of our code block.
( mappings, data ) =>
// Each line of the file contains a single mapping, separated
// by "::". Example: Darn::Beans.
val terms = data.split( "::" )
// Add the current mapping to the aggregate mappings. This
// will create a new Map and pass it to the next iteration
// of the foldLeft().
(mappings + (terms( 0 ) -> terms( 1 )))
}
// Define the method that will take a string and replace it with
// the censored words.
def sanitize( content: String ) = {
// We can sanitize the content by folding it left accross the
// the set of dirty words. Each given word will receive the
// stantization of the word before it.
censoredWords.foldLeft( content )(
// Define the block signature. Note that in this parameter
// list, mapping is a tuple ( goodWord, badWord ).
( censoredContent, mapping ) =>
// Clearn this bad word by replacing all word-boundary
// instances with the good word. This result will be
// passed to the next foldLeft operation.
censoredContent.replaceAll(
("(?i)\\b" + mapping._1 + "\\b"),
mapping._2
)
)
}
}
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// Define the book class. This time, we are passing in both our
// content as well as a behavior for censoring.
class Book( val content: String, val censor: Censor ){
// Define a generic getter for our content.
def getContent() = this.content
// Define a censored getter for our content. This will pass
// the content through our censor behavior.
def getCensoredContent() = this.censor.sanitize( this.getContent )
}
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// Create an instance of the book. Notice that this book has a
// few racy words within its content.
val book = new Book(
"Well Shoot! If you're not the prettiest woman I've ever " +
"seen, you can call me a monkey's uncle. Shoot! I wish " +
"I had my camera handy.",
// Pass in the censor behavior based on the given dictionary.
new Censor( "./dictionary.txt" )
)
// Print the content.
println( "Content:" )
println( book.getContent )
// Print the censored content.
println( "\nCensored Content:" )
println( book.getCensoredContent )
As you can see, Censor is now a full fledged class, not a Trait. And, when I instantiate my Book class, I pass in the configured Censor as the book's sanitization "behavior." This time, when we run the above code, we get the following output:
Content:
Well Shoot! If you're not the prettiest woman I've ever seen, you can call me a monkey's uncle. Shoot! I wish I had my camera handy.
Censored Content:
Well Pucky! If you're not the prettiest woman I've ever seen, you can call me a monkey's uncle. Pucky! I wish I had my camera handy.
Once again, this worked wonderfully.
One thing to notice is that while this is Scala, some of the methods that I am using - split() and replaceAll() - are Java methods. When possible Scala relies on the underlying Java objects. In this case, it is passing around Java String instances which have powerful regular expression and manipulation methods.
A language that seems to err on the size of immutable objects feels somewhat limiting. But, part of that might just be an irrational fear that creating instances is somehow not performant. Of course, I'm not actually basing that on any real field experience. If creating a new Map instance for every iteration of foldLeft is performant, then heck, I guess its cool. And if this immutable state approach has huge payoffs for concurrency, then all the better.
Want to use code from this post? Check out the license.
Reader Comments
I have ordered this book in the last day and am looking forward to getting stuck into the 'homework'!
Thanks for sharing your analysis and breaking down each homework question, your information will no doubt be helpful to me in the near future!
@Jbuda,
Awesome! That's very exciting to hear. I've been having a great time going through the book. For a long time, I sort of wished I could go back to school; I think this, in part, fills that desire. Because each day comes with assignments, it definitely creates a wonderful structure for learning.
I have been trying to find new and alternative ways to carry out tasks i can accomplish with my current skillset. But never found any use for new methods... however, i am hoping that with this book i can get a better understanding of how i can integrate new techniques with what i currently know.
Im definitely with you on the 'being back at school' and learning new things!
@Jbuda,
Yeah, sometimes just seeing new things sparks inspiration!