我在Scala中发现了这句话来解释其功能行为。
程序的操作应该将值的输入映射到输出值而不是更改数据
有人可以用一个很好的例子解释一下吗?
编辑:请在上下文中解释或举例说明上述句子,请不要让其变得更加混乱
答案 0 :(得分:14)
这指的最明显的模式是,与Scala相比,编写使用Java集合的代码的方式不同。如果您正在编写scala但是在成语Java 中,那么您将通过适当地改变数据来处理集合。用于执行相同操作的惯用标量代码有利于将输入值映射到输出值。
让我们看一下你可能想对集合做的一些事情:
在Java中,如果我有一个List<Trade>
且我只对那些用 Deutsche Bank 执行的交易感兴趣,我可能会这样做:
for (Iterator<Trade> it = trades.iterator(); it.hasNext();) {
Trade t = it.next();
if (t.getCounterparty() != DEUTSCHE_BANK) it.remove(); // MUTATION
}
在此循环之后,我的trades
集合仅包含相关交易。但是,我使用变异实现了这一点 - 一个粗心的程序员很容易错过 trades
是一个输入参数,一个实例变量,或者在其他地方用过方法。因此,他们的代码现在很可能很可能。此外,出于同样的原因,这样的代码对于重构非常脆弱;一个希望重构一段代码的程序员必须非常小心,不要让变异的集合逃避它们打算使用的范围,反之亦然,它们不会意外地使用未变异的集合,它们应该在使用过变异的。
与Scala比较:
val db = trades filter (_.counterparty == DeutscheBank) //MAPPING INPUT TO OUTPUT
这个创建了一个新的集合!它不会影响任何正在关注trades
并且本质上更安全的人。
假设我有一个List<Trade>
,我希望得到Set<Stock>
我所交易的独特股票。同样,Java中的习惯用语是创建一个集合并 mutate 它。
Set<Stock> stocks = new HashSet<Stock>();
for (Trade t : trades) stocks.add(t.getStock()); //MUTATION
使用scala正确的做法是映射输入集合,然后转换为集合:
val stocks = (trades map (_.stock)).toSet //MAPPING INPUT TO OUTPUT
或者,如果我们关注绩效:
(trades.view map (_.stock)).toSet
(trades.iterator map (_.stock)).toSet
这有什么好处?良好:
A => B
应用于Coll[A]
以获取Coll[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实现中存在一个错误! 您是否发现了它?
答案 1 :(得分:2)
我之间的区别是:
var counter = 0
def updateCounter(toAdd: Int): Unit = {
counter += toAdd
}
updateCounter(8)
println(counter)
和
val originalValue = 0
def addToValue(value: Int, toAdd: Int): Int = value + toAdd
val firstNewResult = addToValue(originalValue, 8)
println(firstNewResult)
这是一个严格的过度简化,但更全面的例子就像使用foldLeft来建立结果而不是自己做艰苦的工作:foldLeft example
答案 2 :(得分:1)
这意味着如果你像这样写pure functions,你总是从同一输入得到相同的输出,并且没有副作用,这使得更容易推理你的程序并确保它们是正确的。
所以例如函数:
def times2(x:Int) = x*2
是纯粹的,而
def add5ToList(xs: MutableList[Int]) {
xs += 5
}
是不纯的,因为它将数据编辑为副作用。这是一个问题,因为相同的列表可能在程序的其他地方使用,现在我们无法保证行为,因为它已经改变。
纯版本将使用不可变列表并返回新列表
def add5ToList(xs: List[Int]) = {
5::xs
}
答案 3 :(得分:1)
有很多收藏品的例子很容易找到,但可能给人留下错误的印象。此概念适用于该语言的所有级别(但不是在VM级别)。一个例子是案例类。考虑以下两种选择:
// 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!
这个概念适用于构造不可变集合本身:List
永远不会改变。相反,必要时会创建新的List
个对象。使用持久性数据结构可以减少在可变数据结构上发生的复制。