scala - control abstraction

In this post, we are going to examine the control abstraction, and this topic will focus on the following points:

  1. simplifying client code
  2. evolution of scala code - writting less, do more (with lambda, wildcard, and type inference)
  3. function currying and by-name parameter to crate flexible DSL like language

Simplifying client code

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))

lambda, type inference and wildcard

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))
}

Currying and by-name to create custom control and DSL like language

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()
}

你可能感兴趣的:(scala)