Scala 什么是好的例子:“什么是好的例子?”;程序的操作应将输入值映射到输出值,而不是就地更改数据;
我在解释Scala的功能性行为时遇到了这句话 程序的操作应该将输入值映射到输出值,而不是就地更改数据 谁能举个好例子解释一下吗Scala 什么是好的例子:“什么是好的例子?”;程序的操作应将输入值映射到输出值,而不是就地更改数据;,scala,functional-programming,Scala,Functional Programming,我在解释Scala的功能性行为时遇到了这句话 程序的操作应该将输入值映射到输出值,而不是就地更改数据 谁能举个好例子解释一下吗 编辑:请在上下文中解释或举例说明上述句子,请不要让它变得更复杂,以引起更多的混淆我认为这是以下两者之间的区别: var counter = 0 def updateCounter(toAdd: Int): Unit = { counter += toAdd } updateCounter(8) println(counter) 以及: 这是一个过分简化的例子,但更
编辑:请在上下文中解释或举例说明上述句子,请不要让它变得更复杂,以引起更多的混淆我认为这是以下两者之间的区别:
var counter = 0
def updateCounter(toAdd: Int): Unit = {
counter += toAdd
}
updateCounter(8)
println(counter)
以及:
这是一个过分简化的例子,但更完整的例子是使用foldLeft来构建结果,而不是自己做艰苦的工作:这指的是最明显的模式是,与Scala相比,您编写使用Java集合的代码的方式有所不同。如果您是用Java的习惯用法编写scala,那么您将通过就地修改数据来处理集合。这样做的惯用scala代码有利于将输入值映射到输出值 让我们看一下您可能希望对集合执行的几项操作: 过滤 在Java中,如果我有一个
列表
,并且我只对德意志银行执行的那些交易感兴趣,我可能会这样做:
for (Iterator<Trade> it = trades.iterator(); it.hasNext();) {
Trade t = it.next();
if (t.getCounterparty() != DEUTSCHE_BANK) it.remove(); // MUTATION
}
这将创建一个新的集合!它不会影响任何关注交易的人,而且本质上更安全
映射
假设我有一个列表
,我想为我一直交易的独特股票获得一个集合
。同样,Java中的习惯用法是创建一个集合并对其进行变异
或者,如果我们关注绩效:
(trades.view map (_.stock)).toSet
(trades.iterator map (_.stock)).toSet
这里的优势是什么?嗯:
我的代码永远无法观察到部分构造的结果
将函数a=>B
应用到aColl[a]
以获得aColl[B]
更清晰
积累
同样,在Java中,习惯用法必须是变异。假设我们试图将我们所做交易的小数数量相加:
BigDecimal sum = BigDecimal.ZERO
for (Trade t : trades) {
sum.add(t.getQuantity()); //MUTATION
}
同样,我们必须非常小心,不要意外地观察到部分构造的结果!在scala中,我们可以在单个表达式中执行此操作:
val sum = (0 /: trades)(_ + _.quantity) //MAPPING INTO TO OUTPUT
或其他各种形式:
(trades.foldLeft(0)(_ + _.quantity)
(trades.iterator map (_.quantity)).sum
(trades.view map (_.quantity)).sum
哦,顺便说一下,Java实现中有一个bug你发现了吗?这意味着如果你这样写,你总是从相同的输入中得到相同的输出,并且没有副作用,这使得你更容易对程序进行推理并确保它们是正确的
例如,函数:
def times2(x:Int) = x*2
是纯洁的,而
def add5ToList(xs: MutableList[Int]) {
xs += 5
}
是不纯的,因为它作为副作用就地编辑数据。这是一个问题,因为相同的列表可能在程序的其他地方使用,现在我们不能保证行为,因为它已经改变了
纯版本将使用不可变列表并返回新列表
def add5ToList(xs: List[Int]) = {
5::xs
}
收藏的例子很多,很容易找到,但可能会给人错误的印象。这一概念适用于语言的所有级别(但不适用于VM级别)。一个例子是case类。考虑这两个备选方案:
// Java-style
class Person(initialName: String, initialAge: Int) {
def this(initialName: String) = this(initialName, 0)
private var name = initialName
private var age = initialAge
def getName = name
def getAge = age
def setName(newName: String) { name = newName }
def setAge(newAge: Int) { age = newAge }
}
val employee = new Person("John")
employee.setAge(40) // we changed the object
// Scala-style
case class Person(name: String, age: Int) {
def this(name: String) = this(name, 0)
}
val employee = new Person("John")
val employeeWithAge = employee.copy(age = 40) // employee still exists!
这个概念应用于不可变集合本身的构造:一个列表
永远不会改变。而是在必要时创建新的列表
对象。使用持久数据结构可以减少在可变数据结构上发生的复制。这是一个很好的例子。我从你的例子中理解了那句话的确切含义。非常感谢你。你能确切地说你在句子和例子中的解释是如何与函数编程相关的吗?foldLeft是函数编程的一个更好的例子,因为从一个foldLeft中获取结果并将其作为另一个foldLeft或任何其他函数的种子值传递进来是很简单的。当我们知道该值的状态时,我们可以更好地对最终结果进行推理。而对于一些随机可变变量,其他任何东西都可能同时改变该值。可以说,如果不使用不可变变量以函数方式实现,很难理解为什么这是一件好事。请不要让解释变得更复杂。我只想知道这个句子是如何关联功能性行为的。为什么这个解释更复杂?我已经解释了原地改变数据的Java习惯用法,以及在使用集合执行常见任务时,如何在scala中通过将输入映射到输出来实现同样的事情!你要问的另一个名字是“参考透明度”。这意味着函数应始终在其输入参数中接收足够的上下文,这样当函数运行时,它不会修改输入参数,它不需要额外的“全局”上下文,并将返回特定的输出。此外,它还保证,如果使用完全相同的输入参数调用相同的函数,则可以预期和接收完全相同的输出。这就支持了下一个更高的概念,可组合性。
def times2(x:Int) = x*2
def add5ToList(xs: MutableList[Int]) {
xs += 5
}
def add5ToList(xs: List[Int]) = {
5::xs
}
// Java-style
class Person(initialName: String, initialAge: Int) {
def this(initialName: String) = this(initialName, 0)
private var name = initialName
private var age = initialAge
def getName = name
def getAge = age
def setName(newName: String) { name = newName }
def setAge(newAge: Int) { age = newAge }
}
val employee = new Person("John")
employee.setAge(40) // we changed the object
// Scala-style
case class Person(name: String, age: Int) {
def this(name: String) = this(name, 0)
}
val employee = new Person("John")
val employeeWithAge = employee.copy(age = 40) // employee still exists!