Scala 在函数中第二次使用变量不返回相同的值?

Scala 在函数中第二次使用变量不返回相同的值?,scala,iterator,immutability,Scala,Iterator,Immutability,我开始学习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: A

我开始学习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)
  }
}

val
表示不能为其分配新值。如果这是不可变的东西—数字、不可变集合、元组或其他不可变事物的case类—那么您的值在其生命周期内不会改变—如果这是函数中的
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
文件
是对
缓冲源
的不可变引用。您不能将其替换为另一个
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编写的,但现在请记住,您应该以引用透明性为目标-如果您可以使用
val counted=countLines(file)
中的值替换对
countLines(file)
的每个调用,则它将是RT。正如您所检查的,它不是。所以,用一个如果被调用两次也不会改变行为的东西来替换它。一种方法是调用整个计算两次,其间不保留任何全局状态(例如,
BufferedSource
中的内部计数器)。IO单子使这变得更容易,因此,一旦您对语法本身感到满意,就使用它们(以避免一次学习太多东西)。

val
意味着您无法为它分配新的值。如果这是不可变的东西—数字、不可变集合、元组或其他不可变事物的case类—那么您的值在其生命周期内不会改变—如果这是函数中的
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
文件
是对
缓冲源
的不可变引用。您不能将其替换为另一个
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编写的,但现在请记住,您应该以引用透明性为目标-如果您可以使用
val counted=countLines(file)
中的值替换对
countLines(file)
的每个调用,则它将是RT。正如您所检查的,它不是。所以,用一个如果被调用两次也不会改变行为的东西来替换它。一种方法是调用整个计算两次,其间不保留任何全局状态(例如,
BufferedSource
中的内部计数器)。IO monad使这变得更容易,因此,一旦您对语法本身感到满意,就使用它们(以避免一次学习太多东西)。

file.getLines
返回
迭代器[String]
,这意味着我们只能对其进行一次迭代,例如,请考虑

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()
。这个调用将提升迭代器的状态,如果发生这种情况,我们将无法返回到以前的状态

或者,您可以尝试
长度
而不是
计数

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
因此,如果
迭代器的底层具体集合
知道它的大小,那么对
length
的调用将缩短CUIRCIT并
它。next()
将永远不会执行,这意味着迭代器将不会被使用。这是
迭代器
工厂使用的默认具体集合的情况,即
数组

val it = Iterator("a", "b", "c")
it.getClass.getSimpleName
// res6: Class[_ <: Iterator[String]] = class scala.collection.ArrayOps$ArrayIterator
关于
val
ue定义和不变性的最后一点。考虑

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
是对可变对象的不可变引用。

文件。getLines
返回
迭代器[String]
,这意味着我们只能对其进行一次迭代,例如,考虑

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()
。这个调用将提升迭代器的状态,如果发生这种情况,我们将无法返回到以前的状态

作为替代方案,您可以尝试