คุณสมบัติที่ซ่อนอยู่ของสกาล่า


149

อะไรคือคุณสมบัติที่ซ่อนอยู่ของ Scala ที่นักพัฒนา Scala ทุกคนควรระวัง

กรุณาซ่อนคุณสมบัติหนึ่งข้อต่อคำตอบ


6
เฮ้คำถามนี้มีประโยชน์สำหรับการเชื่อมโยงไปยังคุณสมบัติที่ซ่อนอยู่อื่น ๆ โพสต์เช่นเดียวกับคำถามของตัวเอง ไชโย!
JohnMetta

1
@ettadore เพียงแค่ดู ลิงค์ที่เกี่ยวข้องทางด้านขวา
Daniel C. Sobral

2
@JohnMetta: หรือใช้แท็ก

คำตอบ:


85

โอเคฉันต้องเพิ่มอีกหนึ่งรายการ Regexวัตถุทุกชิ้นใน Scala มีตัวแยกข้อมูล (ดูคำตอบจาก 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"

บรรทัดที่สองดูสับสนหากคุณไม่คุ้นเคยกับการใช้การจับคู่รูปแบบและตัวแยกไฟล์ เมื่อใดก็ตามที่คุณกำหนดvalหรือvarสิ่งที่เกิดขึ้นหลังจากคำหลักนั้นไม่ได้เป็นเพียงตัวระบุ แต่เป็นรูปแบบ นั่นเป็นเหตุผลที่ทำงานนี้:

val (a, b, c) = (1, 3.14159, "Hello, world")

การแสดงออกทางด้านขวามือสร้างซึ่งสามารถตรงกับรูปแบบTuple3[Int, Double, String](a, b, c)

ส่วนใหญ่รูปแบบของคุณใช้ตัวแยกที่เป็นสมาชิกของวัตถุเดี่ยว ตัวอย่างเช่นถ้าคุณเขียนรูปแบบเช่น

Some(value)

Some.unapplyแล้วคุณโดยปริยายเรียกระบาย

แต่คุณยังสามารถใช้คลาสอินสแตนซ์ในรูปแบบและนั่นคือสิ่งที่เกิดขึ้นที่นี่ val regex เป็นตัวอย่างของRegexและเมื่อคุณใช้ในรูปแบบคุณโทรโดยปริยายregex.unapplySeq( unapplyแทนที่จะunapplySeqอยู่นอกเหนือขอบเขตของคำตอบนี้) ซึ่งแยกกลุ่มการจับคู่เป็นSeq[String]องค์ประกอบที่ได้รับมอบหมายเพื่อ ตัวแปรปีเดือนและวัน


1
ขอบคุณสำหรับการโพสต์นี้! FYI มีการกล่าวถึงในบท "แยกด้วยนิพจน์ทั่วไป" ในหนังสือ "Programming in Scala" ในหน้า 503 ในรุ่นแรกและหน้า 611 ในรุ่นที่สอง
มนุษย์โลกพอล

51

นิยามประเภทโครงสร้าง - เช่นชนิดที่อธิบายโดยวิธีการที่รองรับ ตัวอย่างเช่น:

object Closer {
    def using(closeable: { def close(): Unit }, f: => Unit) {
      try { 
        f
      } finally { closeable.close }
    }
}

ขอให้สังเกตว่าประเภทของพารามิเตอร์closeableไม่ได้กำหนดไว้นอกจากที่มีcloseวิธีการ


1
ประเภทโครงสร้างไม่ได้กล่าวถึงใน "การเขียนโปรแกรมใน Scala" มันช้ากว่าเทคนิคอื่น ๆ เล็กน้อยสำหรับการส่งผ่านแม้ว่าจะใช้การสะท้อนเพื่อเรียกใช้วิธีการที่เหมาะสม (หวังว่าพวกเขาจะได้มาด้วยวิธีการที่จะเพิ่มความเร็วขึ้น.)
เคนบลูม

1
และยังมีความเป็นไปได้ที่จะสร้างนามแฝงสำหรับพวกเขาสิ่งที่ทำงานเหมือนอินเทอร์เฟซที่ได้รับมอบหมายจากภายนอก (ช้ามาก): ประเภท Closeable = {def close (): หน่วย}
Alexey

45

ความแตกต่างของตัวสร้างประเภท (ประเภทอาคาที่สูงกว่า)

ยกตัวอย่างเช่นหากไม่มีฟีเจอร์นี้คุณสามารถแสดงความคิดในการทำแผนที่ฟังก์ชั่นเหนือรายการเพื่อส่งคืนรายการอื่นหรือทำแผนที่ฟังก์ชั่นบนต้นไม้เพื่อส่งคืนต้นไม้อื่น แต่คุณไม่สามารถแสดงความคิดเห็นนี้โดยทั่วไปหากไม่มีประเภทที่สูงขึ้น

ด้วยชนิดที่สูงกว่าคุณสามารถจับภาพความคิดประเภทใด ๆที่กำหนดพารามิเตอร์ด้วยประเภทอื่น (*->*)ตัวสร้างประเภทที่ใช้พารามิเตอร์หนึ่งจะกล่าวว่าเป็นของชนิด ตัวอย่างเช่นList. (*->*->*)ตัวสร้างประเภทที่ส่งกลับประเภทคอนสตรัคอื่นจะกล่าวว่าเป็นของชนิด ตัวอย่างเช่นFunction1. แต่ใน Scala เรามีชนิดที่สูงกว่าดังนั้นเราจึงสามารถมีตัวสร้างประเภทที่มีพารามิเตอร์กับตัวสร้างชนิดอื่น ((*->*)->*)ดังนั้นพวกเขากำลังชนิดเช่น

ตัวอย่างเช่น:

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

ตอนนี้ถ้าคุณมีFunctor[List]คุณสามารถแมปผ่านรายการต่างๆ หากคุณมีFunctor[Tree]คุณสามารถแผนที่เหนือต้นไม้ แต่ที่สำคัญกว่านั้นคือถ้าคุณมีFunctor[A] ชนิด A(*->*)คุณสามารถแมปฟังก์ชั่นAได้


39

Extractorsซึ่งอนุญาตให้คุณแทนที่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)
}

ด้วยสิ่งนี้ซึ่งชัดเจนมากในความคิดของฉัน

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

ฉันต้องทำ legwork เล็กน้อยในพื้นหลัง ...

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
    }
  }

แต่ legwork นั้นมีค่าสำหรับความจริงที่ว่ามันแยกตรรกะทางธุรกิจออกเป็นส่วน ๆ ฉันสามารถใช้Product.getCodeวิธีการของฉันดังต่อไปนี้ ..

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

class ForwardProduct {
  def getCode = SyntheticCodes.Forward(this)     
}

นี่ไม่ใช่สวิตช์หรอกเหรอ? บางทีนี่อาจได้รับการปรับปรุงใหม่อีกครั้ง
Geo

14
รูปแบบเหมือนสวิตช์ที่ชาร์จด้วยเทอร์โบ: ทรงพลังและชัดเจนกว่ามาก
oxbow_lakes

1
ดีมาก แต่ฉันไม่ชอบที่คุณต้องใช้งานโดยปริยายเพราะขอบเขตนั้นเกินกว่าการแข่งขัน {} คุณยังสามารถเพิ่มวิธีการใน ProductService ที่ค้นหาผลิตภัณฑ์ตามรหัส คุณจะทำการตัดตัวอย่างข้อมูลที่มีการปรับโครงสร้างใหม่ด้วยวิธีใด ๆ เพื่อใช้งานได้ทุกที่
Martin Konicek

35

รายการที่เป็นวิธีในการรับข้อมูลชนิดที่รันไทม์ราวกับว่า Scala มีประเภท reified


8
ฉันคิดว่าเป็นการดีกว่าที่จะอธิบายคำตอบในคำตอบแทนที่จะอ้างถึงลิงก์ อย่างไรก็ตามสวัสดี agai oxbow! :-)
Daniel C. Sobral

นี่เป็นคุณลักษณะที่ซ่อนอยู่อย่างแท้จริง ... ไม่ได้อยู่ในเอกสาร API มีประโยชน์มากแม้ว่า
André Laszlo

35

ใน scala 2.8 คุณสามารถมีวิธีการแบบวนซ้ำโดยใช้แพ็คเกจ scala.util.control.TailCalls (อันที่จริงมันเป็น trampolining)

ตัวอย่าง:

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)

35

คลาสเคสจะผสมคุณลักษณะของผลิตภัณฑ์โดยอัตโนมัติโดยให้การเข้าถึงดัชนีที่ไม่มีการทำดัชนีในฟิลด์โดยไม่มีการสะท้อนใด ๆ :

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)

คุณลักษณะนี้ยังให้วิธีที่ง่ายขึ้นในการแก้ไขผลลัพธ์ของ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)) 

32

มันไม่ได้ซ่อนว่า แต่แน่นอนภายใต้คุณลักษณะโฆษณา: scalac -Xprint

ตามภาพประกอบการใช้งานให้พิจารณาแหล่งข้อมูลต่อไปนี้:

class A { "xx".r }

รวบรวมสิ่งนี้ด้วย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
  }
}

คำบอกกล่าวscala.this.Predef.augmentString("xx").rซึ่งเป็นimplicit def augmentStringการนำเสนอใน Predef.scala

scalac -Xprint: <phase>จะพิมพ์แผนผังไวยากรณ์หลังจากคอมไพเลอร์เฟส หากต้องการดูขั้นตอนที่มีอยู่ใช้scalac -Xshow

นี่เป็นวิธีที่ดีในการเรียนรู้สิ่งที่เกิดขึ้นเบื้องหลัง

ลองด้วย

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

การใช้เฟสtyperเพื่อให้รู้สึกว่ามีประโยชน์จริงๆ


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
  }
}

ตอนนี้คุณสามารถทำสิ่งต่อไปนี้:

/* 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) 

ตัวอย่างเพิ่มเติมได้ที่นี่: programmers.stackexchange.com/questions/13072/…
missingfaktor

ฉันอยากรู้อยากเห็นถ้ามีคนรู้วิธีกำหนดบล็อก if-then-else ด้วยตัวเลือกอื่นที่ตรวจสอบประเภทเหมือนบล็อกมาตรฐาน
ฟิลิปป์

zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero@Philippe: ต้องการ Scalaz
missingfaktor

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 {

26

Dunno ถ้านี่ถูกซ่อนอยู่จริงๆ แต่ฉันคิดว่ามันค่อนข้างดี

Typeconstructors ที่ใช้พารามิเตอร์ 2 ชนิดสามารถเขียนได้ในรูปแบบของ infix

object Main {                                                                   
  class FooBar[A, B]

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

1
ดี! ฉันสามารถจินตนาการได้ว่าบางครั้งการมีประโยชน์ในการปรับปรุงความสามารถในการอ่าน ตัวอย่างเช่นvar foo2barConverter: Foo ConvertTo Barจะทำให้ลำดับของพารามิเตอร์ประเภทตนเองชัดเจน
Esko Luontola

4
บางครั้งฉันทำสิ่งนี้ในโค้ดที่ใช้ PartialFunction ในระดับหนึ่ง: พิมพ์ ~> [A, B] = PartialFunction [A, B]
raichoo

24

Scala 2.8 นำเสนอค่าเริ่มต้นและอาร์กิวเมนต์ที่ตั้งชื่อซึ่งทำให้มีความเป็นไปได้ที่จะเพิ่มวิธีการ "คัดลอก" ใหม่ที่ Scala เพิ่มให้กับคลาสเคส หากคุณกำหนดสิ่งนี้:

case class Foo(a: Int, b: Int, c: Int, ... z:Int)

และคุณต้องการสร้าง Foo ใหม่ที่เหมือนกับ Foo ที่มีอยู่แล้วเท่านั้นที่มีค่า "n" ที่แตกต่างจากนั้นคุณสามารถพูดได้ว่า:

foo.copy(n = 3)

3
คำเตือน: วิธีการคัดลอกจะไม่ถูกแทนที่ถ้าคุณรับคลาสเคสหนึ่งจากคลาสอื่น ดังนั้นคุณต้องแทนที่มันเอง
Alexey

ที่เกี่ยวข้อง: วิธีทำความสะอาดเพื่ออัปเดตโครงสร้างซ้อนกันstackoverflow.com/q/3900307/203968
oluies

5
คลาสเคสไม่ได้รับอนุญาต (Scala 2.8) อีกต่อไปเพื่อสืบทอดจากคลาสเคส ขอบคุณพระเจ้าแห่งสกาลาที่สละมรดกที่ไม่บริสุทธิ์นี้
olle kullberg

24

ใน scala 2.8 คุณสามารถเพิ่ม @specialized ให้กับคลาส / วิธีการทั่วไปของคุณได้ สิ่งนี้จะสร้างรุ่นพิเศษของคลาสสำหรับประเภทดั้งเดิม (ขยาย AnyVal) และประหยัดค่าใช้จ่ายในการชกมวย / การยกเลิกการ Unbox ที่ไม่จำเป็น: class Foo[@specialized T]...

คุณสามารถเลือกชุดย่อยของ AnyVals: class Foo[@specialized(Int,Boolean) T]...


1
มีคำอธิบายอีกต่อไปที่คุณสามารถชี้ให้ฉันเห็น ฉันต้องการเรียนรู้เพิ่มเติม
PawełPrażak

23

การขยายภาษา ฉันอยากทำบางสิ่งเช่นนี้ใน Java (ทำไม่ได้) แต่ใน Scala ฉันสามารถมี:

  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
  }

แล้วเขียน:

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

และรับ

Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)

23

คุณสามารถกำหนดพารามิเตอร์ call-by-name (แก้ไข: นี่แตกต่างจากพารามิเตอร์ที่ขี้เกียจ!) ไปยังฟังก์ชั่นและจะไม่ได้รับการประเมินจนกว่าจะใช้โดยฟังก์ชั่น (แก้ไข: จริง ๆ แล้วมันจะถูกประเมินค่าใหม่ทุกครั้ง ใช้) ดูคำถามที่พบบ่อยนี้สำหรับรายละเอียด

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
*/

ฉันคิดว่า "x: => Bar" หมายความว่า x เป็นฟังก์ชันที่ไม่ใช้พารามิเตอร์และส่งคืน Bar ดังนั้น "แถบใหม่ (22)" เป็นเพียงฟังก์ชั่นที่ไม่ระบุชื่อและได้รับการประเมินว่าเป็นฟังก์ชั่นที่เหมือนกับฟังก์ชั่นอื่น ๆ
Alex Black

1
"x: () => Bar" กำหนดฟังก์ชั่น xa ที่ไม่มีพารามิเตอร์และส่งคืนแถบ x: => บาร์กำหนด x เป็นการโทรตามชื่อ ลองดูที่scala.sygneca.com/faqs/...สำหรับรายละเอียดเพิ่มเติม
agilefall

3
สิ่งที่คุณแสดงคือพารามิเตอร์การโทรตามชื่อ ยังไม่ได้ใช้พารามิเตอร์ Lazy: lampsvn.epfl.ch/trac/scala/ticket/240
ArtemGr

ผมคิดว่าคุณสามารถใช้มันเป็นพระรามขี้เกียจถ้าคุณทำสิ่งที่ชอบในวิธีการของคุณและจากช่วงเวลาที่คุณใช้งานเท่านั้นlazy val xx: Bar = x xx
Cristian Vrabie

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

locally ถูกกำหนดใน "Predef.scala" เป็น:

@inline def locally[T](x: T): T = x

เป็นแบบอินไลน์จะไม่กำหนดค่าใช้จ่ายเพิ่มเติมใด ๆ


3
นี่คือคำอธิบายที่ดีกว่าที่stackoverflow.com/questions/3237727/…
Esko Luontola

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)

เอาท์พุท:

In AbstractT2:  
AbstractT2: value = 10, inverse = 0.1  
c2c.value = 10, inverse = 0.1

เรายกตัวอย่างคลาสภายในที่ไม่ระบุชื่อเริ่มต้นvalueฟิลด์ในบล็อกก่อนส่วนwith AbstractT2คำสั่ง สิ่งนี้รับประกันว่าvalueจะเริ่มต้นได้ก่อนที่เนื้อความของAbstractT2จะถูกดำเนินการตามที่แสดงเมื่อคุณเรียกใช้สคริปต์


1
สิ่งก่อสร้างเรียกว่า "การเริ่มต้นก่อนกำหนด"
Randall Schulz

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
  }
}

17

ไวยากรณ์ตัวยึดตำแหน่งสำหรับฟังก์ชั่นที่ไม่ระบุชื่อ

จากข้อกำหนดภาษา Scala:

SimpleExpr1 ::= '_'

นิพจน์ (ของหมวดหมู่ประโยค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)

เมื่อใช้สิ่งนี้คุณสามารถทำสิ่งต่อไปนี้:

def filesEnding(query: String) =
  filesMatching(_.endsWith(query))

2
สิ่งนี้ควรเรียกว่า 'ตัวยึดตำแหน่งสำหรับฟังก์ชันที่ไม่ระบุชื่อ' โดยนัยมีความหมายที่ชัดเจนใน Scala และมันไม่เกี่ยวข้องกับเรื่องนี้
retronym

ลิงก์มีความสัมพันธ์ที่ไม่ชัดเจนกับคำตอบ "นัย" ไม่ใช่คำที่ถูกต้องสำหรับสิ่งนี้ ดังกล่าวควรเป็น "ตัวยึด"
Alain O'Dea

2
มันไม่ได้ "ซ่อน" จริงๆฉันได้เห็นการใช้งานนี้ในแบบฝึกหัดเกือบทุกเรื่องเกี่ยวกับสกาล่าฉันได้อ่าน ... :-) แต่ฉันขอขอบคุณคำจำกัดความที่เป็นทางการที่ฉันยังไม่เคยเห็น
PhiLho

@PhiLho อาจเป็นที่รู้จักน้อยกว่าในปี 2009 ฉันไม่รู้
Eugene Yokota

ฉันพลาดวันที่ดั้งเดิมเนื่องจากแสดงเฉพาะวันที่แก้ไขล่าสุดเท่านั้น และคุณสมบัติบางอย่างที่อธิบายไว้ในชุดข้อความนี้ไม่ใช่ "ซ่อนอยู่" เธรดเย็นและคำตอบที่ดีอยู่แล้ว
PhiLho

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
}

คุณสามารถใช้สิ่งนั้นกับสตริงใด ๆ และแน่นอนใช้เมธอด 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
}

จากนั้นคุณสามารถผ่านคลาสอื่น ๆ โดยทำสิ่งนี้:

implicit def double2String(d: Double) = d.toString

ตอนนี้คุณสามารถเรียกใช้ฟังก์ชันที่ผ่านสองครั้ง:

sizeBoundedString(12345.12345D, 8)

อาร์กิวเมนต์สุดท้ายคือ implicit และถูกส่งโดยอัตโนมัติเนื่องจากการประกาศ de implicit นอกจากนี้ "s" กำลังได้รับการปฏิบัติเหมือนสตริงภายใน sizeBoundedString เนื่องจากมีการแปลงโดยนัยจากมันเป็นสตริง

การระบุประเภทนี้มีการกำหนดที่ดีกว่าสำหรับประเภทที่ผิดปกติเพื่อหลีกเลี่ยงการแปลงที่ไม่คาดคิด คุณสามารถส่งผ่านการแปลงได้อย่างชัดเจนและจะยังคงถูกใช้ภายใน sizeBoundedString:

sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)

คุณสามารถมีข้อโต้แย้งโดยนัยได้หลายข้อ แต่จากนั้นคุณต้องส่งผ่านข้อโต้แย้งทั้งหมดหรือไม่ผ่านข้อใดข้อหนึ่ง นอกจากนี้ยังมีไวยากรณ์ทางลัดสำหรับการแปลงโดยนัย:

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
}

นี่ใช้วิธีเดียวกันทั้งหมด

การนัยสามารถมีค่าใด ๆ ตัวอย่างเช่นสามารถใช้เพื่อซ่อนข้อมูลไลบรารี ยกตัวอย่างเช่น

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")
  }
}

ในตัวอย่างนี้การเรียก "f" ในวัตถุ Y จะส่งบันทึกไปยัง daemon เริ่มต้นและบนอินสแตนซ์ของ X ไปยัง Daemon X daemon แต่การเรียก g บนอินสแตนซ์ของ X จะส่งบันทึกไปยัง DefaultDaemon ที่กำหนดไว้อย่างชัดเจน

ในขณะที่ตัวอย่างง่ายๆนี้สามารถเขียนใหม่ด้วย overload และสถานะส่วนตัว implicits ไม่ต้องการสถานะส่วนตัวและสามารถนำเข้าบริบทกับการนำเข้า


13

อาจจะไม่ซ่อนเร้น แต่ฉันคิดว่ามันมีประโยชน์:

@scala.reflect.BeanProperty
var firstName:String = _

สิ่งนี้จะสร้างตัวเรียกและตัวตั้งค่าสำหรับฟิลด์ที่ตรงกับการประชุม bean โดยอัตโนมัติ

คำอธิบายเพิ่มเติมที่developerworks


6
และคุณสามารถสร้างทางลัดได้ถ้าคุณใช้บ่อยๆเช่น: import scala.reflect {BeanProperty => BP}
Alexey

13

ข้อโต้แย้งโดยนัยในการปิด

อาร์กิวเมนต์ของฟังก์ชันสามารถทำเครื่องหมายเป็นนัยได้เช่นเดียวกับเมธอด ภายในขอบเขตของเนื้อความของฟังก์ชันพารามิเตอร์ implicit สามารถมองเห็นและมีสิทธิ์ได้รับการแก้ไขโดยนัย:

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")
  })
}


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

อาจเป็นเช่นนั้น แต่เซสชันด้านบนทำให้เข้าใจผิด คำจำกัดความของการfooใช้งานaซึ่งจะต้องมีอยู่ในสภาพแวดล้อมก่อนที่จะดำเนินการตามคำสั่งเหล่านี้ z.perform(x)ผมถือว่าคุณหมาย
Daniel C. Sobral

4

Scala เทียบเท่ากับ Java initializer วงเล็บปีกกา

Scala ช่วยให้คุณสร้างคลาสย่อยแบบไม่ระบุชื่อด้วยเนื้อความของคลาส (ตัวสร้าง) ที่มีคำสั่งเพื่อเริ่มต้นอินสแตนซ์ของคลาสนั้น

รูปแบบนี้มีประโยชน์มากเมื่อสร้างส่วนต่อประสานผู้ใช้ที่เป็นส่วนประกอบ (เช่น Swing, Vaadin) เนื่องจากช่วยให้สามารถสร้างส่วนประกอบ UI และประกาศคุณสมบัติของพวกเขาได้อย่างกระชับ

ดูhttp://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdfสำหรับข้อมูลเพิ่มเติม

นี่คือตัวอย่างของการสร้างปุ่ม Vaadin:

val button = new Button("Click me"){
 setWidth("20px")
 setDescription("Click on this")
 setIcon(new ThemeResource("icons/ok.png"))
}

3

ไม่รวมสมาชิกจากimportข้อความสั่ง

สมมติว่าคุณต้องการใช้ a Loggerที่มี a printlnและ a printerrmethod แต่คุณต้องการที่จะใช้มันสำหรับข้อความแสดงความผิดพลาดเท่านั้นและทำให้เก่าดีPredef.printlnสำหรับเอาท์พุทมาตรฐาน คุณสามารถทำสิ่งนี้:

val logger = new Logger(...)
import logger.printerr

แต่ถ้าloggerมีอีกสิบสองวิธีที่คุณต้องการนำเข้าและใช้มันจะไม่สะดวกในการแสดงรายการ คุณสามารถลองแทน:

import logger.{println => donotuseprintlnt, _}

แต่นี่ยัง "มลภาวะ" รายการของสมาชิกที่นำเข้า ป้อนไวด์การ์ดที่ทรงพลังüber:

import logger.{println => _, _}

และนั่นจะทำในสิ่งที่ถูกต้อง


2

requireวิธีการ (กำหนดไว้ในPredef) ที่ช่วยให้คุณสามารถกำหนดข้อ จำกัด ฟังก์ชั่นเพิ่มเติมที่จะตรวจสอบในช่วงเวลาทำงาน ลองนึกภาพว่าคุณกำลังพัฒนาลูกค้าตัวสั่นด้วยความตื่นเต้นคนอื่นและคุณจำเป็นต้องจำกัดความยาวทวีตไม่เกิน 140 สัญลักษณ์ ยิ่งกว่านั้นคุณไม่สามารถโพสต์ทวีตที่ว่างเปล่า

def post(tweet: String) = {
  require(tweet.length < 140 && tweet.length > 0) 
  println(tweet)
 }

ตอนนี้โทรโพสต์ด้วยอาร์กิวเมนต์ความยาวไม่เหมาะสมจะทำให้เกิดข้อยกเว้น:

scala> post("that's ok")
that's ok

scala> post("")
java.lang.IllegalArgumentException: requirement failed
    at scala.Predef$.require(Predef.scala:145)
    at .post(<console>:8)

scala> post("way to looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong tweet") 
java.lang.IllegalArgumentException: requirement failed
    at scala.Predef$.require(Predef.scala:145)
    at .post(<console>:8)

คุณสามารถเขียนข้อกำหนดหลายข้อหรือเพิ่มรายละเอียดให้กับแต่ละข้อได้:

def post(tweet: String) = {
  require(tweet.length > 0, "too short message")
  require(tweet.length < 140, "too long message")
  println(tweet)
}

ตอนนี้มีข้อยกเว้น verbose:

scala> post("")
java.lang.IllegalArgumentException: requirement failed: too short message
    at scala.Predef$.require(Predef.scala:157)
    at .post(<console>:8)

อีกตัวอย่างหนึ่งคือที่นี่


โบนัส

คุณสามารถทำการกระทำได้ทุกครั้งที่ความต้องการล้มเหลว:

scala> var errorcount = 0
errorcount: Int = 0

def post(tweet: String) = {
  require(tweet.length > 0, {errorcount+=1})
  println(tweet)
  }

scala> errorcount
res14: Int = 0

scala> post("")
java.lang.IllegalArgumentException: requirement failed: ()
    at scala.Predef$.require(Predef.scala:157)
    at .post(<console>:9)
...

scala> errorcount
res16: Int = 1

1
requireไม่ใช่คำที่สงวนไว้ Predefแต่มันเป็นวิธีการที่กำหนดไว้ใน
missingfaktor

1

คุณลักษณะที่มีabstract overrideวิธีการเป็นคุณลักษณะใน Scala ที่ไม่ได้โฆษณากันอย่างแพร่หลายเหมือนคนอื่น ๆ ตั้งใจของวิธีการที่มีปรับปรุงคือการทำดำเนินการบางอย่างและการมอบหมายการเรียกร้องให้abstract override superจากนั้นคุณสมบัติเหล่านี้จะต้องมีการผสมกับการใช้งานที่เป็นรูปธรรมของabstract overrideวิธีการของพวกเขา

trait A {
  def a(s : String) : String
}

trait TimingA extends A {
  abstract override def a(s : String) = {
    val start = System.currentTimeMillis
    val result = super.a(s)
    val dur = System.currentTimeMillis-start
    println("Executed a in %s ms".format(dur))
    result
  }
}

trait ParameterPrintingA extends A {
  abstract override def a(s : String) = {
    println("Called a with s=%s".format(s))
    super.a(s)
  }
}

trait ImplementingA extends A {
  def a(s: String) = s.reverse
}

scala> val a = new ImplementingA with TimingA with ParameterPrintingA

scala> a.a("a lotta as")
Called a with s=a lotta as
Executed a in 0 ms
res4: String = sa attol a

ในขณะที่ตัวอย่างของฉันไม่มากไปกว่าชายที่ยากจน AOP แต่ฉันใช้คุณลักษณะแบบเรียงซ้อนได้เหล่านี้ตามความชอบของฉันในการสร้างอินสแตนซ์ล่ามสกาล่าด้วยการนำเข้าที่กำหนดไว้ล่วงหน้าการผูกแบบกำหนดเองและคลาสพา ธ ซ้อนลักษณะทำให้มันเป็นไปได้ที่จะสร้างโรงงานของฉันตามสายของnew InterpreterFactory with JsonLibs with LuceneLibsแล้วมีการนำเข้าที่มีประโยชน์และขอบเขต varibles สำหรับผู้ใช้สคริปต์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.