Scala 什么是好的例子:“什么是好的例子?”;程序的操作应将输入值映射到输出值,而不是就地更改数据;

Scala 什么是好的例子:“什么是好的例子?”;程序的操作应将输入值映射到输出值,而不是就地更改数据;,scala,functional-programming,Scala,Functional Programming,我在解释Scala的功能性行为时遇到了这句话 程序的操作应该将输入值映射到输出值,而不是就地更改数据 谁能举个好例子解释一下吗 编辑:请在上下文中解释或举例说明上述句子,请不要让它变得更复杂,以引起更多的混淆我认为这是以下两者之间的区别: var counter = 0 def updateCounter(toAdd: Int): Unit = { counter += toAdd } updateCounter(8) println(counter) 以及: 这是一个过分简化的例子,但更

我在解释Scala的功能性行为时遇到了这句话

程序的操作应该将输入值映射到输出值,而不是就地更改数据

谁能举个好例子解释一下吗


编辑:请在上下文中解释或举例说明上述句子,请不要让它变得更复杂,以引起更多的混淆

我认为这是以下两者之间的区别:

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
    应用到a
    Coll[a]
    以获得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实现中有一个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!