【问题标题】:Using variable second times in function not return the same value?在函数中第二次使用变量不返回相同的值?
【发布时间】:2020-09-02 05:10:38
【问题描述】:

我开始学习 Scala,并编写了该代码。我有疑问,为什么 val 是不变的?当我第二次将它传递给同一个函数时返回其他值? scala中的纯函数怎么写?

如果计算正确的话,有什么意见吗?

import java.io.FileNotFoundException
import java.io.IOException
import scala.io.BufferedSource
import scala.io.Source.fromFile

object Main{  
  def main(args: Array[String]): Unit = { 
    val fileName: String = if(args.length == 1) args(0) else ""
    try {
      val file = fromFile(fileName)

      /* In file tekst.txt is 4 lines */
      println(s"In file $fileName is ${countLines(file)} lines")
      /* In file tekst.txt is 0 lines */
      println(s"In file $fileName is ${countLines(file)} lines")

      file.close
    } 
    catch{
      case e: FileNotFoundException => println(s"File $fileName not found")
      case _: Throwable => println("Other error")
    }
  }

  def countLines(file: BufferedSource): Long = {
    file.getLines.count(_ => true)
  }
}

【问题讨论】:

    标签: scala iterator immutability


    【解决方案1】:

    val 表示您不能为其分配新值。如果这是不可变的东西——一个数字、不可变的集合、元组或其他不可变事物的案例类——那么你的值在它的生命周期内不会改变——如果这是val在一个函数中,当你给它赋值时,它会保持不变,直到您离开该功能。如果这是类中的值,则在对该类的所有调用之间它将保持不变。如果这是对象,它将在整个程序生命周期内保持不变。

    但是,如果你说的对象本身是可变的,那么唯一不可变的部分就是对对象的引用。如果你有一个mutable.MutableList 的值,那么你可以将它与另一个mutable.MutableList 交换,但你可以修改列表的内容。这里:

    val file = fromFile(fileName)
    
    /* In file tekst.txt is 4 lines */
    println(s"In file $fileName is ${countLines(file)} lines")
    /* In file tekst.txt is 0 lines */
    println(s"In file $fileName is ${countLines(file)} lines")
    
    file.close
    

    file 是对BufferedSource 的不可变引用。你不能用另一个 BufferedSource 替换它 - 但是这个类有内部状态,它计算它已经从文件中读取了多少行,所以第一次操作它时你会收到文件中的总行数,然后(因为文件已读)0.

    如果您希望代码更纯粹,则应包含可变性,以便用户无法观察到它,例如

    def countFileLines(fileName: String): Either[String, Long] = try {
      val file = fromFile(fileName)
      try {
        Right(file.getLines.count(_ => true))
      } finally {
        file.close()
      }
    } catch {
      case e: FileNotFoundException => Left(s"File $fileName not found")
      case _: Throwable => Left("Other error")
    }
    
    
    println(s"In file $fileName is ${countLines(fileName)} lines")
    println(s"In file $fileName is ${countLines(fileName)} lines")
    

    你仍然有副作用,所以理想情况下它应该是使用 IO monad 编写的东西,但现在请记住,你应该以引用透明为目标——如果你可以用来自 @ 的值替换每个对 countLines(file) 的调用987654331@ 是 RT。正如你检查的那样,它不是。因此,如果它被调用两次,则将其替换为不会改变行为的东西。一种方法是调用整个计算两次,它们之间不保留任何全局状态(例如BufferedSource 中的内部计数器)。 IO monad 使这更容易,所以一旦你对语法本身感到满意就去追求它们(以避免一次学习太多东西)。

    【讨论】:

      【解决方案2】:

      file.getLines 返回Iterator[String]iterator可消耗的,这意味着我们只能迭代它一次,例如,考虑

      val it = Iterator("a", "b", "c")
      
      it.count(_ => true)
      // val res0: Int = 3
      
      it.count(_ => true)
      // val res1: Int = 0
      

      count的实现

      def count(p: A => Boolean): Int = {
        var res = 0
        val it = iterator
        while (it.hasNext) if (p(it.next())) res += 1
        res
      }
      

      请注意对it.next() 的调用。这个调用推进了迭代器的状态,如果它发生了,我们就不能回到之前的状态。

      作为替代方案,您可以尝试length 而不是count

      val it = Iterator("a", "b", "c")
      
      it.length
      // val res0: Int = 3
      
      it.length
      // val res0: Int = 3
      

      查看length 的定义,它只是委托给size

      def size: Int = {
        if (knownSize >= 0) knownSize
        else {
          val it = iterator
          var len = 0
          while (it.hasNext) { len += 1; it.next() }
          len
        }
      }  
      

      注意警卫

      if (knownSize >= 0) knownSize
      

      有些集合知道它们的大小,而不必通过迭代它们来计算它。例如,

      Array(1,2,3).knownSize  //  3: I know my size in advance
      List(1,2,3).knownSize   // -1: I do not know my size in advance so I have to traverse the whole collection to find it
      

      所以如果Iterator 的底层具体集合知道它的大小,那么对length 的调用将缩短电路并且it.next() 将永远不会执行,这意味着不会消耗迭代器。这是Iterator 工厂使用的默认具体集合的情况,即Array

      val it = Iterator("a", "b", "c")
      it.getClass.getSimpleName
      // res6: Class[_ <: Iterator[String]] = class scala.collection.ArrayOps$ArrayIterator
      

      BufferedSource 并非如此。要解决此问题,请考虑在每次调用 countLines 时创建一个新迭代器

      def countLines(fileName: String): Long = {
        fromFile(fileName).getLines().length
      }
      
      println(s"In file $fileName is ${countLines(fileName)} lines")
      println(s"In file $fileName is ${countLines(fileName)} lines")
      
      // In file build.sbt is 22 lines
      // In file build.sbt is 22 lines
      

      关于value 定义和不变性的最后一点。考虑

      object Foo { var x = 42 } // object contains mutable state
      
      val foo = Foo // value definition
      
      foo.x
      // val res0: Int = 42
      
      Foo.x = -11 // mutation happening here
      
      foo.x
      // val res1: Int = -11
      

      这里的标识符 foo 是对 mutable 对象的不可变引用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-09
        • 2018-02-01
        • 1970-01-01
        • 2021-06-03
        • 2014-12-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多