日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Scala 中的函数式编程基础

發布時間:2025/3/21 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Scala 中的函数式编程基础 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

主要來自 Scala 語言發明人 Martin Odersky 教授的 Coursera 課程?《Functional Programming Principles in Scala》


------------------------------部分一---------------------------------------

很久以前寫過一個非常簡單的?python lambda 函數博客,里頭有 filter,map,reduce等,是python中很有意思的部分,可以先看看。

另外酷殼網陳皓寫了一篇介紹函數式編程的博客,言簡意賅,對理解函數式編程很有幫助,非常值得一看。


Scala 本意是可伸展。它的設計哲學是:允許用戶通過定義感覺像原生語言支持一樣的易用庫去在他們需要的方向上改進和發展語言——Scala allows users to grow and adapt the language in the directions they need by defining easy-to-use libraries that feel like native language support.。Scala 運行在 Java 平臺上,可以與所有的 Java 庫無縫交互。

1. Function evaluations

Scala 是純粹的面向對象式的編程,所有的 value 都是一個對象。
Scala 是函數式的編程,它把每一個函數看做一個 value,函數即對象。

1.1 函數的副作用

純函數(Pure Function)是這樣一種函數——輸入輸出數據流全是顯式(Explicit)的。

顯式(Explicit)的意思是,函數與外界交換數據只有一個唯一渠道——參數和返回值;函數從函數外部接受的所有輸入信息都通過參數傳遞到該函數內部;函數輸出到函數外部的所有信息都通過返回值傳遞到該函數外部。

隱式(Implicit)的意思是,函數通過參數和返回值以外的渠道,和外界進行數據交換。比如,讀取全局變量,修改全局變量,都叫作以隱式的方式和外界進行數據交換;比如,利用I/O API(輸入輸出系統函數庫)讀取配置文件,或者輸出到文件,打印到屏幕,都叫做隱式的方式和外界進行數據交換。

Pure Function的好處主要有幾點:

  • 無狀態,Stateless。線程安全。不需要線程互斥或同步。
  • Pure Function相互調用組裝起來的函數,還是Pure Function。
  • 應用程序或者運行環境(Runtime)可以對Pure Function的運算結果進行緩存,運算加快速度。
  • 以上關于 pure function 的描述引用這篇博客。
    另外一篇博客也介紹函數的副作用,推薦看看!

    在函數式編程語言里面,函數是一等公民,這意味著:

    • 像其他 value,可以在任何地方定義函數,包括函數內部
    • 像其他 value,函數可以作為其他函數的輸入參數或者返回值
    • 像其他 value,函數也有自己的操作符進行組合等操作

    1.2 參數調用的區別

    scala 函數定義格式如下:

    • call by value
      遇到表達式就計算,scala 函數參數默認進入函數內部之前計算出 value,再傳進內部,關鍵字?val?也是立即計算模式
    • call by name
      惰性求值,這是函數式編程里面一個非常重要的概念。要用到的時候才計算,可以用def fun(x:Int, y: => Int) = ...?中的?=>顯式調用。關鍵字?def?也是這種模式

    舉個例子:

    def loop: Int = loop // 定義一個死循環,def 可以,val 不行 def constOne(x: Int, y: => Int) = 1 // 第一個參數x: call by value, 第二個參數y: call by name constOne(1+2, loop) // loop一直沒用,所以沒有計算,輸出1 constOne(loop, 1+2) // 遇到loop立即計算,陷入死循環

    1.3 牛頓法求平方根

    • Scala 中遞歸函數需要寫返回值類型,非遞歸不一定要寫。
    • 函數定義可以放在函數內部,防止命名污染。

    牛頓法的基本思路是遞歸,首先猜測為 1.0,如果誤差過大,則猜測值改為 guess 與 x / guess 的平均值。

    object session {def abs(x: Double) = if (x>=0) x else (-x) def sqrt(x: Double) = {def sqrtIter(guess: Double): Double =if (isGoodEnough(guess)) guesselse sqrtIter(improve(guess))def isGoodEnough(guess: Double) =abs(guess * guess - x) / x < 0.001def improve(guess: Double) =(guess + x / guess) / 2sqrtIter(1.0)} sqrt(2.0) }

    1.4 尾遞歸

    這篇博客推薦看看——遞歸與尾遞歸總結。

    遞歸相對常用的算法如普通循環等,運行效率較低。在遞歸調用的過程當中系統為每一層的返回點、局部量等開辟了棧來存儲,因此遞歸次數過多容易造成棧溢出。

    尾遞歸對應 imperative program(如c++)中的循環。?函數調用出現在調用者函數的尾部, 因為是尾部, 所以根本沒有必要去保存任何局部變量(需要保存的變量可以放在函數參數列表里)。它的棧是常數,可復用,與循環一樣高效。

    遞歸:

    // 階乘函數 def factorial(n: Int): Int =if (n == 0) 1 else n * factotial(n - 1)

    尾遞歸:

    // 階乘函數 def factorial(n: Int) = {def loop(acc: Int, n: Int): Int=if (n == 0) accelse loop(acc * n, n - 1)loop(1, n)} ------------------------------部分二---------------------------------------

    2. Higher Order Functions

    把其他函數作為參數或者作為返回值,就是?higher order functions,python 里面也可以看到這樣使用的情形。在酷殼上的博客有一個例子就是將函數作為返回值。

    2.1 匿名函數

    在 python 里邊叫 lambda 函數,常常與 map(), filter(), reduce() 聯合使用,前面也寫過一篇這樣的博客。

    舉一個 scala 的 reduce 的例子,f: Int => Int?表示 f 是一個整數映射到整數的函數,計算下面公式:

    ∑bn=af(n)def sum(f: Int => Int, a: Int, b: Int): Int = {def loop(a: Int, acc: Int): Int =if (a > b) accelse loop(a + 1, f(a) + acc)loop(a, 0) } def sumInts(a: Int, b: Int) = sum(x => x, a, b) // f(n)=n def sumCubes(a: Int, b: Int) = sum(x => x * x * x, a, b) // f(n)=n*n*n println(sumInts(2, 7)) //求和 println(sumCubes(3, 10)) //求立方和

    2.2 currying

    把一個函數的多個參數分解成多個函數, 然后把函數多層封裝起來,每層函數都返回一個函數去接收下一個參數這樣,可以簡化函數的多個參數。

    // sum 返回函數 sumF,風格與 python 相似def sum(f: Int => Int): (Int, Int) => Int = {def sumF(a: Int, b:Int): Int =if (a > b) 0 else f(a) + sumF(a + 1, b)sumF} def sumInts = sum(x => x) def sumCubes = sum(x => x * x * x) sumInts(1, 5) //> res0: Int = 15sumCubes(1, 5) //> res1: Int = 225sum(x=>x)(1, 5) //> res2: Int = 15(sum(x=>x))(1, 5) //> res3: Int = 15

    更為簡短的寫法:

    def sum(f: Int => Int)(a: Int, b: Int): Int =if (a > b) 0 else f(a) + sum(f)(a + 1, b)sum(x => x)(1, 5) // 第一個()相當于創建了一個匿名函數

    mapReduce 實現過程包括 map 一一映射函數和 reduce 函數及單位元素 zero(乘為1,加為0),參數包括序列區間 [a, b] 兩個參數,假設我們求 [a, b] 區間上所有元素的平方和:

    def mapReduce(map: Int => Int, reduce: (Int, Int) => Int, zero: Int)(a: Int, b: Int): Int = if (a > b) zeroelse reduce(map(a), mapReduce(map, reduce, zero)(a + 1, b)) def sumOfSquare(a: Int, b: Int) = mapReduce(x => x*x, (x, y) => x + y, 0)(a, b) //這里確定了三個,留下參數a,b

    比如求立方和,四次方和等,更靈活的用法是 map 和 reduce 可以先指定一個reduce(都是sum),使用時再指定另一個(map),代碼就不貼了。總之,所有mapreduce設置,包括map,reduce, zero, a, b都可以無序設置,替換組合成包含不同參數列表的新函數。

    2.3 類

    構造一個分數(rational)類,實現加減、比大小等基本功能。

    object rationals {val x = new Rational(1, 3) //> x : week3.Rational = 1/3val y = new Rational(5, 7) //> y : week3.Rational = 5/7val z = new Rational(3) //> z : week3.Rational = 3/1x.numer //> res0: Int = 1x.sub(y).sub(z) //> res1: week3.Rational = 71/-21y.add(y) //> res2: week3.Rational = 10/7x.less(y) //> res3: Boolean = truex.max(y) //> res4: week3.Rational = 5/7 }class Rational(x: Int, y: Int) {require(y != 0, "denomitor must be nonzero")// scala 的構造函數就是執行bodydef this(x: Int) = this(x, 1) // 第二種構造函數, 補全到第一種private def gcd(a: Int, b: Int): Int =if (b==0) a else gcd(b, a % b) //private函數,求最大公約數val numer = x / gcd(x, y) // 每次構造新類,都化簡val denom = y / gcd(x, y) // val,gcd函數只計算一次def add(that: Rational) =new Rational(numer * that.denom + denom * that.numer, // 交叉相乘相加denom * that.denom)def neg: Rational = new Rational(-numer, denom)def sub(that: Rational) = add(that.neg)def less(that: Rational) = numer * that.denom < that.numer * denomdef max(that: Rational) = if (this.less(that)) that else this // this 關鍵字,表示使用該method的objectoverride def toString = numer + "/" + denom // 每次打印類的格式 }

    2.4 操作符

    c++里面有操作符的重載,在scala里面技術層面上來說沒有操作符這個概念。比如?1 + 2?實際是?1.+(2)。 + 是對象 1 的一種方法。Scala 實現?1 + 2?這種寫法需要兩種技術,以上面的例子來分析:

  • 上面的例子中?r.add(s)?可以寫成?r add s,任何只包含一個參數的方法都可以寫成這樣的形式,這種做法叫Infix Notation
  • 但問題是,現在整數的加法是?a + b,分數的加法如果是?a add b,風格不一致。還有一個方法叫Relaxed Identifiers。大概意思是標志符不僅可以是字母開頭的字符串組成,還可以是運算符(如果后面是冒號,加至少一個空格,否則會將冒號也看出標志的一部分)。
  • 實現與整數加法風格一致的分數運算,代碼如下:

    package week3object rationals {val x = new Rational(1, 3) //> x : week3.Rational = 1/3val y = new Rational(5, 7) //> y : week3.Rational = 5/7val z = new Rational(3) //> z : week3.Rational = 3/1-x //> res0: week3.Rational = 1/-3x - y - z //> res1: week3.Rational = 71/-21y + y //> res2: week3.Rational = 10/7x < y //> res3: Boolean = truex * x + z * z //> res4: week3.Rational = 82/9 }class Rational(x: Int, y: Int) {require(y != 0, "denomitor must be nonzero")def this(x: Int) = this(x, 1)private def gcd(a: Int, b: Int): Int =if (b==0) a else gcd(b, a % b)val numer = x / gcd(x, y)val denom = y / gcd(x, y)def + (that: Rational) =new Rational(numer * that.denom + denom * that.numer,denom * that.denom)def unary_- : Rational = new Rational(-numer, denom) // unary_:一元運算符和二元運算符不同,一元要特地指出def - (that: Rational) = this + -thatdef < (that: Rational) = numer * that.denom < that.numer * denomdef * (that: Rational) = new Rational(numer * that.numer, denom * that.denom)override def toString = numer + "/" + denom // 打印類的格式 }

    注意到上面代碼中?x*x + z*z?沒用括號也能計算出準確的結果,這是因為 scala 通用一套根據標識符確定運算優先級的規則表。


    ------------------------------部分三---------------------------------------

    3. Data and Abstraction

    3.1 Class Hierarchies

    這一集字幕不同步-,-,聽得有點費力!

    類的概念和其他語言里面很相似,基類,子類,父類啥的叫法差不多。在 Scala 中,所有用戶自定義的類都是另外一個類的子類,如果沒有顯式給定父類,java 里面默認繼承 java.lang,scala 里面是 Object。無論基類中的方法有沒有具體實現,子類都可以用?override?重新定義,回想起之前強大的?toString?了嗎?

    舉一個二叉樹的例子:

    package week3object insets {val t1 = new NonEmpty(3, Empty, Empty) //> t1 : week3.NonEmpty = {.3.}val t2 = t1 incl 4 //> t2 : week3.IntSet = {.3{.4.}}val t3 = new NonEmpty(5, Empty, Empty) //> t3 : week3.NonEmpty = {.5.}t2 union t3 //> res0: week3.IntSet = {{{.3.}4.}5.} }abstract class IntSet { // 抽象類作為基類,無法實例化,定義了三個接口def contains(x: Int): Boolean // 查找是否包含 xdef incl(x: Int): IntSet // 如果 x 不存在,將 x 放入二叉樹中def union(x: IntSet): IntSet // 兩棵樹融合}object Empty extends IntSet { // Empty 是 IntSet 的 subclass,`object` 表示單例模式,所有空節點都可以用一個對象來表示def contains(x: Int): Boolean = falsedef incl(x: Int): IntSet = new NonEmpty(x, Empty, Empty)def union(other: IntSet): IntSet = otheroverride def toString = "." // 空節點打印"." }class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet {def contains(x: Int): Boolean =if (x < elem) left contains xelse if (x > elem) right contains xelse truedef incl(x: Int): IntSet =// 實際上創建了一個新樹,新樹和舊樹共用未改變的子樹// 這個叫 persistent data structure,是把函數式編程擴展到 collections 的關鍵之一// 反正他是這么說的 `-,-`if (x < elem) new NonEmpty(elem, left incl x, right) // 一重一重地復制節點else if (x > elem) new NonEmpty(elem, left, right incl x)else thisdef union(other: IntSet): IntSet =((left union right) union other) incl elem // override def toString = "{" + left + elem + right + "}" //強大的遞歸啊 }

    3.2 How Classes Are Organized

    沒學過 java,估計和 java 中 package 管理一樣。

    在源碼最頂端寫上?package week3?表示這個文件的 object 或者 class 屬于這個包。要使用某一個類,可以在源碼中用全名?week3.classA,也可以像 python 一樣在最開始 import,源碼中間用類名:

    • import week3.classA:導入類 classA
    • import week3.{classA, classB}:導入兩個類
    • import week3._ :導入包所有(通配符導入方法)

    除了從包導入,還可以從 object 導入。所有 Scala 程序默認導入一些 entities,比如 scala 中的 Int,java.lang 中的 Object,scala.Predef 中的斷言等。更多信息可以查看 scala 的標準庫。

    在 java 和 scala 中,一個類只能有一個父類(單繼承),如何實現多繼承,scala 中采用 traits。trait 像 java 里面的接口,偏抽象,但是更強大,可以包含 field 和具體方法,但是不能有value參數。子類可只能繼承一個父類,但是可以繼承任意多個 traits,例如:class Square extends Shape with Planar with Moveble。

    scala 類型結構如下,實線表示繼承,虛線表示隱式轉化。

    • Any是所有類型的基本類,包含的方法有:‘==’,‘!=’,‘equals’,‘hashCode’,‘toString’
    • AnyVal是數值類型的基本類。
    • AnyRef是所有引用類型的基本類,也是 java.lang.Object 的別名。
    • Nothing是所有類的子類型。主要作用是異常類與collection中的一個空元素。
    • Null?是所有類的子類型。但是與 AnyVal 的子類型不兼容。

    Q:if (true) 1 else False?的返回類型是什么?
    A:int 和 boolean 類型,返回父類 AnyVal。

    3.3 Polymorphism

    Polymorphism 意味著函數可以以多種類型出現。一張 PPT 總結 Polymorphism:

    假設我們要建立一個 list 類,它可能包含了不同的數據類型(整數,布爾,list自身類型等),例子如下:

    這時需要用泛型來表示。新建一個package叫week4,在其中新建一個 trait。它的兩個‘子類’分別為 Cons 和 Nil,分別表示含有元素的節點和空節點。

    package week4// [T] 是類型參數,比如int,double之類。是泛型編程的基礎 trait List[T] {def isEmpty: Booleandef head: Tdef tail: List[T] }class Cons[T](val head: T, val tail: List[T]) extends List[T] {def isEmpty = false// head 和 tail 已經在初始化中實現 }class Nil[T] extends List[T] {def isEmpty = truedef head: Nothing = throw new NoSuchElementException("Nil.head")// Nothing 是任何類型的子類,所以也是 T 的子類def tail: Nothing = throw new NoSuchElementException("Nil.tail") }

    在 week4 中新建一個 scala worksheet,測試一下上述代碼:

    package week4import week4._object nth {// 創建一個含有一個元素的 listdef singleton[T](elem: T) = new Cons(elem, new Nil)//> singleton: [T](elem: T)week4.Cons[T]singleton[Int](3) //> res0: week4.Cons[Int] = week4.Cons@71be98f5singleton(3) // 編譯器可以從 3 推到出 T 是 Int // 尋找 list 中的第 n 個元素def nth[T](n: Int, xs: List[T]): T =if (xs.isEmpty) throw new IndexOutOfBoundsExceptionelse if (n == 0) xs.headelse nth(n - 1, xs.tail) //> nth: [T](n: Int, xs: week4.List[T])T// 創建一個 list = [1, 2, 3]val list = new Cons(1, new Cons(2, new Cons(3, new Nil)))nth(2, list) //> res2: Int = 3 }

    小記

    這里是課程前四次的大概內容,因為第一次課是教你怎么安裝,所以實際內容只有三次課,后面還有四次課。總體來說,函數式編程給人很多啟發,但是如果不是真正需要用,也不宜占用太多時間去學習。暑假要去實習了,等下學期再學吧。


    from:http://www.cnblogs.com/daniel-D/?

    總結

    以上是生活随笔為你收集整理的Scala 中的函数式编程基础的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。