我读过Scala函数(Scala另一个指南的一部分)。在那篇帖子中,他说:
方法和函数不是一回事
但他什么也没解释。他到底想说什么?
我读过Scala函数(Scala另一个指南的一部分)。在那篇帖子中,他说:
方法和函数不是一回事
但他什么也没解释。他到底想说什么?
当前回答
函数不支持默认参数。做的方法。从方法转换到函数会丢失参数默认值。(Scala 2.8.1发布)
其他回答
假设你有一个列表
scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
定义一个方法
scala> def m1(i:Int)=i+2
m1: (i: Int)Int
定义一个函数
scala> (i:Int)=>i+2
res0: Int => Int = <function1>
scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
接受参数的方法
scala> m1(2)
res3: Int = 4
用val定义函数
scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>
函数的参数是可选的
scala> p(2)
res4: Int = 4
scala> p
res5: Int => Int = <function1>
方法的参数是强制的
scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function
查看下面的教程,它解释了通过其他示例传递其他差异,如diff与方法Vs函数的其他示例,使用函数作为变量,创建返回函数的函数
区别是细微的,但却是实质性的,它与所使用的类型系统有关(除了来自面向对象或函数范式的术语)。
当我们谈论函数时,我们谈论的是函数类型:它是一种类型,它的实例可以作为输入或输出传递给其他函数(至少在Scala中是这样)。
当我们谈论(类的)方法时,我们实际上是在谈论它所属的类所表示的类型:也就是说,方法只是更大类型的一个组件,不能单独传递。它必须与它所属类型的实例(即类的实例)一起传递。
在Scala 2.13中,与函数不同,方法可以接受/返回
类型参数(多态方法) 隐式参数 从属类型
然而,这些限制在dotty (Scala 3)中通过多态函数类型#4672解除了,例如,dotty版本0.23.0-RC1支持以下语法
类型参数
def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
隐式参数(上下文参数)
def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero
从属类型
class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet
更多示例请参见tests/run/ polymorphism -functions.scala
方法属于一个对象(通常是定义它的类、trait或对象),而函数本身是一个值,因为在Scala中每个值都是一个对象,因此,函数是一个对象。
例如,给定下面的方法和函数:
def timesTwoMethod(x :Int): Int = x * 2
def timesTwoFunction = (x: Int) => x * 2
第二个def是Int => Int类型的对象(Function1[Int, Int]的语法糖)。
Scala将函数作为对象,这样它们就可以作为一级实体使用。通过这种方式,可以将函数作为参数传递给其他函数。
然而,Scala也可以通过一种称为Eta展开的机制将方法视为函数。
例如,定义在List上的高阶函数映射,接收另一个函数f: A => B作为其唯一参数。接下来的两行是等价的:
List(1, 2, 3).map(timesTwoMethod)
List(1, 2, 3).map(timesTwoFunction)
当编译器在需要函数的地方看到def时,它会自动将该方法转换为等效的函数。
吉姆在他的博客文章中已经详细介绍了这一点,但我在这里发布了一个简报供参考。
首先,让我们看看Scala规范告诉了我们什么。第3章(类型)告诉我们函数类型(3.2.9)和方法类型(3.3.1)。第4章(基本声明)讲了值声明和定义(4.1),变量声明和定义(4.2)和函数声明和定义(4.6)。第6章(表达式)谈到了匿名函数(6.23)和方法值(6.7)。奇怪的是,函数值只在3.2.9中提到过一次,其他地方都没有提到过。
函数类型(大致)是一种形式为(T1,…, Tn) => U,这是标准库中trait FunctionN的缩写。匿名函数和方法值具有函数类型,并且函数类型可以用作值、变量和函数声明和定义的一部分。事实上,它可以是方法类型的一部分。
方法类型是非值类型。这意味着没有值-没有对象,没有实例-具有方法类型。如上所述,方法值实际上有一个函数类型。方法类型是一个def声明——关于def的一切,除了它的主体。
值声明和定义以及变量声明和定义是val和var声明,包括类型和值——分别可以是函数类型和匿名函数或方法值。注意,在JVM上,这些(方法值)是用Java所称的“方法”实现的。
函数声明是一个def声明,包括类型和主体。类型部分是方法类型,主体是表达式或块。这也是在JVM上实现的,Java称之为“方法”。
最后,一个匿名函数是一个函数类型的实例(例如,trait FunctionN的实例),一个方法值是一样的!区别在于方法值是从方法中创建的,或者通过添加下划线(m_是对应于“函数声明”(def) m的方法值),或者通过称为eta-expansion的过程创建,这类似于从方法到函数的自动强制转换。
这是说明书上说的,所以让我把这放在前面:我们不使用这个术语!它导致了所谓的“函数声明”,这是程序的一部分(第4章-基本声明)和“匿名函数”,这是一个表达式,以及“函数类型”,这是一种类型-一种特征之间的太多混淆。
下面的术语是由有经验的Scala程序员使用的,它与规范中的术语有一个不同之处:我们说方法而不是函数声明。甚至是方法声明。此外,我们注意到值声明和变量声明也是用于实际目的的方法。
因此,鉴于上述术语的变化,这里有一个对区别的实际解释。
函数是包含FunctionX特征之一的对象,如Function0、Function1、Function2等。它可能还包括了PartialFunction,它实际上扩展了Function1。
让我们看看其中一个特征的类型签名:
trait Function2[-T1, -T2, +R] extends AnyRef
这个特性有一个抽象方法(它也有一些具体方法):
def apply(v1: T1, v2: T2): R
这告诉了我们关于它的一切。一个函数有一个apply方法,它接收N个类型为T1, T2,…, TN,并返回r类型的东西。它接收的参数是逆变的,结果是协变的。
这种差异意味着Function1[Seq[T], String]是Function1[List[T], AnyRef]的子类型。作为子类型意味着它可以用来代替它。人们可以很容易地看到,如果我要调用f(List(1,2,3))并期望返回AnyRef,上面两种类型中的任何一种都可以工作。
现在,方法和函数有什么相似之处呢?好吧,如果f是一个函数,m是一个局部作用域的方法,那么两者都可以像这样调用:
val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))
这些调用实际上是不同的,因为第一个调用只是一个语法糖。Scala将其扩展为:
val o1 = f.apply(List(1, 2, 3))
当然,这是对对象f的方法调用。函数还有其他语法糖的优势:函数字面量(实际上是两个)和(T1, T2) => R类型签名。例如:
val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
case i: Int => "Int"
case d: Double => "Double"
case o => "Other"
}
方法和函数的另一个相似之处在于前者可以很容易地转换为后者:
val f = m _
Scala将展开它,假设m type为(List[Int])AnyRef为(Scala 2.7):
val f = new AnyRef with Function1[List[Int], AnyRef] {
def apply(x$1: List[Int]) = this.m(x$1)
}
在Scala 2.8上,它实际上使用了AbstractFunction1类来减小类的大小。
注意,不能以另一种方式进行转换——从函数转换到方法。
然而,方法有一个很大的优势(好吧,是两个——它们可以稍微快一点):它们可以接收类型参数。例如,上面的f可以指定它接收到的List的类型(例子中的List[Int]), m可以参数化它:
def m[T](l: List[T]): String = l mkString ""
我认为这几乎涵盖了所有内容,但我很乐意补充回答任何可能存在的问题。