Scala的隐藏功能

Krz*_*Goj 149 scala hidden-features

每个Scala开发人员都应该注意Scala的隐藏功能是什么?

请回答一个隐藏的功能.

Wil*_*urn 85

好的,我不得不再添加一个.RegexScala中的每个对象都有一个提取器(参见上面的oxbox_lakes的答案),可以访问匹配组.所以你可以这样做:

// Regex to split a date in the format Y/M/D.
val regex = "(\\d+)/(\\d+)/(\\d+)".r
val regex(year, month, day) = "2010/1/13"
Run Code Online (Sandbox Code Playgroud)

如果您不习惯使用模式匹配和提取器,则第二行看起来很混乱.每当你定义一个val或者var,关键字之后的内容不仅仅是一个标识符,而是一个模式.这就是为什么这样做的原因:

val (a, b, c) = (1, 3.14159, "Hello, world")
Run Code Online (Sandbox Code Playgroud)

右手表达式创建了一个Tuple3[Int, Double, String]可以匹配模式的表达式(a, b, c).

大多数情况下,您的模式使用作为单例对象成员的提取器.例如,如果你写一个类似的模式

Some(value)
Run Code Online (Sandbox Code Playgroud)

然后你隐含地调用了提取器Some.unapply.

但是你也可以在模式中使用类实例,这就是这里发生的事情.val正则表达式是一个实例Regex,当你在一个模式中使用它时,你隐式调用regex.unapplySeq(unapplyunapplySeq不是超出了这个答案的范围),它将匹配组提取到a中Seq[String],其元素被分配以便变量年,月和日.


oxb*_*kes 51

结构类型定义 - 即通过它支持的方法描述的类型.例如:

object Closer {
    def using(closeable: { def close(): Unit }, f: => Unit) {
      try { 
        f
      } finally { closeable.close }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,除了具有方法之外,未定义参数的类型closeableclose


Apo*_*isp 45

Type-Constructor Polymorphism(又名更高级的类型)

例如,如果没有此功能,您可以表达在列表上映射函数以返回另一个列表,或在树上映射函数以返回另一个树的想法.但你不能表达这种想法通常不高于种.

使用更高类型,您可以捕获任何使用其他类型参数化的类型的想法.一个带有一个参数的类型构造函数被认为是实物(*->*).例如,List.返回另一个类型构造函数的类型构造函数被认为是实物(*->*->*).例如,Function1.但是在Scala中,我们有更高的类型,因此我们可以使用其他类型构造函数进行参数化的类型构造函数.所以他们就像((*->*)->*).

例如:

trait Functor[F[_]] {
  def fmap[A, B](f: A => B, fa: F[A]): F[B]
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你有Functor[List],你可以映射列表.如果你有Functor[Tree],你可以映射树木.但更重要的是,如果你有Functor[A] 任何A类(*->*),你可以映射一个函数A.


oxb*_*kes 39

提取器,允许您if-elseif-else用模式替换凌乱的样式代码.我知道这些并没有被完全隐藏,但我已经使用Scala几个月而没有真正理解它们的力量.对于(很长)的例子,我可以替换:

val code: String = ...
val ps: ProductService = ...
var p: Product = null
if (code.endsWith("=")) {
  p = ps.findCash(code.substring(0, 3)) //e.g. USD=, GBP= etc
}
else if (code.endsWith(".FWD")) {
  //e.g. GBP20090625.FWD
  p = ps.findForward(code.substring(0,3), code.substring(3, 9))
}
else {
  p = ps.lookupProductByRic(code)
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我认为这更加清晰

implicit val ps: ProductService = ...
val p = code match {
  case SyntheticCodes.Cash(c) => c
  case SyntheticCodes.Forward(f) => f
  case _ => ps.lookupProductByRic(code)
}
Run Code Online (Sandbox Code Playgroud)

我必须在后台做一些腿部工作......

object SyntheticCodes {
  // Synthetic Code for a CashProduct
  object Cash extends (CashProduct => String) {
    def apply(p: CashProduct) = p.currency.name + "="

    //EXTRACTOR
    def unapply(s: String)(implicit ps: ProductService): Option[CashProduct] = {
      if (s.endsWith("=") 
        Some(ps.findCash(s.substring(0,3))) 
      else None
    }
  }
  //Synthetic Code for a ForwardProduct
  object Forward extends (ForwardProduct => String) {
    def apply(p: ForwardProduct) = p.currency.name + p.date.toString + ".FWD"

    //EXTRACTOR
    def unapply(s: String)(implicit ps: ProductService): Option[ForwardProduct] = {
      if (s.endsWith(".FWD") 
        Some(ps.findForward(s.substring(0,3), s.substring(3, 9)) 
      else None
    }
  }
Run Code Online (Sandbox Code Playgroud)

但是,由于它将一块业务逻辑分离到一个合理的位置,所以腿部工作是值得的.我可以Product.getCode按如下方式实现我的方法..

class CashProduct {
  def getCode = SyntheticCodes.Cash(this)
}

class ForwardProduct {
  def getCode = SyntheticCodes.Forward(this)     
}
Run Code Online (Sandbox Code Playgroud)

  • 模式就像涡轮增压开关:功能更强大,更清晰 (14认同)

oxb*_*kes 35

清单,它是在运行时获取类型信息的一种方式,就像Scala已经实现类型一样.

  • 我认为最好在答案中解释答案,而不是指一个链接.顺便说一句,嗨agai牛轭!:-) (8认同)

Aym*_*men 35

在scala 2.8中,您可以使用包scala.util.control.TailCalls(事实上它是蹦床)来使用尾递归方法.

一个例子:

def u(n:Int):TailRec[Int] = {
  if (n==0) done(1)
  else tailcall(v(n/2))
}
def v(n:Int):TailRec[Int] = {
  if (n==0) done(5)
  else tailcall(u(n-1))
}
val l=for(n<-0 to 5) yield (n,u(n).result,v(n).result)
println(l)
Run Code Online (Sandbox Code Playgroud)


Aar*_*rup 35

案例类自动混合Product trait,提供对字段的无类型索引访问,无需任何反射:

case class Person(name: String, age: Int)

val p = Person("Aaron", 28)
val name = p.productElement(0) // name = "Aaron": Any
val age = p.productElement(1) // age = 28: Any
val fields = p.productIterator.toList // fields = List[Any]("Aaron", 28)
Run Code Online (Sandbox Code Playgroud)

此功能还提供了一种更改toString方法输出的简化方法:

case class Person(name: String, age: Int) {
   override def productPrefix = "person: "
}

// prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
println(Person("Aaron", 28)) 
Run Code Online (Sandbox Code Playgroud)


ped*_*rla 32

它并没有完全隐藏,但肯定是一个宣传不足的功能:scalac -Xprint.

作为使用说明,请考虑以下来源:

class A { "xx".r }
Run Code Online (Sandbox Code Playgroud)

使用scalac -Xprint:typer输出编译它:

package <empty> {
  class A extends java.lang.Object with ScalaObject {
    def this(): A = {
      A.super.this();
      ()
    };
    scala.this.Predef.augmentString("xx").r
  }
}
Run Code Online (Sandbox Code Playgroud)

注意scala.this.Predef.augmentString("xx").r,这是implicit def augmentStringPredef.scala中现在的应用.

scalac -Xprint:<phase>将在一些编译阶段后打印语法树.要查看可用的阶段,请使用scalac -Xshow-phases.

这是了解幕后发生的事情的好方法.

试试吧

case class X(a:Int,b:String)

使用typer阶段来真正感受它是多么有用.


Ale*_*tec 30

您可以定义自己的控制结构.它实际上只是函数和对象以及一些语法糖,但它们看起来和行为都像真实的东西.

例如,以下代码定义dont {...} unless (cond)dont {...} until (cond):

def dont(code: => Unit) = new DontCommand(code)

class DontCommand(code: => Unit) {
  def unless(condition: => Boolean) =
    if (condition) code

  def until(condition: => Boolean) = {
    while (!condition) {}
    code
  }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以执行以下操作:

/* This will only get executed if the condition is true */
dont {
  println("Yep, 2 really is greater than 1.")
} unless (2 > 1) 

/* Just a helper function */
var number = 0;
def nextNumber() = {
  number += 1
  println(number)
  number
}

/* This will not be printed until the condition is met. */
dont {
  println("Done counting to 5!")
} until (nextNumber() == 5) 
Run Code Online (Sandbox Code Playgroud)


mis*_*tor 26

@switch Scala 2.8中的注释:

要应用于匹配表达式的注释.如果存在,编译器将验证匹配是否已编译为tableswitch或lookupswitch,如果它编译为一系列条件表达式,则发出错误.

例:

scala> val n = 3
n: Int = 3

scala> import annotation.switch
import annotation.switch

scala> val s = (n: @switch) match {
     |   case 3 => "Three"
     |   case _ => "NoThree"
     | }
<console>:6: error: could not emit switch for @switch annotated match
       val s = (n: @switch) match {
Run Code Online (Sandbox Code Playgroud)


rai*_*hoo 26

Dunno,如果这真的是隐藏的,但我发现它非常好.

采用2种类型参数的类型构造函数可以用中缀表示法编写

object Main {                                                                   
  class FooBar[A, B]

  def main(args: Array[String]): Unit = {
    var x: FooBar[Int, BigInt] = null
    var y: Int FooBar BigInt   = null
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我有时会在某种程度上使用PartialFunction的代码中执行此操作:type~> [A,B] = PartialFunction [A,B] (4认同)

Wil*_*urn 24

Scala 2.8引入了默认和命名参数,这使得Scala添加到案例类的新"复制"方法成为可能.如果你定义这个:

case class Foo(a: Int, b: Int, c: Int, ... z:Int)
Run Code Online (Sandbox Code Playgroud)

并且你想创建一个新的Foo,就像现有的Foo,只有不同的"n"值,那么你可以说:

foo.copy(n = 3)
Run Code Online (Sandbox Code Playgroud)

  • 案例类不再(Scala 2.8)允许从案例类继承.感谢Scala之王弃用这个邪恶的继承. (5认同)
  • 警告:如果从另一个案例类继承一个案例类,则不会覆盖复制方法.所以你必须手动覆盖它 (3认同)

Aym*_*men 24

在scala 2.8中,您可以将@specialized添加到您的泛型类/方法中.这将为原始类型(扩展AnyVal)创建类的特殊版本,并节省不必要的装箱/拆箱的成本: class Foo[@specialized T]...

您可以选择AnyVals的子集: class Foo[@specialized(Int,Boolean) T]...


agi*_*all 23

你可以为函数指定一个按名称调用参数(EDITED:这与一个惰性参数不同!),直到函数使用它才会被评估(编辑:事实上,它每次都会被重新评估)用过的).有关详细信息,请参阅此faq

class Bar(i:Int) {
    println("constructing bar " + i)
    override def toString():String = {
        "bar with value: " + i
    }
}

// NOTE the => in the method declaration.  It indicates a lazy paramter
def foo(x: => Bar) = {
    println("foo called")
    println("bar: " + x)
}


foo(new Bar(22))

/*
prints the following:
foo called
constructing bar 22
bar with value: 22
*/
Run Code Online (Sandbox Code Playgroud)

  • 您显示的是按名称调用参数.懒惰参数尚未实现:https://lampsvn.epfl.ch/trac/scala/ticket/240 (3认同)

Adr*_*ian 23

扩展语言.我一直想在Java中做这样的事情(不可能).但在斯卡拉我可以:

  def timed[T](thunk: => T) = {
    val t1 = System.nanoTime
    val ret = thunk
    val time = System.nanoTime - t1
    println("Executed in: " + time/1000000.0 + " millisec")
    ret
  }
Run Code Online (Sandbox Code Playgroud)

然后写:

val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
val sorted = timed {   // "timed" is a new "keyword"!
  numbers.sortWith(_<_)
}
println(sorted)
Run Code Online (Sandbox Code Playgroud)

得到

Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)
Run Code Online (Sandbox Code Playgroud)


mis*_*tor 20

您可以使用locally引入本地块而不会导致分号推断问题.

用法:

scala> case class Dog(name: String) {
     |   def bark() {
     |     println("Bow Vow")
     |   }
     | }
defined class Dog

scala> val d = Dog("Barnie")
d: Dog = Dog(Barnie)

scala> locally {
     |   import d._
     |   bark()
     |   bark()
     | }
Bow Vow
Bow Vow
Run Code Online (Sandbox Code Playgroud)

locally 在"Predef.scala"中定义为:

@inline def locally[T](x: T): T = x
Run Code Online (Sandbox Code Playgroud)

作为内联,它不会产生任何额外的开销.

  • 这可以在http://stackoverflow.com/questions/3237727/what-does-predef-locally-do-and-how-is-it-different-from-predef-identity更好地解释 (3认同)

Eug*_*ota 17

匿名函数的占位符语法

来自Scala语言规范:

SimpleExpr1 ::= '_'
Run Code Online (Sandbox Code Playgroud)

表达式(语法类别Expr)可以_在标识符合法的位置包含嵌入的下划线符号.这样的表达式表示匿名函数,其中下划线的后续出现表示连续的参数.

来自Scala语言更改:

_ + 1                  x => x + 1
_ * _                  (x1, x2) => x1 * x2
(_: Int) * 2           (x: Int) => x * 2
if (_) x else y        z => if (z) x else y
_.map(f)               x => x.map(f)
_.map(_ + 1)           x => x.map(y => y + 1)
Run Code Online (Sandbox Code Playgroud)

使用它你可以做类似的事情:

def filesEnding(query: String) =
  filesMatching(_.endsWith(query))
Run Code Online (Sandbox Code Playgroud)

  • 这应该被称为"匿名函数的占位符语法".隐式在Scala中具有明显的含义,并且与此无关. (2认同)
  • 它不是真正的"隐藏",我已经在Scala的几乎所有教程中看到了这种用法我已经阅读了...... :-)但是我很欣赏我还没有看到的正式定义. (2认同)

mis*_*tor 17

早期初始化:

trait AbstractT2 {
  println("In AbstractT2:")
  val value: Int
  val inverse = 1.0/value
  println("AbstractT2: value = "+value+", inverse = "+inverse)
}

val c2c = new {
  // Only initializations are allowed in pre-init. blocks.
  // println("In c2c:")
  val value = 10
} with AbstractT2

println("c2c.value = "+c2c.value+", inverse = "+c2c.inverse)
Run Code Online (Sandbox Code Playgroud)

输出:

In AbstractT2:  
AbstractT2: value = 10, inverse = 0.1  
c2c.value = 10, inverse = 0.1
Run Code Online (Sandbox Code Playgroud)

我们实例化一个匿名内部类,valuewith AbstractT2子句之前初始化块中的字段.这保证了value在执行主体之前初始化AbstractT2,如运行脚本时所示.


rai*_*hoo 17

您可以使用'with'关键字组合结构类型

object Main {
  type A = {def foo: Unit}
  type B = {def bar: Unit}

  type C = A with B

  class myA {
    def foo: Unit = println("myA.foo")
  }


  class myB {
    def bar: Unit = println("myB.bar")
  }
  class myC extends myB {
    def foo: Unit = println("myC.foo")
  }

  def main(args: Array[String]): Unit = { 
    val a: A = new myA 
    a.foo
    val b: C = new myC 
    b.bar
    b.foo
  }
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*ral 16

隐含定义,尤其是转换.

例如,假设一个函数将输入字符串格式化为适合大小,通过用"..."替换它的中间:

def sizeBoundedString(s: String, n: Int): String = {
  if (n < 5 && n < s.length) throw new IllegalArgumentException
  if (s.length > n) {
    val trailLength = ((n - 3) / 2) min 3
    val headLength = n - 3 - trailLength
    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
  } else s
}
Run Code Online (Sandbox Code Playgroud)

您可以将它与任何String一起使用,当然,也可以使用toString方法转换任何内容.但你也可以像这样写:

def sizeBoundedString[T](s: T, n: Int)(implicit toStr: T => String): String = {
  if (n < 5 && n < s.length) throw new IllegalArgumentException
  if (s.length > n) {
    val trailLength = ((n - 3) / 2) min 3
    val headLength = n - 3 - trailLength
    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
  } else s
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过执行以下操作传递其他类型的类:

implicit def double2String(d: Double) = d.toString
Run Code Online (Sandbox Code Playgroud)

现在你可以调用该函数传递一个double:

sizeBoundedString(12345.12345D, 8)
Run Code Online (Sandbox Code Playgroud)

最后一个参数是隐式的,并且由于隐式de声明而被自动传递.此外,"s"被视为 sizeBoundedString中的String,因为它有一个从它到String的隐式转换.

对于不常见的类型,可以更好地定义此类型的隐含,以避免意外转换.你也可以明确地传递一个转换,它仍然会在sizeBoundedString中隐式使用:

sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)
Run Code Online (Sandbox Code Playgroud)

您也可以有多个隐式参数,但是您必须传递所有这些参数,或者不传递任何参数.隐式转换还有一种快捷语法:

def sizeBoundedString[T <% String](s: T, n: Int): String = {
  if (n < 5 && n < s.length) throw new IllegalArgumentException
  if (s.length > n) {
    val trailLength = ((n - 3) / 2) min 3
    val headLength = n - 3 - trailLength
    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
  } else s
}
Run Code Online (Sandbox Code Playgroud)

这使用方式完全相同.

Implicits可以有任何价值.例如,它们可用于隐藏库信息.以下面的示例为例:

case class Daemon(name: String) {
  def log(msg: String) = println(name+": "+msg)
}

object DefaultDaemon extends Daemon("Default")

trait Logger {
  private var logd: Option[Daemon] = None
  implicit def daemon: Daemon = logd getOrElse DefaultDaemon

  def logTo(daemon: Daemon) = 
    if (logd == None) logd = Some(daemon) 
    else throw new IllegalArgumentException

  def log(msg: String)(implicit daemon: Daemon) = daemon.log(msg)
}

class X extends Logger {
  logTo(Daemon("X Daemon"))

  def f = {
    log("f called")
    println("Stuff")
  }

  def g = {
    log("g called")(DefaultDaemon)
  }
}

class Y extends Logger {
  def f = {
    log("f called")
    println("Stuff")
  }
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,在Y对象中调用"f"将日志发送到默认守护程序,并在X的实例上发送到守护程序X守护程序.但是在X的实例上调用g会将日志发送到明确给定的DefaultDaemon.

虽然这个简单的例子可以用重载和私有状态重写,但implicits不需要私有状态,并且可以通过导入进入上下文.


agi*_*all 13

也许不是太隐蔽,但我认为这很有用:

@scala.reflect.BeanProperty
var firstName:String = _
Run Code Online (Sandbox Code Playgroud)

这将自动为与bean约定匹配的字段生成getter和setter.

developerworks上的进一步描述

  • 如果你经常使用它,你可以为它创建快捷方式,例如:import scala.reflect.{BeanProperty => BP} (6认同)

axe*_*l22 13

闭包中的隐含参数.

函数参数可以像方法一样标记为隐式.在函数体内的范围内,隐式参数是可见的,并且有资格进行隐式解析:

trait Foo { def bar }

trait Base {
  def callBar(implicit foo: Foo) = foo.bar
}

object Test extends Base {
  val f: Foo => Unit = { implicit foo =>
    callBar
  }
  def test = f(new Foo {
    def bar = println("Hello")
  })
}
Run Code Online (Sandbox Code Playgroud)


jsu*_*eth 12

结果类型取决于隐式解析.这可以为您提供多种发送方式:

scala> trait PerformFunc[A,B] { def perform(a : A) : B }
defined trait PerformFunc

scala> implicit val stringToInt = new PerformFunc[String,Int] {
  def perform(a : String)  = 5
}
stringToInt: java.lang.Object with PerformFunc[String,Int] = $anon$1@13ccf137

scala> implicit val intToDouble = new PerformFunc[Int,Double] {
  def perform(a : Int) = 1.0
}
intToDouble: java.lang.Object with PerformFunc[Int,Double] = $anon$1@74e551a4

scala> def foo[A, B](x : A)(implicit z : PerformFunc[A,B]) : B = z.perform(x)
foo: [A,B](x: A)(implicit z: PerformFunc[A,B])B

scala> foo("HAI")
res16: Int = 5

scala> foo(1)
res17: Double = 1.0
Run Code Online (Sandbox Code Playgroud)