简体   繁体   English

有什么好的例子:“程序的操作应该将输入值映射到输出值而不是更改数据”

[英]What are good examples of: “operation of a program should map input values to output values rather than change data in place”

I came across this sentence in Scala in explaining its functional behavior . 我在Scala中发现了这句话,解释了它的功能行为

operation of a program should map input of values to output values rather than change data in place 程序的操作应该将值的输入映射到输出值,而不是在适当的位置改变数据

Could somebody explain it with a good example? 有人可以用一个很好的例子来解释它吗?

Edit: Please explain or give example for the above sentence in its context, please do not make it complicate to get more confusion 编辑:请在上下文中解释或举例说明上述句子,请不要让它变得更加混乱

The most obvious pattern that this is referring to is the difference between how you would write code which uses collections in Java when compared with Scala. 这是指最明显的模式,即与Scala相比,编写使用Java集合的代码的方式之间存在差异。 If you were writing scala but in the idiom of Java , then you would be working with collections by mutating data in place. 如果您正在编写scala但是在Java习惯用法中 ,那么您将通过适当地改变数据来处理集合。 The idiomatic scala code to do the same would favour the mapping of input values to output values. 执行相同操作的惯用 scala代码有利于将输入值映射到输出值。

Let's have a look at a few things you might want to do to a collection: 让我们来看看你可能想要对集合做的一些事情:

Filtering 过滤

In Java, if I have a List<Trade> and I am only interested in those trades executed with Deutsche Bank , I might do something like: 在Java中,如果我有一个List<Trade>而且我只对那些用德意志银行执行的交易感兴趣,我可能会这样做:

for (Iterator<Trade> it = trades.iterator(); it.hasNext();) {
    Trade t = it.next();
    if (t.getCounterparty() != DEUTSCHE_BANK) it.remove(); // MUTATION
}

Following this loop, my trades collection only contains the relevant trades. 在此循环之后,我的trades集合仅包含相关交易。 But, I have achieved this using mutation - a careless programmer could easily have missed that trades was an input parameter, an instance variable, or is used elsewhere in the method. 但是,我已经使用变异实现了这一点 - 粗心的程序员很容易错过trades是输入参数,实例变量,或者在方法的其他地方使用。 As such, it is quite possible their code is now broken . 因此, 很可能他们的代码现在已被破坏 Furthermore, such code is extremely brittle for refactoring for this same reason; 此外,由于同样的原因,这样的代码对于重构非常脆弱 ; a programmer wishing to refactor a piece of code must be very careful to not let mutated collections escape the scope in which they are intended to be used and, vice-versa, that they don't accidentally use an un-mutated collection where they should have used a mutated one. 一个希望重构一段代码的程序员必须非常小心,不要让变异的集合逃避它们打算使用的范围,反之亦然,它们不会意外地使用一个未变异的集合,它们应该在使用过变异的。

Compare with Scala: 与Scala比较:

val db = trades filter (_.counterparty == DeutscheBank) //MAPPING INPUT TO OUTPUT

This creates a new collection ! 创造了一个新的集合 It doesn't affect anyone who is looking at trades and is inherently safer. 它不会影响任何正在寻找trades并且本质上更安全的人。

Mapping 制图

Suppose I have a List<Trade> and I want to get a Set<Stock> for the unique stocks which I have been trading. 假设我有一个List<Trade> ,我想得到一个我已经交易的独特股票的Set<Stock> Again, the idiom in Java is to create a collection and mutate it. 同样,Java中的习语是创建一个集合并对其进行变异

Set<Stock> stocks = new HashSet<Stock>();
for (Trade t : trades) stocks.add(t.getStock()); //MUTATION

Using scala the correct thing to do is to map the input collection and then convert to a set: 使用scala正确的做法是映射输入集合,然后转换为集合:

val stocks = (trades map (_.stock)).toSet  //MAPPING INPUT TO OUTPUT

Or, if we are concerned about performance: 或者,如果我们关注性能:

(trades.view map (_.stock)).toSet
(trades.iterator map (_.stock)).toSet

What are the advantages here? 这有什么好处? Well: 好:

  1. My code can never observe a partially-constructed result 我的代码永远不会观察到部分构造的结果
  2. The application of a function A => B to a Coll[A] to get a Coll[B] is clearer. 将函数A => B应用于Coll[A]以获得Coll[B]更清楚。

Accumulating 累积

Again, in Java the idiom has to be mutation. 同样,在Java中,成语必须是变异。 Suppose we are trying to sum the decimal quantities of the trades we have done: 假设我们试图将我们所做交易的十进制数量相加:

BigDecimal sum = BigDecimal.ZERO
for (Trade t : trades) {
    sum.add(t.getQuantity()); //MUTATION
}

Again, we must be very careful not to accidentally observe a partially-constructed result! 同样,我们必须非常小心,不要意外地观察到部分构造的结果! In scala, we can do this in a single expression: 在scala中,我们可以在一个表达式中执行此操作:

val sum = (0 /: trades)(_ + _.quantity) //MAPPING INTO TO OUTPUT

Or the various other forms: 或其他各种形式:

(trades.foldLeft(0)(_ + _.quantity)
(trades.iterator map (_.quantity)).sum
(trades.view map (_.quantity)).sum

Oh, by the way, there is a bug in the Java implementation! 哦,顺便说一句, Java实现中有一个错误! Did you spot it? 你发现了吗?

I'd say it's the difference between: 我会说它之间的区别是:

var counter = 0
def updateCounter(toAdd: Int): Unit = {
  counter += toAdd
}
updateCounter(8)
println(counter)

and: 和:

val originalValue = 0
def addToValue(value: Int, toAdd: Int): Int = value + toAdd
val firstNewResult = addToValue(originalValue, 8)
println(firstNewResult)

This is a gross over simplification but fuller examples are things like using a foldLeft to build up a result rather than doing the hard work yourself: foldLeft example 这是一个粗略的过度简化,但更全面的例子就像使用foldLeft来建立结果而不是自己做艰苦的工作: foldLeft示例

What it means is that if you write pure functions like this you always get the same output from the same input, and there are no side effects, which makes it easier to reason about your programs and ensure that they are correct. 这意味着如果你编写这样的纯函数,你总是从同一个输入得到相同的输出,并且没有副作用,这使得更容易推理你的程序并确保它们是正确的。

so for example the function: 所以例如功能:

def times2(x:Int) = x*2

is pure, while 是纯粹的,而

def add5ToList(xs: MutableList[Int]) {
    xs += 5
}

is impure because it edits data in place as a side effect. 是不纯的,因为它将数据编辑为副作用。 This is a problem because that same list could be in use elsewhere in the the program and now we can't guarantee the behaviour because it has changed. 这是一个问题,因为相同的列表可能在程序的其他地方使用,现在我们无法保证行为,因为它已经改变。

A pure version would use immutable lists and return a new list 纯版本将使用不可变列表并返回新列表

def add5ToList(xs: List[Int]) = {
    5::xs
}

There are plenty examples with collections, which are easy to come by but might give the wrong impression. 收藏品有很多例子,很容易找到,但可能给人留下错误的印象。 This concept works at all levels of the language (it doesn't at the VM level, however). 这个概念适用于该语言的所有级别 (但不是在VM级别)。 One example is the case classes. 一个例子是案例类。 Consider these two alternatives: 考虑以下两种选择:

// 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!

This concept is applied on the construction of the immutable collection themselves: a List never changes. 这个概念适用于构造不可变集合本身: List永远不会改变。 Instead, new List objects are created when necessary. 而是在必要时创建新的List对象。 Use of persistent data structures reduce the copying that would happen on a mutable data structure. 使用持久性数据结构可以减少可变数据结构上发生的复制。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM