函數柯里化(Currying)示例

 

  」函數柯里化」是指將多變量函數拆解爲單變量的多個函數的依次調用, 能夠從高元函數動態地生成批量的低元的函數。能夠當作一個強大的函數工廠,結合函數式編程,能夠疊加出很BT的能力。下面給出了兩個示例,說明如何使用 Currying 用一行代碼計算任意指數的多項式的和; 以及使用 Currying 實現一個簡單的文件處理框架。java

 

     舉例一: 計算任意指數的多項式的和 sum(n, m) = 1^m + 2^m + ... + n^m編程

     定義以下多變量的函數就能夠解決框架

  def polynomialSum2(m:Int, n:Int):Long = {
    return 1.to(n).toList.map(pow(_,m)).sum.asInstanceOf[Long];
  }

 

      爲何要使用柯里化呢? 由於柯里化不單單能夠求出最終的值,還能夠生成批量的函數,複用這些函數。好比ide

  def polynomialSum(m: Int)(list: List[Int]): Long = {
      return list.map(pow(_,m)).sum.asInstanceOf[Long];
  }

i = 2
val listPolySum = polynomialSum(i)(_)

      當咱們對第一個變量賦值後,返回獲得的是一個平方和函數 listPolySum = 1^2 + 2^2 + ... + n^2 , 能夠研究這個函數的性質而不單單是求值。函數式編程

 

  注意到, Currying 的過程當中,參數的順序是有講究的。通常, 函數參數建議放在前面,按照想要調用的順序; 數據參數放在後面;最易變的參數放在最後面。若是這個函數寫成如下形式,求值仍是同樣的, 可是生成的函數就不同了, 假設第一個變量賦值爲 list[1:10], 那麼生成的函數就變成:函數

  generateFunc = 1^m + 2^m + ... + 10^m , 沒有研究價值了。fetch

def polynomialSumNotGood(list: List[Int])(m:Int):Long = {
    return list.map(pow(_,m)).sum.asInstanceOf[Long];
  }

 

 

      舉例二: Currying 實現簡易的文件處理框架spa

  咱們不是打算成爲數學家的或計算機科學家的,那麼 Currying 對實際的軟件開發有什麼用處呢? 細細想來仍是有的。經過在 Currying 的參數中傳入函數,能夠作一些簡易的微框架。假設要作一個簡易的文件處理框架: 1.  路徑處理器 filePathHandler: 經過解析文件路徑名生成一個文件名列表; 2. 文件名過濾器 fileFilterHander: 指定條件從文件名列表中篩選出須要的文件; 3. 文件處理器 fileHandler: 處理每個文件生成一個列表; 4. totalHandler:  彙總全部列表的值並進行處理。能夠對 (2) 進行擴展,使之成爲一個過濾器處理鏈;能夠對 (3) 進行擴展, 使之成爲一個文件處理器鏈。 代碼以下:scala

/* a simple frame for processing files */
  def handleFiles(filePathHandler:(String) => List[String])
                 (fileFilterHandler: (String) => Boolean)
                 (fileHandlerList: List[(String)=>List[Any]])
                 (totalHandler: List[Any] => Any)
                 (filepath:String): Any = {
    return totalHandler(
      fileHandlerList.map(
        (handle:(String)=>List[Any]) =>
          filePathHandler(filepath).filter(fileFilterHandler(_))
            .map(handle(_)).flatten
      )
    )
  }

  

  怎麼理解這段代碼呢? 最主要的是文件處理器鏈的 map 。這裏爲了表達形式的"簡潔",犧牲了點"可讀性"。最重要的是理解 map 函數: map 的參數是一個函數,該函數以列表元素爲參數。list.map(func(_)) 函數是將函數 func 應用到一個列表的全部元素, 從而獲得另外一個列表 [func(list[0]), func(list[1]), ..., func(list[n-1])], 即: list.map(func(_)) = for (e <- list) { func(e) }; 舉個更簡單的例子: list.map((x:Int) => 2*x) , map 裏是一個 lambda 表達式, x 表示 list 的每一個元素;code

  若是 list 中的元素是函數呢 ?  首先, 將函數定義抽取出來 (String) => List[Any] , 這就是文件處理器鏈中每一個元素的類型, 針對這個元素類型寫一個函數:

  (handle:(String)=>List[Any]) => do something using handle function. 

      確實有點大膽!連我本身都驚呆了! 怎麼可讓列表中的元素存儲函數, 再去 map 呢! 哪有這麼用的! 列表中不是通常都是字符串或數值的嗎,就像 

  filePathHandler(filepath).map( (file:String) => dosomethingWith(file) ) ?

  不過,一旦突破了這一點,也就是將函數當成一個普通變量同樣"肆意玩弄", Currying + 函數式編程的威力就發揮出來了。爲簡單起見,諸位請看 handleFile 的調用, 先將第一個變量賦值爲讀文件內容的函數, 返回一個函數, 該函數接受一個用來處理文件內容的函數做爲參數以得到靈活的能力, 可使用任意的函數對文件內容進行處理,好比在文件中查找字符串,計算非空行的行數等; 這就是函數動態組合得到的能力。

 

    完整程序以下:

    CurryDemo.scala    

package scalastudy.basic

import scala.math.pow
import scalastudy.utils.PathConstants
import scalastudy.utils.DefaultFileUtil._

/**
 * Created by lovesqcc on 16-4-16.
 */
object CurryDemo extends App {

  launch()

  def launch(): Unit = {

    val listNum = 10
    val alist = (1 to listNum).toList

    for (i <- 1 to 3) {
      val listPolySum = polynomialSum(i)(_)
      val sum = listPolySum(alist)
      assert(sum == polynomialSum2(i, listNum))
      assert(sum == polynomialSumNotGood(alist)(i))
      println("sum: " + sum)
    }
    println("test passed.")

    val filename = PathConstants.scalaSrcPath + "/basic/CurryDemo.scala"
    val fileContentHandler = handleFile(readFile(_))(_)
    val findInFileFunc = fileContentHandler(findInFile)(_)
    println(findInFileFunc(filename))

    val countInFileFunc = fileContentHandler(countInFile)(_)
    println("Non Empty Lines: " + countInFileFunc(filename))

    val result = handleFiles(fetchAllFiles)((file:String) => file.endsWith("scala"))( List((s:String) => readFileLines(s)))((liststr: List[Any]) => liststr.mkString)(PathConstants.srcPath)
    println(result)
  }

  /* calc 1^m + 2^m + ... + n^m */
  def polynomialSum2(m:Int, n:Int):Long = {
    return 1.to(n).toList.map(pow(_,m)).sum.asInstanceOf[Long];
  }

  /* calc 1^m + 2^m + ... + n^m */
  def polynomialSum(m: Int)(list: List[Int]): Long = {
    return list.map(pow(_,m)).sum.asInstanceOf[Long];
  }

  /* calc 1^m + 2^m + ... + n^m */
  def polynomialSumNotGood(list: List[Int])(m:Int):Long = {
    return list.map(pow(_,m)).sum.asInstanceOf[Long];
  }

}

 

    FileAbility.scala

package traits

import java.io.File

import scala.collection.mutable.ArrayBuffer
import scala.io.Source
import scalastudy.traits.LineHandler

/**
 * Created by lovesqcc on 16-3-27.
 */
trait FileAbility extends LineHandler {

  def readFile(filename:String): String = {
    val fileSource =  Source.fromFile(filename)
    try {
      return fileSource.mkString
    } finally {
      fileSource.close()
    }
  }

  def readFileLines(filename:String):List[String] = {
    val fileSource =  Source.fromFile(filename)
    try {
      return fileSource.getLines().toList
    } finally {
      fileSource.close()
    }
  }

  /* a simple tool for processing a file */
  def handleFile(filePathHandler:(String) => String)
                (fileContentHandler: (String) => Any)
                (filepath: String): Any = {
    return fileContentHandler(filePathHandler(filepath))
  }

  /* a simple frame for processing files */
  def handleFiles(filePathHandler:(String) => List[String])
                 (fileFilterHandler: (String) => Boolean)
                 (fileHandlerList: List[(String)=>List[Any]])
                 (totalHandler: List[Any] => Any)
                 (filepath:String): Any = {
    return totalHandler(
      fileHandlerList.map(
        (handle:(String)=>List[Any]) =>
          filePathHandler(filepath).filter(fileFilterHandler(_))
            .map(handle(_)).flatten
      )
    )
  }

  def findInFile(text:String):Any = {
    val patt = "f\\w+".r
    return patt.findAllIn(text).toList
  }

  def countInFile(text:String):Any = {
    return text.split("\n").toList.filter(s => ! s.matches("^\\s*$")).length
  }

  def fetchAllFiles(path:String): List[String] = {
    val fetchedFilesBuf = ArrayBuffer[String]()
    fetchFiles(path, fetchedFilesBuf)
    return fetchedFilesBuf.toList
  }

  def fetchFiles(path:String, fetchedFiles:ArrayBuffer[String]):Unit = {
    val dirAndfiles = new File(path).listFiles
    if (dirAndfiles!=null && dirAndfiles.length > 0) {
      val files = dirAndfiles.filter(_.isFile)
      if (files.length > 0) {
        fetchedFiles ++= files.map(_.getCanonicalPath)
      }

      val dirs = dirAndfiles.filter(_.isDirectory)
      if (dirs.length > 0) {
        dirs.map(_.getCanonicalPath).foreach { dirpath =>
          fetchFiles(dirpath, fetchedFiles) }
      }
    }
  }


  def handleFile(filename:String):List[Any] = {
    return readFileLines(filename).map(handle(_)).toList
  }

  def handleFileWithNoReturn(filename:String, lineHandler: LineHandler):Unit = {
    readFileLines(filename).foreach { line =>
      lineHandler.handle(line)
    }
  }

}

 

   DefaultFileUtil.scala  

package scalastudy.utils

import traits.FileAbility

import scalastudy.traits.LinePrintHandler

/**
 * Created by lovesqcc on 16-4-16.
 */
object DefaultFileUtil extends FileAbility with LinePrintHandler {

}

 

    LineHandler.scala   

package scalastudy.traits

/**
 * Created by lovesqcc on 16-3-27.
 */
trait LineHandler {
  def handle(line: String):Any = {}
}

 

    LinePrintHandler.scala  

package scalastudy.traits

/**
 * Created by lovesqcc on 16-4-2.
 */
trait LinePrintHandler extends LineHandler {
  override def handle(line: String): Any = {
    println(line)
  }
}

 

   PathConstants.scala

package scalastudy.utils

/**
 * Created by lovesqcc on 16-4-16.
 */
object PathConstants {

  val projPath = System.getProperty("user.dir")
  var srcPath = projPath + "/src/main/java"
  val scalaSrcPath = projPath + "/src/main/java/scalastudy"

}
相關文章
相關標籤/搜索