In this post, we are going to examine the control abstraction, and this topic will focus on the following points:
Firts, let's see the example that can reduce the client code.
// simplified_client_code.scala // description // in this file we will examine how can we leverage the advanced control abstraction to write less code while accomplish more // // the contrsuct for the control abstraction includes the following // foreach // last // firt // any // all // exists // ... def containsNeg(nums : List[Int]) = { var exist = false for (num <- nums) { if (num < 0) exist = true } exist } // def containsOdd(nums: List[Int]) = { nums.exists(_ % 2 == 1) } containsOdd(List(1,2 ,3, 5)) containsNeg(List(1, 2, -3, 8))
An file matcher example is given
// file_matcher.scala // // based on some file matcher strategy and files that matches to a certain criteria import scala.io.Source object FileMatcher { private def filesHere = new java.io.File(".").listFiles private def fileMatching(matcher : String => Boolean) = { for (file <- filesHere; if matcher(file.getName)) yield file } def filesEnding(query: String) = fileMatching(_.endsWith(query)) def filesContaining(query: String) = fileMatching(_.contains(query)) def filesRegex(query: String) = fileMatching(_.matches(query)) } // but let's see how itr evolvs // imperative style object FileMatcher1 { private def filesHere = new java.io.File(".").listFiles def filesEnding(query: String) = { for (file <- filesHere ; if file.getName.endsWith(query)) yield file } def filesContains(query: String) = { for (file <- filesHere ; if file.getName.contains(query)) yield file } def fileRegex(query: String) = { for (file <- filesHere ; if file.getName.matches(query)) yield file } } // now let 'e move it to be more general // let 's introduce the lambada expression // step 1. the lambda expression do not use closure. // every parameter is passed via parameter object FileMatcher2 { private def filesHere = new java.io.File(".").listFiles private def fileMatching(query: String, matcher : (String, String) => Boolean) = { for (file <- filesHere ; if matcher(file.getName, query)) yield file } def filesEnding(query: String) = fileMatching(query, (fileName : String, queryString : String) => fileName.endsWith(queryString)) def filesContains(query: String) = fileMatching(query, (fileName: String, queryString : String) => fileName.contains(queryString)) def fileRegex(query: String) = fileMatching(query, (fileName: String, queryString: String) => fileName.matches(queryString)) } // however, that Scala has the same shorthands as the C# language has. // but Scala has done even a step further in that it // 1. suppport wildcard, so if you have _.endsWith(_), the first _ stands for the first argument, while the second _ stands for the second argument // 2. suppport type inference, so that in _.endswith(_), and given the context of the fileMatching method which expect the second function type to be // (String, String) => Boolean, then it can figures out the firt '_' type is String, and the second '_' 's type is String too, and also the return type is "Boolean" // // with all this in-mind, so you can take a step further, by reducing the amount of code that you can type . object FileMatcher3 { private def filesHere = new java.io.File(".").listFiles private def fileMatching(query: String, matcher : (String, String) => Boolean) = { for (file <- filesHere ; if matcher(file.getName, query)) yield file } def filesEnding(query : String) = fileMatching(query, _.endsWith(_)) def filesContains(query: String) = fileMatching(query, _.contains(_)) def fileRegex(query: String) = fileMatching(query, _.matches(_)) } // now, if you take a closer look matcher usge in the FileMatcher3.fileMatching method // if matcher(file.Name, query) // and each of the call to the FileMatching e.g. // filesEnding(query : String) = fileMatching(query, _.endsWith(_)) // while you know that // query : String -> (query, _.endWith(_)) // matcher : (String, String) => Boolean -> matcher(file.getName, query) // // ==> // // query -> matcher(({query}), file.getName) # the ({query}) means that you can pass in a bound closure variable in // object FileMatcher4 { private def filesHere = new java.io.File(".").listFiles private def fileMatching(matcher : (String) => Boolean) = { for (file <- filesHere ; if matcher(file.getName)) yield file } def filesEnding(query : String) = fileMatching((fileName : String) => fileName.endsWith(query)) def filesContains(query :String) = fileMatching((fileName : String) => fileName.contains(query)) def filesRegex(query : String) = fileMatching((fileName: String) => fileName.matches(query)) } // now, we can apply the same // type inference && wildcard trqansformation, // here is the result of the transformation object FileMatcher5 { private def filesHere = new java.io.File(".").listFiles private def fileMatching(matcher : (String) => Boolean) = { for (file <- filesHere ; if matcher(file.getName)) yield file } def filesEnding(query : String) = fileMatching(_.endsWith(query)) def filesContains(query :String) = fileMatching(_.contains(query)) def filesRegex(query : String) = fileMatching(_.matches(query)) }
Suppose we will write
1. loan pattern, where we accept code piece as the action to perfom and we take care of resource init/tear down
2. 'assert' where we accept function rathen than boolean value with a defered execution nature.
// curring.scala // // desription: // currying in scala allows you to create new control abstraction // that " I feel like native language support" // currying is a functional programming technique // let's see how curry, in additonal to other Scala language feature can help you shape your custom control abstration def plainOldSum(x : Int, y : Int) = x + y plainOldSum(3, 5) def curriedSum(x : Int )(y : Int) = x + y curriedSum(3)(5) // with the curried function, that you actually get two function invocation back to back. // rameter named x, and returns a function value for the second function def first(x: Int) = (y : Int) => x + y val second = first(1) // now you can invoke the second function, by passing a value to parameter named y second(3) // to support you to create partially applied curried function, // scala has the following syntax (place holder notation), which gives you a reference to the "second" // function // val onePlus = curriedSum(1)_ onePlus(2) val twoPlus = curriedSum(2)_ twoPlus(2) // now we have the curried function for our disposal, let's see how we can use that to write new controls. // the simplest way is to use function as parameter. def twice(op: Double => Double, x : Double) = op(op(x)) twice(_ + 1 , 5) // but we can take a step further by the scala special support of interchaning of // {} versus () // .e.g we have a function that take a function of PrintWriter to Uint calls as such // a side note, this is so called load-pattern def withPrintWriter(file : java.io.File, op : java.io.PrintWriter => Unit) = { val writer = new java.io.PrintWriter(file) try { op(writer) } finally { writer.close() } } withPrintWriter(new java.io.File("data.txt"), writer => writer.println(new java.util.Date)) // and we know because of the () versus {} thing, we can write as follow, (note: you can only do this if there is // only one parameter will be invovled) println("Hello world") println { "Hello world"} // though we said that we cannot do the () to {} things if there are more than one parameter in the parameter list // but we have currying functions. // now the new withPrintWriter def withPrintWirter2(file : java.io.File) (op : java.io.PrintWriter) = { val writer = new java.io.PrintWriter(file) try { op(writer) } finally { writer.close() } } // now let's call it val file = new java.io.File("data.txt") withPrintWirter2(file) { writer => writer.println(new java.util.Date) } // there is all good, so far, but still the content inside the // block // writer => writer.println(new java.util.Date) // // still does not feel like a control steps // // scala provided a by-name arguments // below is an by-name parameter use case var assertionsEnabled = true def myAssert(predicate : () => Boolean) = if (assertionsEnabled && !predicate()) throw new AssertionError // to call it myAssert(() => 5 > 3) // however, what you want to is as follow, which does not work myAssert(5 > 3) // to give a by-name parameter, you give the parameter a type starting with => instead of // ()=> def byNamedMyAssertion(predicate : => Boolean) = if (assertionsEnabled && !predicate) throw new AssertionError byNameMyAssertion(5 > 3) // note: a by-name parameter ,in which the empty parametet list, (), is left out, is only allowed for // parameters. there is no such a thing such as a by-name variable or a by-name field. // you may starting wondering, why do we need the by-name parameter, because we can just pass in a boolean and hope that // can work as well. def boolAssert(predicate : Boolean) = if (assertionsEnabled && !predicate) throw new AssertionError // you can do the same such as this boolAssert(5 > 3) // the difference here is the time of evaluation. // boolAssert - which takes a boolean parameter evaluate before going into the boolAssert() // while the by-name one is like a inline expansion // given our discussion here, if assertionsEnabled is false, then if you pass x /0 // to the byNamedMyAssertion(...) it won't fail, otherwise, the assertion might failed. // // so the final version of the withPrintWriter is as follow // CAVEAT: actually it is not the best example on the by-named parameter def withPrintWriter_newControl(file: java.io.File)(op : => Unit)= { val writer = new java.io.PrintWriter(file) try { op // by named parameter do not take parameter?? } finally { writer.close() } } val file2 = new java.io.File("data.txt") withPrintWriter_newControl(file2) { val writer = new java.io.PrintWriter(file2) writer.println(new java.util.Date) writer.close() }