您的位置:首页 > 编程语言

scala函数、运行机制(代码+注释)、柯里化的四种写法

2017-04-23 17:46 323 查看
/* 阅读建议 —— 读本文之前,
* 假设你使用过java,假设你使用过python
* 假设你了解不同的编程语言有不同的特性和使用场景
*/
首先,不要被SCALA的奇葩语法吓到了!scala也是一种函数式编程,它把java语言脚本化了,给人的感觉就是“所见即所得”,这一特性类似于Linux下的bash脚本,还有python语言也是脚本语言。
scala结合了java和python的优点,能静能动,很灵活。scala的语法很简洁,功能也很强大,但是代码很精简。实现同样的功能,代码量比java语言要少很多。scala代码的可读性也不错,虽然scala语法给人的感觉诡异、奇葩,但是熟悉之后就不奇葩了呀,就像你刚开始学C语言或者Java的时候也觉得语法诡异,但是熟悉之后就不觉得诡异了呀,对不对?
个人来说,我更愿意使用scala来写spark的应用。

————————————————————
【定义函数,创建函数】
(1)定义函数时带小括号,但是小括号里无参,那么调用此函数时带不带括号都OK, 
scala解释器示例:
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

// 函数名带小括号, 无参。冒号: 声明了返回值类型为字符串
scala> def func1(): String = {
     |  var s = "hello scala~"
     |  s
     | }
func1: ()String        // 等号=意味着函数有返回值,若无等号则返回空(没有返回值)

scala> func1
res11: String = hello scala~

scala> print(func1)
hello scala~
scala> print(func1())
hello scala~

(2)定义函数时不带小括号,那么调用此函数时就不可以带小括号!
在scala解释器的示例如下:
scala> def func2: String = {
     |  var s2 = "hello again"
     |  s2
     | }
func2: String

scala> print(func2)
hello again
scala> print(func2())
<console>:13: error: not enough arguments for method apply: (index: Int)Char in class StringOps.
Unspecified value parameter index.
       print(func2())     //定义函数时无(),调用时用()则报错!
                  ^

(3)给函数传参
(3.1)按照定义函数时的参数顺序逐个传入。  IntelliJ IDEA的完整示例如下
object
TestClosPack {

   
def main(args: Array[String]){

        val str = test_args(2,1)      //调用函数,按定义时的参数顺序传参

        for(s <- str){print(s)}

    }

    def test_args(a:Int, b:Int)={"|scala|"* (a+b)}

}


输出结果:
|scala||scala||scala|

(3.2)按照参数名称,但不按照参数顺序传入. IntelliJ IDEA的示例如下
object
TestClosPack {

   
def main(args: Array[String]){

        val str = test_args(b=1,a=2)    //调用函数,指定参数名称, 但不按照定义时的顺序传参

        for(s <- str){print(s)}

    }


    def test_args(a:Int, b:Int)={"|scala|"* (a+b)}

}


输出结果:
|scala||scala||scala|

(4)匿名函数
语法:使用=>符号。箭头的左边是参数列表,右边是函数体,参数的类型可省略,Scala的类型推测系统会推测出参数的类型。使用匿名函数能让我们的代码变得简洁。
示例一:指定参数类型
// (a: Int, b:Int)  => a * b返回一个匿名函数,把此函数赋给变量multiply
scala> var multiply =  (a: Int, b:Int)  => a * b
multiply: (Int, Int) => Int = <function2>

scala> multiply(2,4)//调用匿名函数时,要加小括号,在小括号里面传参
res16: Int = 8

示例二:省略参数
scala> var currentUser = () => System.getProperty("user.name")    //获取此系统的当前用户
currentUser: () => String = <function0>

scala> currentUser()
res21: String = tonykidkid

scala> var currentMillsTime = () => System.currentTimeMillis    //获取当前系统(毫秒)时间戳
currentMillsTime: () => Long = <function0>

scala> currentMillsTime()//调用匿名函数时,要加小括号
res22: Long = 1492871662593

————————————————————
【函数的返回值问题】
1,声明函数的返回值类型,则在函数返回时一定要符合返回类型
2,可以不声明函数返回值类型,scala解释器会自动推断返回的数据类型
3,递归函数必须显式声明返回的类型
4,定义函数时没有等号= 则没有返回值。这类函数相当于一个执行过程
1,示例:
scala> def func3(a:Int) :Int = {
     |  a+10             // 两个整数相加,结果还是整数
     | }
func3: (a: Int)Int    // 意思是:形式参数的数据类型是Int,返回类型是Int

scala> func3(5)
res16: Int = 15

scala> print(func3(5))
15
scala> def func4(a:Int) :Int = {        //以“: Int”方式声明了返回值类型为整数
     |  a + "10"
     | }
<console>:12: error: type mismatch;
 found   : String
 required: Int
        a + "10"    //错误:整数和字符串相加得到字符串,但是函数要求返回值类型是整数
          ^

2,定义函数时不声明返回类型 示例:
scala> def func5(a:Int) = {//未声明返回的类型,则自动推断,若无法推断则会报错
     |  a + "10"
     | }
func5: (a: Int)String    // 意思是:形式参数的数据类型是Int,返回类型是String

scala> func5 (3)
res18: String = 310

scala> :type func5(3)    //查看数据类型使用:type命令
String

3,递归函数必须声明返回值类型,以斐波那契数列为例:
/**斐波那契数列 ——第一个数是0,第二个数是1,

* 从第三个数开始,每个数都是它前两个的和: 0,1,1,2,3,5,8,13,21...
*/
scala> def fiboNums(num: Int): Int = {    //递归函数必须声明返回值类型
     |             if (num == 1) {return 0}
     |             if (num == 2) { 1}
     |             else {
     |                 var res = fiboNums(num - 2) + fiboNums(num - 1)
     |                 res
     |             }
     |         }
fiboNums: (num: Int)Int

scala> fiboNums(5)
res0: Int = 3

//非递归函数可以不声明返回类型
scala> def sum_fibo_nums(num: Int) = {
     |             import scala.collection.mutable.ArrayBuffer
     |             var list_fibo = ArrayBuffer[Int]()
     |             list_fibo.clear()
     |             for (n <- 1 to num) {
     |                 var res = fiboNums(n)
     |                 printf("第%d个斐波那契数: %d\n", n, res)
     |                 list_fibo += res        //把每个数都追加到数组中
     |             }
     |             printf("前%d个斐波那契数的和为:%d",num,list_fibo.sum)
     |         }
sum_fibo_nums: (num: Int)Unit

scala> fiboNums(5)
res0: Int = 3

scala> sum_fibo_nums(5)
第1个斐波那契数: 0
第2个斐波那契数: 1
第3个斐波那契数: 1
第4个斐波那契数: 2
第5个斐波那契数: 3
前5个斐波那契数的和为:7

4,示例
scala> def no_equals_symbol(a:Int, b:Int) {
     |  var c = a + b
     |  c
     | }
no_equals_symbol: (a: Int, b: Int)Unit      //定义函数时无等号=,则无返回值

scala> print(no_equals_symbol(1,2))
()

————————————————————
【scala如何解析函数的参数?两种方式】
1,传值调用(call-by-value):进入函数内部前,就已将参数表达式的值计算完毕
传值调用最好理解,就是在定义函数时声明参数及其数据类型,这意味着调用此函数时必须传一个确定类型的值作为参数
语法:使用 : 符号来设置传值调用,请看示例:
scala> def passvalue(arg:Long) = {    //传给函数参数是指定类型的值
     |   println("In function 'passvalue'")
     |   println("Value of parameter 'arg' is: " + arg)
     | }
passvalue: (arg: Long)Unit

scala> def get_milis_time() = {
     |   println("get milisecond in function 'get_milis_time()'")
     |   System.currentTimeMillis
     | }
get_milis_time: ()Long

//从输出内容的顺序可知,在进入passvalue函数内部之前,此函数的参数就已经有值了
scala> passvalue( get_milis_time() )
get milisecond in function 'get_milis_time()'
In function 'passvalue'
Value of parameter 'arg' is: 1492838244412

2,传名调用(call-by-name):只有在函数内部才计算参数表达式的值
语法:在变量名和变量的数据类型之间使用=>符号来设置传名调用。在scala解释器示例如下
scala> def passname( para : => Long) = {    //传给函数的参数是一个函数
     |   println("In function 'passname'")
     |   println("the value of parameter 'para' is: " + para)
     | }
passname: (para: => Long)Unit

scala> def get_milis_time() = {
     |   println("get milisecond in function 'get_milis_time()'")
     |   System.currentTimeMillis
     | }
get_milis_time: ()Long

//表达式的值只有在passname函数内部才计算
scala> passname( get_milis_time() )
In function 'passname'
get milisecond in function 'get_milis_time()'
the value of parameter 'para' is: 1492835629778

3,传值调用和传名调用混合使用
scala> def duplicate(a:String,b:Int, func1:(String,Int)=>String, func2:(String,Int)=>String)= { 
     |  var s = ""
     |  if (b>0) {
     |   s = func1(a,b)
     |  }
     |  if (b<=0) {
     |   s = func2(a,b)
     |  }
     |  s
     | }
duplicate: (a: String, b: Int, func1: (String, Int) => String, func2: (String, Int) => String)String

scala> var f1 = (s:String, i:Int) => s * i
f1: (String, Int) => String = <function2>

scala> var f2 = (s:String, i:Int) => "i<0 Cannot duplicate"
f2: (String, Int) => String = <function2>

scala> duplicate("hi~", 3, f1, f2)
res9: String = hi~hi~hi~

scala> duplicate("hi~", -3, f1, f2)
res10: String = i<0 Cannot duplicate

这中混合使用,其实是一个高阶函数的用法,高阶函数特点是,传给函数的参数类型是函数

————————————————————
【闭包】
闭包是个函数,其返回值由定义在函数外部的一个或多个变量决定
scala> val factor = 3
factor: Int = 3

// 匿名函数里面有个形参I,还有个定义在函数外部的变量factor
scala> val multiply = (I:Int) => I * factor
multiply: Int => Int = <function1>

scala> multiply(2)
res0: Int = 6

在intelliJ IDEA中的完整示例:
object
TestClosPack {    // scala的这种object用法相当于java中的单实例模式
    def main(args: Array[String]){

       
var factor =
3.14

        val multi = (i: Int) => i * factor

        println("multi(3) value is:" + multi(3))

        println("multi(10) value is: " + multi(10))

    }

}

输出:
multi(3) value is:9.42
multi(10) value is: 31.400000000000002

————————————————————
【函数的变长参数】
变长的参数意味着传入函数的参数列表,其长度可变,即可以有1个或多个参数
语法:参数类型后面加一个符号*
示例一:
scala> def get_sum(a:Int*) = {
     |  var s = 0
     |  for (x <- a) s += x;
     |  s
     | }
get_sum: (a: Int*)Int

scala> get_sum(1,3,5,7)    //可以往里传多个值
res17: Int = 16

示例二:
scala> def get_string(str:String*) = {
     |  var i = 0
     |  for (s <- str) {
     |   println("string value " + i + ": "+ s )
     |   i += 1
     |  }
     | }
get_string: (str: String*)Unit

scala> get_string("My","name", "is", "Tonykidkid")
string value 0: My
string value 1: name
string value 2: is
string value 3: Tonykidkid

————————————————————
【柯里化的四种写法】
科里化就是把普通的接收2个参数的函数,转化成接收1个参数的函数。调用科里化函数的时候,需要分两步:先传一个参数进去,再紧接着传第二个参数;就是分两次传参每次传1个参数。请看以下代码示例的注释

scala> def replicate(x:Int, y:String) = {  //尚未科里化,这是普通的函数写法
     |  y * x
     | }
replicate: (x: Int, y: String)String

scala> replicate(3, "scalA")    // 调用普通函数,一次性接收2个参数
res28: String = scalAscalAscalA

//科里化函数的第一种写法
scala> def currying_1(x:Int) = {   //不显式声明返回类型,scala会自动推断出返回类型
     |    y:String => y * x
     | }
currying_1: (x: Int)String => String      // =>符号意味着返回的是一个函数

// 调用时传入第一个参数3,返回一个新的函数;继续向新函数传入第二个参数”scalA”,返回最终结果
scala> currying_1(3)(“scalA")
res29: String = scalAscalAscalA

// 第二种写法:显式声明返回类型——函数
scala> def curry_2(x:Int) : String => String = {   // explicitly declare returned type
     |   y => y *x
     | }
curry_2: (x: Int)String => String       // =>符号左边的String对应的是y,=>符号右边的String就对应着y*x

scala> :type curry_2(3)//验证返回类型,确实是个函数
String => String

// 调用curry_2(3)会返回一个新的函数, 该函数以String为参数类型,再调用此函数时要传参
scala> curry_2(3)("scalA ")
res30: String = "scalA scalA scalA "

// 第三种写法
scala> def curry_3rd(x:Int)(y:String) = {
     |  y * x
     | }
curry_3rd: (x: Int)(y: String)String

scala> curry_3rd(3)("good!")    //效果等同于上面两种写法
res31: String = good!good!good!

// 第四种写法
scala> def curry_4th(x:Int)(y:String) : String = {
     |  y * x
     | }
curry_4th: (x: Int)(y: String)String

scala> curry_4th(3)("good!")
res35: String = good!good!good!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  scala 函数式编程
相关文章推荐