scala函数式编程之Function与Method

本文详细探讨了Scala中方法与函数的区别与联系,包括它们在语法上的不同、如何在特定场景下相互转换以及如何利用这些特性编写简洁高效的代码。

在scala中方法(method)与函数(function) ,method是面向对象的概念而function是函数式的概念.
在scala中“一切都是对象”我们可以将scala中的函数称为函数对象;在scala中def 是个method,当我们需要的时候,可以用方法来表达函数,然后将方法转换成函数:


scala> val a=2

a: Int = 2

scala> a //传递

res1: Int = 2

scala> val aa=(x:Int)=>x*2

aa: Int => Int = <function1>

scala> aa 

res2: Int => Int = <function1>


scala> def aa=(x:Int)=>x*2
aa: Int => Int

scala> aa  //引用

res3: Int => Int = <function1>    

scala> aa() //调用

<console>:9: error: not enough arguments for method apply: (v1: Int)Int in trait Function1.

Unspecified value parameter v1.

              aa()

                ^

scala> aa

res6: Int => Int = <function1>


scala中方法与函数有太多的交集;scala是基于面向对象的设计,为了表达函数的概念,所以以面向对象的形式来表达函数的概念。

1.function与method明显的区别点:

1)方法可以作为一个表达式的一部分出现(调用函数并传参),但是方法(带参方法)不能作为最终的表达式,但是函数可以作为最终的表达式出现:

scala> //定义一个方法scala> def m(x:Int) = 2*x
m: (x: Int)Int

scala> //定义一个函数scala> val f = (x:Int) => 2*x
f: Int => Int = <function1>scala> //方法不能作为最终表达式出现scala> m<console>:9: error: missing arguments for method m;
follow this method with `_' if you want to treat it as a partially applied function              m              ^scala> //函数可以作为最终表达式出现scala> f
res9: Int => Int = <function1>

无参方法可以作为最终表达式出现,其实这属于方法调用,scala规定无参函数的调用可以省略括号

(关于方法调用我们下面会涉及到)

scala> def m1()=1+2m1: ()Int

scala> m1
res10: Int = 3

 

2) 参数列表对于方法是可选的,但是对于函数是强制的

方法的可以没有参数列表,参数列表也可以为空。但是函数必须有参数列表(也可以为空),见下面例子

scala> //方法可以没有参数列表scala> def m2 = 100;
m2: Int

scala> //方法可以有一个空的参数列表
scala> def m3() = 100m3: ()Int

scala> //函数必须有参数列表,否则报错
scala> var f1 =  => 100
<console>:1: error: illegal start of simple expression
       var f1 =  => 100
                 ^scala> //函数也可以有一个空的参数列表scala> var f2 = () => 100f2: () => Int = <function0>

那么方法为什么可以没有参数列表呢,往下看。

 

3)方法名意味着方法调用,函数名只是代表函数自身

因为方法不能作为最终的表达式存在,所以如果你写了一个方法的名字并且该方法不带参数(没有参数列表或者无参)

该表达式的意思是:调用该方法得到最终的表达式。因为函数可以作为最终表达式出现,如果你写下函数的名字,函数

调用并不会发生,该方法自身将作为最终的表达式进行返回,如果要强制调用一个函数,你必须在函数名后面写()

scala> //该方法没有参数列表scala> m2
res11: Int = 100scala> //该方法有一个空的参数列表scala> m3
res12: Int = 100scala> //得到函数自身,不会发生函数调用scala> f2
res13: () => Int = <function0>scala> //调用函数scala> f2()
res14: Int = 100

 

为什么在函数出现的地方我们可以提供一个方法

在scala中很多高级函数,如map(),filter()等,都是要求提供一个函数作为参数。但是为什么我们可以提供一个方法呢

?就像下面这样:

scala> val myList = List(3,56,1,4,72)
myList: List[Int] = List(3, 56, 1, 4, 72)

scala> // map()参数是一个函数
scala> myList.map((x) => 2*x)
res15: List[Int] = List(6, 112, 2, 8, 144)

scala> //尝试给map()函提供一个方法作为参数
scala> def m4(x:Int) = 3*x
m4: (x: Int)Int

scala> //正常执行
scala> myList.map(m4)
res17: List[Int] = List(9, 168, 3, 12, 216)

这是因为,如果期望出现函数的地方我们提供了一个方法的话,该方法就会自动被转换成函数。该行为被称为ETA expansion。

这样的话使用函数将会变得简单很多。你可以按照下面的代码验证该行为:

scala> //期望出现函数的地方,我们可以使用方法scala>  val f3:(Int)=>Int = m4
f3: Int => Int = <function1>scala> //不期望出现函数的地方,方法并不会自动转换成函数scala> val v3 = m4<console>:8: error: missing arguments for method m4;
follow this method with `_' if you want to treat it as a partially applied function
       val v3 = m4                ^

利用这种自动转换,我们可以写出很简洁的代码,如下面这样

scala> //10.<被解释成obj.method,即整形的<的方法,所以该表达式是一个方法,会被解释成函数scala> myList.filter(10.<)
res18: List[Int] = List(56, 72)

因为在scala中操作符被解释称方法

  • 前缀操作符:op obj 被解释称obj.op

  • 中缀操作符:obj1 op obj2被解释称obj1.op(obj2)

  • 后缀操作符:obj op被解释称obj.op

你可以写成10<而不是10.<

scala> myList.filter(10<)
warning: there were 1 feature warning(s); re-run with -feature for details
res19: List[Int] = List(56, 72)

 

如何强制把一个方法变成函数

可以在方法名后面加一个下划线强制变成函数,部分应用函数

scala> val f4 = m4 _
f4: Int => Int = <function1>scala> f4(2)
res20: Int = 6

 

传名参数是一个方法

传名参数实质是一个没有参数列表的方法。正是因此你才可以使用名字调用而不用添加()

scala> //使用两次'x',意味着进行了两次方法调用scala> def m1(x: => Int)=List(x,x)
m1: (x: => Int)List[Int]

scala> import util.Randomimport util.Random

scala> val r = new Random()
r: scala.util.Random = scala.util.Random@d4c330b

scala> //因为方法被调用了两次,所以两个值不相等scala> m1(r.nextInt)
res21: List[Int] = List(-1273601135, 2004676878)

如果你在方法体部分缓存了传名参数(函数),那么你就缓存了值(因为x函数被调用了一次)

scala> //把传名参数代表的函数缓存起来scala> def m1(x: => Int) ={val y=x;List(y,y)}
m1: (x: => Int)List[Int]

scala> m1(r.nextInt)
res22: List[Int] = List(-1040711922, -1040711922)

 

能否在函数体部分引用传名参数所代表的方法呢,是可以的(缓存的是传名参数所代表的方法)。

scala> def m1(x: => Int)={val y=x _;List(y(),y())}
m1: (x: => Int)List[Int]

scala> m1(r.nextInt)
res23: List[Int] = List(-1982925840, -933815401)

 

2.方法的一些特殊形式

 def f:Int=>Double = { 
   // 可以看成 def f: (Int=>Double) = {...}
        case 1 => 0.1
        case 2 => 0.2
        case _ => 0.0
    }
    f(1) // 0.1
    f(3) // 0.0


3.过程:如果函数体包含在花括号当中但没有前面的=号,那么返回类型就是Unit。这样的函数被称做过程(procedure)。过程不返回值,我们调用它仅仅是为了它的副作用。

例:
由于过程不返回任何值,所以我们可以略去=号。

  def box(s: String) { // 仔细看:没有=号

    var border = "-" * s.length + "--\n"

    println(border + "|" + s + "|\n" + border)

  }

 

4.匿名函数:函数字面量

函数的字面量是有类型的,(x:Int) => println(x) 的类型是  Int => Unit 。

这个类型同样由两部分组成, =>左边是参数的类型, =>右边是函数体的返回值类型。 
         函数的字面量类型:从哪里(类型) => 到哪里(类型) 如:   Int => Unit  
                            形式:((命名参数列表)=>函数实现)(参数列表) 

                    对比: save()        方法名(参数)

特殊地:

  • 无参数: (()=>函数实现)()
  • 有一个参数且在最后: (函数实现)(参数)
  • 无返回值: ((命名参数列表)=>Unit)(参数列表)

匿名函数过多也可能造成代码的可读性下降,这点在开发的过程中要注意。

 

5.参数:

1) "_" 匿名函数中的匿名参数    
    注意:多个下划线指代多个参数,而不是单个参数的重复使用。第一个下划线代表第一个参数,第二个下划线代表第二个,第三个……,如此类推。    


    ((i:Int, j:Int) => i+j)(3, 4) // 7    
    ((_:Int) + (_:Int))(3,4) // 7 注意:括号(_:Int)括号是必须的
  "Hello".exists(_.isUpper) // true    

 如果_在最后,还可以省略     

   1 to 5 foreach println

        1 to 5 map (10*)

        1 to 5 map (*10) // 错误!_不在最后不能省    
        
    

        def sum(x:Int,y:Int,z:Int) = x+y+z

        val sum1 = sum _

        val sum2 = sum(1000,_:Int,100)

        sum1(1,2,3) // 6

        sum2(5) // 1105

        

 2)变长参数 *

 变长参数只能放在最后一个,否则就歧义了,           

 def sum(ns:Int*, s:String) = ... // 错误

            例子1:

            def sum(ns: Int*) = {

                var s = 0

                for (n<-ns) s += n

                s

            }

            sum(1,2,3,4) // 10

 更函数化的写法:           

 def sum(ns:Int*) = ns.reduceLeft(_+_)

            sum(1,2,3,4) // 10

 例子2:            

def m(args:Any*) = args foreach println

            scala> m(3,3.14,"hello",List(1,2,3))

            3

            3.14

            hello

            List(1, 2, 3)

 

转载于:https://my.oschina.net/wii01/blog/919711

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值