Ukryte funkcje Scali


149

Jakie są ukryte funkcje Scali, o których powinien wiedzieć każdy programista Scali?

Proszę o jedną ukrytą funkcję na odpowiedź.


6
Heh, to pytanie jest równie przydatne, ponieważ zawiera linki do innych postów z ukrytymi funkcjami, jak samo pytanie. Twoje zdrowie!
JohnMetta,

1
@mettadore wystarczy spojrzeć na powiązane linki po prawej stronie.
Daniel C. Sobral

2
@JohnMetta: Lub użyj tagu .

Odpowiedzi:


85

OK, musiałem dodać jeszcze jednego. Każdy Regexobiekt w Scali ma ekstraktor (patrz odpowiedź z oxbox_lakes powyżej), który daje dostęp do grup dopasowań. Możesz więc zrobić coś takiego:

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

Druga linia wygląda myląco, jeśli nie jesteś przyzwyczajony do używania dopasowywania wzorców i ekstraktorów. Za każdym razem, gdy definiujesz vallub var, to, co pojawia się po słowie kluczowym, nie jest po prostu identyfikatorem, ale raczej wzorcem. Dlatego to działa:

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

Wyrażenie po prawej stronie tworzy, Tuple3[Int, Double, String]które może pasować do wzorca (a, b, c).

Przez większość czasu wzorce używają ekstraktorów, które są członkami obiektów pojedynczych. Na przykład, jeśli napiszesz wzór, taki jak

Some(value)

to niejawnie wywołujesz ekstraktor Some.unapply.

Ale możesz także używać instancji klas we wzorcach i to właśnie się tutaj dzieje. Val regex jest instancją Regex, a kiedy używasz go we wzorcu, wywołujesz niejawnie regex.unapplySeq(w unapplyprzeciwieństwie do tego, że unapplySeqjest poza zakresem tej odpowiedzi), co wyodrębnia grupy dopasowań do a Seq[String], których elementy są przypisane w celu zmienne rok, miesiąc i dzień.


1
Dzięki za wysłanie tego! FYI jest to wspomniane w rozdziale „Wyodrębnianie za pomocą wyrażeń regularnych” w książce „Programowanie w Scali” na stronie 503 w pierwszym wydaniu i na stronie 611 w drugim wydaniu.
ziemski Paweł

51

Definicje typów strukturalnych - tj. Typ opisywany przez obsługiwane metody. Na przykład:

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

Zauważ, że typ parametru closeablenie jest zdefiniowany inaczej niż ma closemetodę


1
O typach strukturalnych nie ma nawet wzmianki w „Programowaniu w Scali”. Są jednak nieco wolniejsze niż inne techniki przekazywania typów, ponieważ używają odbicia do wywoływania właściwych metod. (Miejmy nadzieję, że wymyślą sposób, aby to przyspieszyć.)
Ken Bloom,

1
Istnieje również możliwość stworzenia dla nich aliasu, co działa jak zewnętrznie przypisany interfejs (bardzo wolny): wpisz Closeable = {def close (): Unit}
Alexey

45

Polimorfizm konstruktora typu (inaczej typy wyższego rodzaju)

Bez tej funkcji możesz na przykład wyrazić pomysł odwzorowania funkcji na liście w celu zwrócenia innej listy lub odwzorowania funkcji na drzewie w celu zwrócenia innego drzewa. Ale nie możesz ogólnie wyrazić tego pomysłu bez wyższych rodzajów.

W przypadku wyższych rodzajów można uchwycić ideę dowolnego typu sparametryzowaną innym typem. Mówi się, że konstruktor typu, który przyjmuje jeden parametr, jest rodzaju (*->*). Na przykład List. Mówi się, że konstruktor typu, który zwraca inny konstruktor typu, jest rodzaju (*->*->*). Na przykład Function1. Ale w Scali mamy wyższe rodzaje, więc możemy mieć konstruktory typów, które są sparametryzowane za pomocą innych konstruktorów typów. Więc są tego rodzaju ((*->*)->*).

Na przykład:

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

Teraz, jeśli masz Functor[List], możesz mapować listy. Jeśli masz Functor[Tree], możesz mapować drzewa. Ale co ważniejsze, jeśli masz Functor[A] dla dowolnego rodzaju A(*->*) , możesz zmapować funkcję A.


39

Ekstraktory, które pozwalają zastąpić niechlujny if-elseif-elsekod stylu wzorami. Wiem, że nie są one dokładnie ukryte, ale używam Scali od kilku miesięcy, nie rozumiejąc ich mocy. Na (długi) przykład mogę wymienić:

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

Dzięki temu, co moim zdaniem jest znacznie jaśniejsze

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

Muszę trochę popracować w tle ...

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

Ale praca jest tego warta, ponieważ oddziela kawałek logiki biznesowej w rozsądne miejsce. Mogę zaimplementować moje Product.getCodemetody w następujący sposób.

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

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

czy to nie jest jak przełącznik? może można to bardziej refaktoryzować.
Geo

14
Wzory są jak przełączniki z turbodoładowaniem: znacznie mocniejsze i wyraźniejsze
oxbow_lakes

1
Fajnie, ale nie podoba mi się to, że musisz używać implicit, ponieważ jego zakres sięga dalej niż dopasowanie {}. Możesz również po prostu dodać metodę do ProductService, która wyszukuje produkt według kodu. I tak zawinąłbyś refaktoryzowany fragment w metodę, aby móc go używać wszędzie.
Martin Konicek,

35

Manifesty, które są swego rodzaju sposobem uzyskiwania informacji o typie w czasie wykonywania, tak jakby Scala miała typy zreifikowane.


8
Myślę, że lepiej jest wyjaśnić odpowiedź w odpowiedzi niż odnosić się do linku. Swoją drogą, cześć agai! :-)
Daniel C. Sobral

To jest naprawdę ukryta funkcja ... nawet nie w dokumentacji API. Jednak bardzo przydatne.
André Laszlo

35

W scali 2.8 możesz mieć metody rekurencyjne typu tail, używając pakietu scala.util.control.TailCalls (w rzeczywistości jest to trampolinowanie).

Przykład:

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

Klasy Case automatycznie łączą cechę Product, zapewniając nietypowy, indeksowany dostęp do pól bez żadnej refleksji:

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)

Ta funkcja zapewnia również uproszczony sposób zmiany wyniku toStringmetody:

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

Nie jest to dokładnie ukryte, ale z pewnością niedostatecznie reklamowana funkcja: scalac -Xprint .

Jako ilustrację zastosowania rozważ następujące źródło:

class A { "xx".r }

Kompilując to za pomocą wyjścia 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
  }
}

Uwaga scala.this.Predef.augmentString("xx").r, która jest aplikacją implicit def augmentStringobecną w Predef.scala.

scalac -Xprint: <faza> wypisze drzewo składni po jakiejś fazie kompilatora. Aby zobaczyć dostępne fazy, użyj scalac -Xshow-phases .

To świetny sposób, aby dowiedzieć się, co dzieje się za kulisami.

Spróbuj z

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

wykorzystując fazę typer, aby naprawdę poczuć, jak jest ona użyteczna.


30

Możesz zdefiniować własne struktury sterowania. To tak naprawdę tylko funkcje i obiekty oraz trochę cukru syntaktycznego, ale wyglądają i zachowują się jak prawdziwe.

Na przykład poniższy kod definiuje dont {...} unless (cond)i 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
  }
}

Teraz możesz wykonać następujące czynności:

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


Byłbym ciekawy, czy ktoś zna sposób definiowania bloków if-then-else z opcjonalnym innym sprawdzaniem typu, jak standardowe.
Philippe

@Philippe: zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero. Wymaga Scalaz.
missingfaktor

26

@switch adnotacja w Scali 2.8:

Adnotacja, która ma być zastosowana do wyrażenia dopasowującego. Jeśli jest obecny, kompilator sprawdzi, czy dopasowanie zostało skompilowane do przełącznika tabeli lub przełącznika wyszukiwania, i wygeneruje błąd, jeśli zamiast tego skompiluje się w serię wyrażeń warunkowych.

Przykład:

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

Nie wiem, czy to jest naprawdę ukryte, ale uważam to za całkiem miłe.

Konstruktory typów, które przyjmują 2 parametry typu, można zapisać w notacji wrostkowej

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
Miły! Mogę sobie wyobrazić, że czasami jest to przydatne w poprawie czytelności. Na przykład var foo2barConverter: Foo ConvertTo Barsprawi, że kolejność parametrów typu będzie oczywista.
Esko Luontola

4
Czasami robię to w kodzie, który do pewnego stopnia używa PartialFunction: wpisz ~> [A, B] = PartialFunction [A, B]
raichoo

24

Scala 2.8 wprowadziła domyślne i nazwane argumenty, co umożliwiło dodanie nowej metody "kopiowania", którą Scala dodaje do klas przypadków. Jeśli to zdefiniujesz:

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

i chcesz utworzyć nowe Foo podobne do istniejącego Foo, tylko z inną wartością „n”, możesz po prostu powiedzieć:

foo.copy(n = 3)

3
OSTRZEŻENIE: metoda copy nie zostanie zastąpiona, jeśli będziesz dziedziczyć jedną klasę przypadku z innej. Musisz więc zmienić to ręcznie
Alexey

Powiązane:
Czystszy

5
klasa przypadku nie jest już (Scala 2.8) może dziedziczyć z klasy przypadku. Dziękuję panie Scala za potępienie tego bezbożnego dziedzictwa.
olle kullberg

24

w scali 2.8 możesz dodać @specialized do swoich klas / metod ogólnych. Spowoduje to utworzenie specjalnych wersji klasy dla typów pierwotnych (rozszerzenie AnyVal) i zaoszczędzenie kosztów niepotrzebnego pakowania / rozpakowywania: class Foo[@specialized T]...

Możesz wybrać podzbiór AnyVals: class Foo[@specialized(Int,Boolean) T]...


1
Czy jest jakieś dłuższe wyjaśnienie, na które mógłbyś mi wskazać? Chciałbym dowiedzieć się więcej.
Paweł Prażak

23

Rozszerzenie języka. Zawsze chciałem zrobić coś takiego w Javie (nie mogłem). Ale w Scali mogę mieć:

  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
  }

a potem napisz:

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

i dostać

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

23

Możesz wyznaczyć parametr wywołania według nazwy (EDYCJA: to jest inny niż parametr leniwy!) Do funkcji i nie będzie on oceniany, dopóki nie zostanie użyty przez funkcję (EDYCJA: w rzeczywistości zostanie ponownie oszacowany za każdym razem, gdy jest używany). Zobacz ten FAQ, aby uzyskać szczegółowe informacje

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

Pomyślałem, że „x: => Bar” oznacza, że ​​x jest funkcją, która nie przyjmuje parametrów i zwraca Bar. Zatem „nowy słupek (22)” jest po prostu funkcją anonimową i jest oceniany jako funkcja jak każda inna funkcja.
Alex Black

1
„x: () => Bar” definiuje funkcję x, która nie przyjmuje parametrów i zwraca słupek. x: => Bar definiuje x jako wywołanie według nazwy. Zajrzyj na scala.sygneca.com/faqs/ ... po więcej szczegółów
agilefall

3
To, co pokazujesz, to parametry Call-by-name. Leniwe parametry nie są jeszcze zaimplementowane: lampsvn.epfl.ch/trac/scala/ticket/240
ArtemGr

Myślę, że możesz użyć tego jako leniwego parametru, jeśli robisz coś podobnego lazy val xx: Bar = xw swojej metodzie i od tego momentu używasz tylko xx.
Cristian Vrabie,

20

Możesz użyć, locallyaby wprowadzić blok lokalny bez powodowania problemów z wnioskami średnikami.

Stosowanie:

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 jest zdefiniowany w „Predef.scala” jako:

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

Będąc w linii, nie nakłada żadnych dodatkowych kosztów.


3
Jest to lepiej wyjaśnione na stackoverflow.com/questions/3237727/ ...
Esko Luontola

17

Wczesna inicjalizacja:

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)

Wynik:

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

Tworzymy instancję anonimowej klasy wewnętrznej, inicjalizując valuepole w bloku przed with AbstractT2klauzulą. Gwarantuje valueto, że zostanie zainicjowany przed wykonaniem treści programu AbstractT2, jak pokazano po uruchomieniu skryptu.


1
Konstrukcja ta nosi nazwę „wczesna inicjalizacja”.
Randall Schulz

17

Możesz komponować typy strukturalne za pomocą słowa kluczowego „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

składnia symbolu zastępczego dla funkcji anonimowych

Ze specyfikacji języka Scala:

SimpleExpr1 ::= '_'

Wyrażenie (kategorii składniowej Expr) może zawierać osadzone symbole podkreślenia _w miejscach, w których identyfikatory są dozwolone. Takie wyrażenie reprezentuje anonimową funkcję, w której kolejne wystąpienia podkreślenia oznaczają kolejne parametry.

Z zmian języka 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)

Używając tego, możesz zrobić coś takiego:

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

2
Powinno to być nazywane „składnią zastępczą dla funkcji anonimowych”. Niejawny ma wyraźne znaczenie w Scali i nie jest z tym związany.
retronim

Link ma nieoczywisty związek z odpowiedzią. „ukryty” nie jest właściwym terminem na to. Jak wyżej, powinno to być „symbol zastępczy”.
Alain O'Dea

2
To nie jest tak naprawdę "ukryte", widziałem to użycie w prawie wszystkich tutorialach o Scali, które przeczytałem ... :-) Ale doceniam formalną definicję, której jeszcze nie widziałem.
PhiLho

@PhiLho być może był mniej znany w 2009 roku. Nie wiem.
Eugene Yokota,

Brakowało mi oryginalnej daty, ponieważ wyświetlana jest tylko data ostatniej edycji. No cóż, nie wszystkie funkcje omówione w tym wątku są „ukryte”. Fajny wątek i tak dobra odpowiedź.
PhiLho

16

Niejawne definicje, zwłaszcza konwersje.

Na przykład załóżmy, że funkcja, która sformatuje ciąg wejściowy tak, aby pasował do rozmiaru, zastępując jego środek znakiem „...”:

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
}

Możesz tego użyć z dowolnym ciągiem i oczywiście użyć metody toString do konwersji wszystkiego. Ale możesz też napisać to tak:

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
}

Następnie możesz przejść zajęcia innego typu, wykonując następujące czynności:

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

Teraz możesz wywołać tę funkcję, przekazując double:

sizeBoundedString(12345.12345D, 8)

Ostatni argument jest niejawny i jest przekazywany automatycznie z powodu niejawnej deklaracji. Ponadto "s" jest istnieniem traktowane jak String wewnątrz sizeBoundedString, ponieważ następuje niejawna konwersja z niego na String.

Implikacje tego typu są lepiej zdefiniowane dla nietypowych typów, aby uniknąć nieoczekiwanych konwersji. Możesz również jawnie przekazać konwersję i nadal będzie ona niejawnie używana wewnątrz sizeBoundedString:

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

Możesz również mieć wiele niejawnych argumentów, ale wtedy musisz albo przekazać je wszystkie, albo nie przekazywać żadnego z nich. Istnieje również składnia skrótów dla niejawnych konwersji:

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
}

To jest używane dokładnie w ten sam sposób.

Implikacje mogą mieć dowolną wartość. Można ich użyć na przykład do ukrycia informacji bibliotecznych. Weźmy na przykład następujący przykład:

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

W tym przykładzie wywołanie „f” w obiekcie Y spowoduje wysłanie dziennika do demona domyślnego, a na instancji X do demona Daemon X. Ale wywołanie g na instancji X wyśle ​​dziennik do jawnie podanego DefaultDaemon.

Chociaż ten prosty przykład można ponownie napisać z przeciążeniem i stanem prywatnym, implicity nie wymagają stanu prywatnego i można je umieścić w kontekście za pomocą importu.


13

Może niezbyt ukryte, ale myślę, że jest to przydatne:

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

Spowoduje to automatyczne wygenerowanie metody pobierającej i ustawiającej dla pola zgodnego z konwencją fasoli.

Dalszy opis w pracach deweloperskich


6
I możesz zrobić do niego skrót, jeśli często go używasz, np. Import scala.reflect. {BeanProperty => BP}
Alexey

13

Niejawne argumenty w domknięciach.

Argument funkcji można oznaczyć jako niejawny, tak jak w przypadku metod. W zakresie treści funkcji niejawny parametr jest widoczny i kwalifikuje się do niejawnego rozwiązania:

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

Typy wyników zależą od niejawnego rozwiązania. Może to dać formę wielokrotnej wysyłki:

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

Może tak być, ale powyższa sesja jest myląca. Definicja foozastosowań, aktóre musiały być obecne w środowisku przed wykonaniem tych poleceń. Zakładam, że miałeś na myśli z.perform(x).
Daniel C. Sobral

4

Scala jest odpowiednikiem inicjatora podwójnego nawiasu klamrowego Java.

Scala umożliwia utworzenie anonimowej podklasy z treścią klasy (konstruktora) zawierającą instrukcje do zainicjowania instancji tej klasy.

Ten wzorzec jest bardzo przydatny podczas budowania interfejsów użytkownika opartych na komponentach (na przykład Swing, Vaadin), ponieważ umożliwia tworzenie komponentów UI i bardziej zwięzłe deklarowanie ich właściwości.

Więcej informacji można znaleźć pod adresem http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf .

Oto przykład tworzenia przycisku Vaadin:

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

3

Wyłączanie członków z importoświadczeń

Załóżmy, że chcesz użyć metody Loggerzawierającej a printlni printerrmetodę, ale chcesz używać tylko tej dla komunikatów o błędach i zachować stare dobre Predef.printlndla standardowego wyjścia. Możesz to zrobić:

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

ale jeśli loggerzawiera również kolejne dwanaście metod, które chciałbyś zaimportować i użyć, ich lista staje się niewygodna. Zamiast tego możesz spróbować:

import logger.{println => donotuseprintlnt, _}

ale to nadal „zanieczyszcza” listę importowanych członków. Wpisz niezwykle potężną kartę wieloznaczną:

import logger.{println => _, _}

a to wystarczy ™.


2

requiremetoda (zdefiniowana w Predef), która umożliwia zdefiniowanie dodatkowych ograniczeń funkcji, które byłyby sprawdzane w czasie wykonywania. Wyobraź sobie, że tworzysz kolejnego klienta Twittera i musisz ograniczyć długość tweeta do 140 symboli. Ponadto nie możesz publikować pustego tweeta.

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

Teraz wywołanie posta z niewłaściwym argumentem długości spowoduje wyjątek:

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)

Możesz napisać wiele wymagań lub nawet dodać opis do każdego:

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

Teraz wyjątki są szczegółowe:

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

Tutaj jest jeszcze jeden przykład .


Premia

Możesz wykonać akcję za każdym razem, gdy wymaganie nie powiedzie się:

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
requirenie jest słowem zastrzeżonym. To tylko metoda zdefiniowana w Predef.
missingfaktor

1

Cechy z abstract overridemetodami to funkcja w Scali, która nie jest tak szeroko reklamowana, jak wiele innych. Celem metod z abstract overridemodyfikatorem jest wykonanie pewnych operacji i delegowanie wywołania do super. Następnie cechy te należy wymieszać z konkretnymi implementacjami ich abstract overridemetod.

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

Chociaż mój przykład jest naprawdę niewiele więcej niż kiepskim człowiekiem AOP, użyłem tych Stackable Traits bardzo do moich upodobań do zbudowania instancji interpretera Scala z predefiniowanymi importami, niestandardowymi powiązaniami i klasami. W Nakładane cechy pozwoliły stworzyć moją fabrykę wzdłuż linii new InterpreterFactory with JsonLibs with LuceneLibs, a następnie mieć użytecznych importu i zakres varibles dla skryptów użytkowników.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.