scala中的对象实例化变体

Object instantiation variants in scala

我真的是 scala 的新手,我目前正在浏览教程 (https://docs.scala-lang.org/tour/variances.html)。

现在,查看一些库 (akka-http),我偶然发现了一些这样的代码:

def fetchItem(itemId: Long): Future[Option[Item]] = Future {
    orders.find(o => o.id == itemId)
  }

而且我不太了解语法,或者更准确地说,= Future { 部分。据我了解,方法的语法是 def [methodName]([Arguments])(:[returnType]) = [codeblock]

然而,上面的内容似乎有所不同,因为它在 "codeblock" 前面有 Future。这是某种对象实例化吗?因为我找不到关于这种语法的文档,所以我在我的播放代码中尝试了这样的东西:

{
  val myCat:Cat      = new Cat("first cat")
  val myOtherCat:Cat = Cat { "second cat" }
  val myThirdCat:Cat = MyObject.getSomeCat
}

...

object MyObject
{
   def getSomeCat: Cat = Cat 
   {
     "blabla"
   }
}

所有这些都有效,因为它创建了一个新的 Cat 对象。所以看起来 new Cat(args) 等同于 Cat { args }.

但是 def getSomeCat: Cat = Cat 不应该用代码块定义一个方法,而不是实例化一个新的 Cat 对象吗?我很困惑。

我认为这里有两点:

1. 方法语法中的 [codeblock] 不必包含在 {} 中。如果只有一个表达式,可以省略。

例如

def add(x: Int, y: Int) = x + y

def add(x: Int, y: Int) = Future { x + y }

2. 每个 class 都可以让其伴随对象定义一个 apply() 方法,无需显式声明 "apply" 即可调用该方法(这是特殊的 Scala 语法糖)。这允许我们通过遍历伴随对象来构造 class 的实例,并且由于 "apply" 可以省略,乍一看它看起来像遍历 class 本身,只是没有"new" 关键字。

没有对象:

class Cat(s: String)

val myFirstCat: Cat = new Cat("first cat") // OK
val mySecondCat: Cat = Cat("second cat") // error

现在有了对象:

class Cat(s: String)

object Cat {
  def apply(s: String) = new Cat(s)
}

val myFirstCat: Cat = new Cat("first cat") // OK
val mySecondCat: Cat = Cat.apply("second cat") // OK 
val myThirdCat: Cat = Cat("third cat") // OK (uses apply under the hood)
val myFourthCat: Cat = Cat { "fourth cat" } // OK as well

请注意第四次 cat 调用如何与花括号一起工作得很好,因为方法可以通过代码块传递(将传递代码块中最后计算的值,就像在函数中一样)。

3. Case classes 是另一种 "special" Scala 构造,从某种意义上说,它们通过自动为您提供一些东西 "behind the curtain" 来为您提供便利 "behind the curtain",包括与 apply() 关联的伴随对象。

case class Cat(s: String)

val myFirstCat: Cat = new Cat("first cat") // OK
val mySecondCat: Cat = Cat.apply("second cat") // OK
val myThirdCat: Cat = Cat("third cat") // OK

在你的 Future 案例中发生的事情是数字 2,与 "fourth cat" 相同。关于您关于 new Cat(args) 等同于 Cat { args } 的问题,最有可能的情况是第 3 种情况 - Cat 是 class 的情况。要么,要么它的伴随对象显式定义了 apply() 方法。

简短的回答是肯定的,Future 代码是一个对象实例化。

您的 Cat class 有一个 String 参数,可以使用 Cat(<string>) 创建。如果要计算字符串的值,可以像示例中那样使用 {} 将其放入块中。这个块可以包含任意代码,块的值将是块中最后一个表达式的值,类型必须是 String.

Future[T] class 有一个 T 类型的参数,可以使用 Future(T) 创建。您可以像以前一样传递任意代码块,只要它 returns 是 T.

类型的值

因此创建 Future 就像创建任何其他对象一样。 fetchItem 代码只是创建一个 Future 对象并返回它。

但是 Future 有一个微妙之处,因为参数被定义为 "call-by-name" 参数而不是默认的 "call-by-value" 参数。这意味着它在使用之前不会被评估,并且每次被使用时都会被评估。在 Future 的情况下,该参数稍后会被评估一次,并且可能会在不同的线程上评估。如果您使用块来计算参数,则每次使用参数时都会执行整个块。

Scala 具有非常强大的语法和一些有用的快捷方式,但可能需要一段时间才能习惯!

典型的方法结构如下所示:

case class Bar(name: String)
def foo(param: String): Bar = {
  // code block.
}    

但是,Scala 在方法定义方面非常灵活。灵活性之一是,如果您的方法块包含单个表达式,那么您可以忽略大括号 { }

def foo(param: String): Bar = {
  Bar("This is Bar")  //Block only contains single expression.
} 
// Can be written as:
def foo(param: String): Bar = Bar("This is Bar")

// In case of multiple expressions:
def foo(param: String): Bar = {
  println("Returning Bar...")
  Bar("This is Bar")
} 
def foo(param: String): Bar = println("Returning Bar...") Bar("This is Bar") //Fails
def foo(param: String): Bar = println("Returning Bar..."); Bar("This is Bar") //Fails
def foo(param: String): Bar = {println("Returning Bar..."); Bar("This is Bar")} // Works

同样,在您的代码中,fetchItem 仅包含一个表达式 - Future {orders.find(o => o.id == itemId)} return Option[Item] 的新 Future(Future 的实例),因此大括号 { } 是可选的。但是,如果您愿意,可以将其写在大括号内,如下所示:

def fetchItem(itemId: Long): Future[Option[Item]] = {
    Future {
        orders.find(o => o.id == itemId)
    }
}

同样,如果一个方法只有一个参数,你可以使用花括号。也就是说,您可以将 fetchItems 调用为:

fetchItem(10)
fetchItem{10}
fetchItem{
    10
}

So, why use curly braces { } instead of brackets ( )?

因为,您可以在大括号内提供多个代码块,当需要执行多次计算并且 return 一个值作为该计算的结果时,需要这种情况。例如:

fetchItem{
    val x = 2
    val y = 3
    val z = 2
    (x + y)*z  //final result is 10 which is passed as parameter.
}

// In addition, using curly braces will make your code more cleaner e.g. in case of higher ordered functions. 
def test(id: String => Unit) = ???
test {
    id => {
        val result: List[String] = getDataById(x)
        val updated = result.map(_.toUpperCase)
        updated.mkString(",")
    }
}

现在,谈到你的情况,当你调用 Future{...} 时,你正在调用 Scala 未来伴随对象的 apply(...) 方法,该方法将函数文字 body: =>T 作为参数并且 return 一个新的未来。

//The below code can also be written as:
Future {
    orders.find(o => o.id == itemId)
}
//Can be written as:
Future(orders.find(o => o.id == itemId))
Future.apply(orders.find(o => o.id == itemId))

// However, braces are not allowed with multiple parameter.
def foo(a:String, b:String) = ???
foo("1","2") //work
foo{"1", "2"} //won't work.