harryのブログ

ロードバイクとか模型とかゲームについて何か書いてあるかもしれません

Scalaの記号みたいな奴らなんなの


これなんなの?

Scalaの記号はググラビリティ低すぎて調べようがないし全くわからん…せめて定義位置とか実装がわかれば…」という自分のためのメモ、いわゆるチラシの裏です。

とりあえずScalaの記号については、ソースコードをダウンロードして下記の正規表現grepしました。

^[^*/]* (?:def|class|object|type|val) ([^0-9A-Za-z" \(\[]+)(?:[ \(\[]|$)

基本、わからない記号見つけたら定義元に飛んで *1、ソースを読んでます。 *2

  • IDEで定義元に飛べない記号(_*など)は、言語仕様の可能性が高いので言語仕様を読みましょう。
  • 記号の使用例がわからない場合、そのライブラリのテストコードを読むと良いケースがあります。
  • 急に()が出てきた場合、カリー化された引数かapply()です。
    apply()の場合、一時的に書き換えてショートカットキーで飛ぶか、検索してapply()の定義を確認します。

Language Specification

バッカス・ナウア記法

Scalaの言語仕様では、文法の定義にバッカス・ナウア記法(BNF: Backus-Naur form)が使用されています。使用されているBNFの記号と意味は下記の通りです。

記号 意味
::= 定義
‘…’ 文字列
`…' 文字列
| OR
( … ) グループ化
{ … } 0回以上の繰り返し
[ … ] オプション (0回 or 1回)
// コメント (注釈)
“…” コメント (注釈)

記号ではありませんが、nlsemiの意味は下記の通りです。

nl               ::=  “newlinecharacter”  
semi             ::=  ‘;’ |  nl {nl}

Arrow

Identifiers

/

http://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html#identifiers

The Unicode operators \u21D2 ‘⇒’ and \u2190 ‘←’, which have the ASCII equivalents => and <-, are also reserved.

なお、2.13.xから非推奨になりました。(実務で使ってる人は皆無だと思いますが…)

Deprecate unicode arrows , and by smarter · Pull Request #7540 · scala/scala https://github.com/scala/scala/pull/7540

Import Clauses

http://www.scala-lang.org/files/archive/spec/2.11/04-basic-declarations-and-definitions.html#import-clauses

Import selectors work in the same way for type and term members. For instance, an import clause import p.{x => y} renames the term name p.x to the term name y and the type name p.x to the type name y. At least one of these two names must reference an importable member of p.

import p.{x => y}と書くと、p.xyという名前でimportすることができます。 SessionModule等のかぶりやすい名前のtypeを複数importする際に便利なことがあります。

  • java.sql.DateSQLDateとしてimportする例
import java.util.Date
import java.sql.{Date => SQLDate}

If the target in an import selector is a wildcard, the import selector hides access to the source member. For instance, the import selector x => _ “renames” x to the wildcard symbol (which is unaccessible as a name in user programs), and thereby effectively prevents unqualified access to x. This is useful if there is a final wildcard in the same import selector list, which imports all members not mentioned in previous import selectors.

package内のすべてのtypeをimportする際はpackage._を使用しますが、特定のtypeだけ除外したい場合はpackage.{Type => _, _}と書くことで除外できます。 複数のtypeを除外することもできます。

  • java.util.Dateを除くjava.util._java.sql._をimportする例
import java.util.{Date => _, _}
import java.sql._

Scalaスケーラブルプログラミング 第2版 13.3 インポート (P.238~239)

Self type

http://www.scala-lang.org/files/archive/spec/2.11/05-classes-and-objects.html#templates

ClassTemplate   ::=  [EarlyDefs] ClassParents [TemplateBody]
TraitTemplate   ::=  [EarlyDefs] TraitParents [TemplateBody]
ClassParents    ::=  Constr {`with' AnnotType}
TraitParents    ::=  AnnotType {`with' AnnotType}
TemplateBody    ::=  [nl] `{' [SelfType] TemplateStat {semi TemplateStat} `}'
SelfType        ::=  id [`:' Type] `=>'
                 |   this `:' Type `=>'

The sequence of template statements may be prefixed with a formal parameter definition and an arrow, e.g. x =>, or x:T =>. If a formal parameter is given, it can be used as an alias for the reference this throughout the body of the template.

class, object, traitの定義部(TemplateBody)の最初にid : Type =>またはthis : Type =>と書くことができます。これはSelf Type(自分型)で、基本的に自分自身のインスタンスthisと同じものを指します。つまり、thisの別名(alias)を定義することができます。

id : Typeの型注釈は省略可能で、idは慣習的にselfが使用されます。

self =>の使用例

Optionでの使用例です。

Option.scala#L98-L99

sealed abstract class Option[+A] extends Product with Serializable {
  self =>

Option.scala#L197-L211

  /** Necessary to keep $option from being implicitly converted to
   *  [[scala.collection.Iterable]] in `for` comprehensions.
   */
  @inline final def withFilter(p: A => Boolean): WithFilter = new WithFilter(p)

  /** We need a whole WithFilter class to honor the "doesn't create a new
   *  collection" contract even though it seems unlikely to matter much in a
   *  collection with max size 1.
   */
  class WithFilter(p: A => Boolean) {
    def map[B](f: A => B): Option[B] = self filter p map f
    def flatMap[B](f: A => Option[B]): Option[B] = self filter p flatMap f
    def foreach[U](f: A => U): Unit = self filter p foreach f
    def withFilter(q: A => Boolean): WithFilter = new WithFilter(x => p(x) && q(x))
  }

abstract class Option内で定義したclass WithFilterからOptionインスタンスを参照する際、thisを使用するとWithFilter自身のインスタンスを指してしまいますが、自分型のselfを使用することでOptionインスタンスを参照することができます。

this: Type =>の使用例

例えば特定のtraitがmixinされていることが前提のクラスに対してmixinするためのtraitを定義するとします。 その際、定義したtrait内でクラスへmixinされているはずのtraitのメンバを使用したいことがあります。

class SomeClass extends Foo with Bar

trait Foo {
  // ...
}

// Bar は Foo をmixinしているtypeにmixinすることが前提のtrait
trait Bar {
  // Fooのメンバを使用したい…
}

その場合は、自分型のTypeにそのtraitを指定することで、そのtraitのメンバが使用できます。 (thisがTypeに指定したtypeとして振舞います)

trait Bar {
  this: Foo =>

  // Fooのメンバが使用できる
}

Play Frameworkでの使用例です。 Controllerをmixinしているクラスに対してmixinするtraitで、Controllerのメンバを使用したい場合です。

import play.api.mvc._

trait FooController {
  this: Controller =>

  // `Controller` のメンバが使用できる
}

さらに、I18nSupportをmixinしていることが前提の場合は下記のようになります。

import play.api.i18n.I18nSupport
import play.api.mvc._

trait BarController {
  this: Controller with I18nSupport =>

  // `Controller` と `I18nSupport` のメンバが使用できる
}

自分型でtypeを指定した場合、指定したtypeを継承しているtrait, class, objectに対してのみ継承やmixinができます。

import play.api.mvc._

// OK
class SomeContoller extends Controller with FooController

// Compile error
// illegal inheritance; self-type SomeClass does not conform to FooController's selftype Controller
class SomeClass extends FooController

Atmark

Pattern Binders

http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#pattern-binders

A pattern binder x@p consists of a pattern variable x and a pattern p. The type of the variable x is the static type T of the pattern p. This pattern matches any value v matched by the pattern p, provided the run-time type of v is also an instance of T, and it binds the variable name to that value.

パターンマッチングでx@pと書くと、パターンpに一致した際の値を変数xとして参照(束縛)することができます。

List(1, 2, 3) match {
  // headが 1 にマッチするListを変数 x に束縛する
  case x @ 1 :: _ =>
    x // = x: List = List(1, 2, 3)
  case _ =>
    Nil
}

[Scala]パターンマッチにおけるアットマーク(@) http://qiita.com/petitviolet/items/89843d948b6fc06eba57

Colon

Infix Operations

http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#infix-operations

The associativity of an operator is determined by the operator's last character. Operators ending in a colon `:' are right-associative. All other operators are left-associative.

演算子(operator、メソッド)の最後の文字がコロン(:)の場合、その演算子は右被演算子から呼び出されます。(左被演算子は引数になります)

  • Seqの例
val a = Seq(1, 2, 3)

a :+ 4

a.:+(4) // = Seq(1, 2, 3, 4)

// メソッド `+:` は、右被演算子の a から呼び出される
0 +: a

a.+:(0) // = Seq(0, 1, 2, 3)

Unary

Prefix Operations

http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#prefix-operations

A prefix operation op;e consists of a prefix operator op, which must be one of the identifiers ‘+’, ‘-’, ‘!’ or ‘~’. The expression op;e is equivalent to the postfix method application e.unary_op.

単項(unary)演算子のうち、+, -, !, ~は前置演算子として使用することができます。前置演算子として使用された場合、unary_opメソッドが呼び出されます。

  • Booleanの例
val b = true

// 前置演算子 `!` の場合、`unary_!` メソッドが呼び出される
!b

b.unary_! // = false

つまり、unary_opメソッドを定義すればopを前置演算子として使用することができます。 メソッド定義のunary_op:の間にはスペースが必要です。

case object Black extends Things
case object White extends Things

sealed abstract class Things {
  def unary_! : Things = {
    this match {
      case Black => White
      case White => Black
    }
  }
}

!Black // = White

Underscore

http://stackoverflow.com/questions/8000903/what-are-all-the-uses-of-an-underscore-in-scala

* / _*

Repeated Parameters

http://www.scala-lang.org/files/archive/spec/2.11/04-basic-declarations-and-definitions.html#repeated-parameters

http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#function-applications

  • メソッド、関数定義のType*は可変長引数
  • 可変長引数は最後の引数でのみ使用できます
def sum(args: Int*): Int = {
  var result = 0
  for (arg <- args) result += arg
  result
}
val sum: (Int*) => Int = { args =>
  var result = 0
  for (arg <- args) result += arg
  result
}

Seq型は_*型注釈をつけることで可変長引数に渡すことができます。

sum(Seq(1, 2, 3, 4): _*)

Seq型であればよいので、Seq型に暗黙の型変換されるものでもよいです。

// Char の可変長引数を取るメソッド
def toHexString(args: Char*): String = {
  args.map(c => f"$c%#06x").mkString(",")
}

// String は WrappedString (Seq[Char]) へ暗黙の型変換されます
toHexString("str": _*)

// 0x0073,0x0074,0x0072

Pattern Sequences

http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#pattern-sequences

Pattern matching中の_*はsequence wildcard

case List(1, 2, _*) => ... // will match all lists starting with 1, 2, ...

[]

型パラメーター

[A <: B] [A >: B] [+T] [-T]

上限・下限境界と変位指定アノテーション

第21章:Scalaの型パラメータ http://qiita.com/f81@github/items/7a664df8f4b87d86e5d8

共変・反変・上限境界・下限境界の関係性まとめ http://qiita.com/mtoyoshi/items/bd0ad545935225419327

[_]

def f[M[_]]       // Higher kinded type parameter
def f(m: M[_])    // Existential type

private[C] / protected[C]

http://www.scala-lang.org/files/archive/spec/2.11/05-classes-and-objects.html#modifiers

A private modifier can be qualified with an identifier C (e.g. private[C]) that must denote a class or package enclosing the definition. Members labeled with such a modifier are accessible respectively only from code inside the package C or only from code inside the class C and its companion module.

A protected modifier can be qualified with an identifier C (e.g. protected[C]) that must denote a class or package enclosing the definition. Members labeled with such a modifier are also accessible respectively from all code inside the package C or from all code inside the class C and its companion module.

Scala Standard Library

Any

==

  final def ==(that: Any): Boolean = this equals that

!=

  final def != (that: Any): Boolean = !(this == that)

##

  /** Equivalent to `x.hashCode` except for boxed numeric types and `null`.
   *  For numerics, it returns a hash value which is consistent
   *  with value equality: if two value type instances compare
   *  as true, then ## will produce the same hash value for each
   *  of them.
   *  For `null` returns a hashcode where `null.hashCode` throws a
   *  `NullPointerException`.
   *
   *  @return   a hash value consistent with ==
   */
  final def ##(): Int = sys.error("##")

null安全なhashCodeの取得ができます。

> scala -feature

scala> val s: String = null
s: String = null

scala> s.hashCode
java.lang.NullPointerException
  ... 33 elided

scala> s##
<console>:12: warning: postfix operator ## should be enabled
by making the implicit value scala.language.postfixOps visible.
This can be achieved by adding the import clause 'import scala.language.postfixOps'
or by setting the compiler option -language:postfixOps.
See the Scala docs for value scala.language.postfixOps for a discussion
why the feature should be explicitly enabled.
       s##
        ^
res0: Int = 0

Scalaパズル 36の罠から学ぶベストプラクティス PUZZLE28 好きな値を選んでください!――Pick a Value, AnyValue! (P.187-189)

AnyRef

==

  final def ==(that: Any): Boolean =
    if (this eq null) that.asInstanceOf[AnyRef] eq null
    else this equals that

null安全な等価比較ができます。

library

package.scala

  type ::[A] = scala.collection.immutable.::[A]
  val :: = scala.collection.immutable.::

  val +: = scala.collection.+:
  val :+ = scala.collection.:+

  type Stream[+A] = scala.collection.immutable.Stream[A]
  val Stream = scala.collection.immutable.Stream
  val #:: = scala.collection.immutable.Stream.#::

Numeric value types

http://www.scala-lang.org/files/archive/spec/2.11/12-the-scala-standard-library.html#numeric-value-types

Boolean

== != || && | & ^

Byte / Char / Short / Int / Long

<< >> <<< >>> == != < <= > >= | & ^ + - * / %

Float / Double

== != < <= > >= | & ^ + - * / %

Enumeration

Value

    /** Create a ValueSet which contains this value and another one */
    def + (v: Value) = ValueSet(this, v)

ValueSet

    def + (value: Value) = new ValueSet(nnIds + (value.id - bottomId))
    def - (value: Value) = new ValueSet(nnIds - (value.id - bottomId))

ValueSet.newBuilder

      def += (x: Value) = { b += (x.id - bottomId); this }

Predef.scala

http://www.scala-lang.org/files/archive/spec/2.11/12-the-scala-standard-library.html#the-predef-object

???

  /** `???` can be used for marking methods that remain to be implemented.
   *  @throws NotImplementedError
   */
  def ??? : Nothing = throw new NotImplementedError

ArrowAssoc

  implicit final class ArrowAssoc[A](private val self: A) extends AnyVal {
    @inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
    def →[B](y: B): Tuple2[A, B] = ->(y)
  }

a -> bと書くとTuple2(a, b)に変換されて不思議に思ったことありませんか?これです。

<:<

An instance of A <:< B witnesses that A is a subtype of B.

<:<を使ってメソッド引数の型を限定する (例: IntもしくはBooleanもしくはString http://qiita.com/mtoyoshi/items/fbf2c744e3c7fd69a438

続<:<を使ってメソッド引数の型を限定する(厳密に) http://qiita.com/mtoyoshi/items/13ac0500aa23fb4fa0d4

=:=

An instance of A =:= B witnesses that the types A and B are equal.

collections

Traversable

TraversableLike

  • ++ コレクションを追加した新しいコレクションを返す
  • ++: 右辺のコレクションと左辺のコレクションを追加した新しいコレクションを返す
Seq(1, 2, 3) ++ Seq(4, 5, 6)  // = Seq(1, 2, 3, 4, 5, 6)

Seq(1, 2, 3) ++: Seq(4, 5, 6) // = Seq(1, 2, 3, 4, 5, 6)

TraversableOnce

  def /:[B](z: B)(op: (B, A) => B): B = foldLeft(z)(op)

  def :\[B](z: B)(op: (A, B) => B): B = foldRight(z)(op)

SeqLike

  • +: 要素にSeqを追加した新しいSeqを返す (Seqの先頭に要素を挿入した新しいSeqを返す)
  • :+ Seqに要素を追加した新しいSeqを返す
0 +: Seq(1, 2, 3) // = Seq(0, 1, 2, 3)

Seq(1, 2, 3) :+ 4 // = Seq(1, 2, 3, 4)

generic

Growable

http://www.scala-lang.org/api/2.11.7/index.html#scala.collection.generic.Growable

mutableなコレクションなどにmixinされているtrait 新しいコレクションを作らず、コレクションに直接要素を追加する

  • += コレクションへ1つ、または複数の要素を追加する
  • ++= 引数のコレクションにある要素を追加する

Shrinkable

http://www.scala-lang.org/api/2.11.7/index.html#scala.collection.generic.Shrinkable

mutableなコレクションなどにmixinされているtrait 新しいコレクションを作らず、コレクションから直接要素を削除する

  • -= コレクションから1つ、または複数の要素を削除する
  • --= 引数のコレクションにある要素を削除する

Subtractable

http://www.scala-lang.org/api/2.11.7/index.html#scala.collection.generic.Subtractable

  • - コレクションから1つ、または複数の要素を削除した新しいコレクションを返す

override def -ソースコードを検索すると、コレクション毎の実装詳細を確認することができます。

  def --(xs: GenTraversableOnce[A]): Repr = (repr /: xs.seq) (_ - _)

引数のコレクションにある要素を削除した新しいコレクションを返す

BitSetLike

| & &~ ^

BitSet同士のor, and, and-not, xorを取り、新しいBitSetを返す

mutable.BitSet

|= &= &~= ^=

BitSet同士のor, and, and-not, xorを取り、BitSetを更新する

GenSetLike

+ -

  def +(elem: A): Repr
  def -(elem: A): Repr

Setへ要素を追加、削除する

&

  def &(that: GenSet[A]): Repr = this intersect that

Set同士のandを取る

|

  def | (that: GenSet[A]): Repr = this union that

Set同士のorを取る

&~

  def &~(that: GenSet[A]): Repr = this diff that

Set同士のand-notを取る*3

val a = Set(1, 2, 3, 4, 5, 6)
val b = Set(1, 2, 3)

a &~ b // = Set(4, 5, 6)

b &~ a // = Set()

immutable.::

final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
  override def tail : List[B] = tl
  override def isEmpty: Boolean = false
}

::(head, tail)となるようなListを返すクラスです。

package.scala::のtype aliasが定義されており、Listに対するパターンマッチ(case head :: tail)でも使用されます。

余談ですが、REPLでは:から始まる文字列がコマンドとして解釈されるため、バッククォートで囲みリテラル識別子(literal identifiers)にする必要があります。

scala> `::`(1, List(2, 3, 4))
res0: scala.collection.immutable.::[Int] = List(1, 2, 3, 4)

scala> res0.head
res1: Int = 1

scala> res0.tail
res2: List[Int] = List(2, 3, 4)

immutable.List

::

  def ::[B >: A] (x: B): List[B] =
    new scala.collection.immutable.::(x, this)

メソッドの最後の文字が:なので、right-associativeです。 Scaladocのexampleに書かれていますが、下記のようになります。

1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)

つまり、終端がListであればよいので、Nilも使えます。

1 :: 2 :: 3 :: Nil

1 :: 2 :: Nil.::(3)

1 :: 2 :: List(3)

1 :: List(3).::(2)

1 :: List(2, 3)

List(2, 3).::(1)

List(1, 2, 3)

:::

  def :::[B >: A](prefix: List[B]): List[B] =
    if (isEmpty) prefix
    else if (prefix.isEmpty) this
    else (new ListBuffer[B] ++= prefix).prependToList(this)

List同士を連結した新しいListを返す

List(1, 2, 3) ::: List(4, 5, 6) // = List(1, 2, 3, 4, 5, 6)

Stream.#::

  object #:: {
    def unapply[A](xs: Stream[A]): Option[(A, Stream[A])] =
      if (xs.isEmpty) None
      else Some((xs.head, xs.tail))
  }

Streamのパターンマッチ(case head #:: tail)のために定義されている記号(object) package.scala#::のtype aliasが定義されています。

Stream.ConsWrapper

#::#:::が定義されていて、Streamからこれらのメソッドが呼ばれるとConsWrapperに暗黙の型変換が行われます。

  /** A wrapper method that adds `#::` for cons and `#:::` for concat as operations
   *  to streams.
   */
  implicit def consWrapper[A](stream: => Stream[A]): ConsWrapper[A] =
    new ConsWrapper[A](stream)

#::

  def #::(hd: A): Stream[A] = cons(hd, tl)

簡単にいうと、head #:: tailとなるようなStreamが返ります。

実際にはobject consapply()が実行され、final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A]が返ります。

#:::

  def #:::(prefix: Stream[A]): Stream[A] = prefix append tl

Stream同士を連結した新しいStreamを返す

mutable.BufferLike

++=:

  def ++=:(xs: TraversableOnce[A]): this.type = { insertAll(0, xs.toTraversable); this }

コレクションxsを現在のBufferの前方に挿入する

val buffer = mutable.Buffer(4, 5, 6)

Seq(1, 2, 3) ++=: buffer

buffer // = mutable.Buffer(1, 2, 3, 4, 5, 6)

StringLike

*

  def * (n: Int): String = {
    val buf = new StringBuilder
    for (i <- 0 until n) buf append toString
    buf.toString
  }

現在の文字列を指定回数連結した文字列を返す

math

/%: 商と余りをTupleで返す &~: and-not (this & ~that)

BigDecimal

<= >= < > + - * / /% %

BigInt

<= >= < > + - * / % /% << >> & | ^ &~

Fractional

/

Integral

/ % /%

Numeric

+ - *

Ordered

< > <= >=

Ordering

< <= > >=

PartiallyOrdered

< > <= >=

sys

SystemProperties

-= +=

package process

type =?>[-A, +B]     = PartialFunction[A, B]

ProcessBuilder (extends Source with Sink)

http://www.scala-lang.org/api/2.11.x/#scala.sys.process.ProcessBuilder

記号 機能
!! blocks until all external commands exit, and returns a String with the output generated.
!!< stdin + !!
! blocks until all external commands exit, and returns the exit code of the last one in the chain of execution.
!< stdin + !
#| pipe (Shellの | )
### コマンドが終了後、後続のコマンドを実行。
#&& コマンドのexit codeが0の時、後続のコマンドを実行。 (Shellの && )
#|| コマンドのexit codeが0以外の時、後続のコマンドを実行 (Shellの || )

FileBuilder (extends Sink with Source)

#<<

Source

記号 意味
#> Redirect stdout (overwrite)
#>> Redirect stdout (append)

Sink

記号 意味
#< Redirect stdin

AbstractBuilder (extends ProcessBuilder with Sink with Source)

!! !!< ! !< #| ### #&& #||

FileImpl (extends FileBuilder with Sink with Source)

#<<

ex-Scala Standard Library

以下のライブラリは2.11.0で標準ライブラリから分離されました。

Scala 2.11.0で標準ライブラリから分離されたライブラリのjarファイル - kmizuの日記 http://kmizu.hatenablog.com/entry/2014/04/24/005958

scala-xml

Lexical Syntax - 1.5 XML mode http://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html#xml-mode

XML Expressions and Patterns http://www.scala-lang.org/files/archive/spec/2.11/10-xml-expressions-and-patterns.html

XML expressions

ScalaXML(XMLリテラル)を直接扱うことができます。ただし、前述のとおりscala.xmlが標準ライブラリから外されたため、クラスパスに追加しないとコンパイルできません。*4

val a = <a>text</a> // = a: scala.xml.Elem = <a>text</a>

XML expressions may contain Scala expressions as attribute values or within nodes. In the latter case, these are embedded using a single opening brace { and ended by a closing brace }. To express a single opening braces within XML text as generated by CharData, it must be doubled. Thus, {{ represents the XML text { and does not introduce an embedded Scala expression.

XMLScalaの変数や式を参照する場合は{}を使用します。 参照ではなく単純に波括弧を使用したい場合は{{}}を使用します。

<a>{brace}</a>   // error: not found: value brace
<a>{{brace}}</a> // <a>{brace}</a>

val text = "text value"

<a>{text}</a>         // = <a>text value</a>
<a>{{text</a>         // = <a>{text</a>
<a>text}}</a>         // = <a>text}</a>

<a>{{{text}}}</a>     // = <a>{text value}</a>
<a>{{{{text}}}}</a>   // = <a>{{text}}</a>
<a>{{{{{text}}}}}</a> // = <a>{{text value}}</a>

単純に{{の組み合わせが、XML textの{に変換されています。

XML patterns

XMLリテラルはそのままパターンマッチングで使用することができます。 パターンマッチングの{}内ではScalaのパターンマッチが使用できます。

// 変数パターンによるマッチング
<a><b>text</b></a> match {
  case <a>{value}</a> => println(value)
  case _ => println("unmatched")
}

// <b>text</b>

<a><b>bold</b> text</a> match {
  case <a>{value}</a> => println(value)
  case _ => println("unmatched")
}

// unmatched

任意のXMLノードシーケンスにマッチさせるためには、Pattern Sequences(_*)を使用する必要があります。 (2つ目の<a>には<b>textの2つのノードが含まれていました)

// Pattern Sequencesによるマッチング
val list = <ul type="circle">
             <li>a</li>
             <li>b</li>
             <li>c</li>
           </ul>

list match {
  case <ul>{items @ _*}</ul> =>
    for (item <- items) {
      println(s"item: $item")
    }
}

// item: 
//                
// item: <li>a</li>
// item: 
//                
// item: <li>b</li>
// item: 
//                
// item: <li>c</li>
// item: 
//              

XMLでは改行や空白もTextノードなのでパターンに一致します。 for式のGeneratorではPattern `<-' Exprでパターンマッチングが使用できるので、これで必要なノードのみ抽出できます。

val list = <ul type="circle">
             <li>a</li>
             <li>b</li>
             <li>c</li>
           </ul>

list match {
  case <ul>{items @ _*}</ul> =>
    for (item @ <li>{_*}</li> <- items) {
      println(s"item: $item")
    }
}

// item: <li>a</li>
// item: <li>b</li>
// item: <li>c</li>

Scalaスケーラブルプログラミング 第2版 28.8 XMLを対象とするパターンマッチ (P.566~569)

Atom

https://github.com/scala/scala-xml/blob/v1.0.5/src/main/scala/scala/xml/Atom.scala

記号とは直接関係ありませんが、この後new Atom()_.isAtomが出てくるため簡単に説明します。 Atomはlabelが#PCDATA*5であるTextノードです。

例えば、XML patternsのサンプルコードで一致した改行や空白のTextノードは、isAtomtrueを返します。

Elem

%

  final def %(updates: MetaData): Elem =
    copy(attributes = MetaData.update(attributes, scope, updates))

指定されたattributes(MetaData)に更新した新しいElemを返す

NodeBuffer

&+

  def &+(o: Any): NodeBuffer = {
    o match {
      case null | _: Unit | Text("") => // ignore
      case it: Iterator[_]           => it foreach &+
      case n: Node                   => super.+=(n)
      case ns: Iterable[_]           => this &+ ns.iterator
      case ns: Array[_]              => this &+ ns.iterator
      case d                         => super.+=(new Atom(d))
    }
    this
  }

下記のような処理になっています。

  • IterableArrayのようなコレクションはiterator.foreachで要素個別に処理
  • Nodeであればそのまま追加、それ以外はAtom(Textノード)として追加
  • nullUnit、空文字のTextは無視

NodeSeq

XPath///がありますが、Scala//はコメント行として使用されるため、NodeSeqの記号ではバックスラッシュの\\\が使用されています。 (Scalaスケーラブルプログラミング 第2版 P.564より)

\

自身のノードを基準として条件に一致するノードを抽出します。 多機能なのでScaladocを引用すると下記のようになります。

  • this \ "foo" to get a list of all elements that are labelled with "foo";
  • \ "_" to get a list of all elements (wildcard);
  • ns \ "@foo" to get the unprefixed attribute "foo";
  • ns \ "@{uri}foo" to get the prefixed attribute "pre:foo" whose prefix "pre" is resolved to the namespace "uri".
  • this \ "foo"
    • 直下の子ノードで要素名(label)に一致するノードを返します。
    • XPathnode/fooに相当します。
  • this \ "_"
  • ns \ "@foo"
    • 自身のノードから一致する属性名の値を返します。
    • XPathnode/@attributeに相当します。
    • ただし、自身が単一のノード(this.length == 1)である必要があります。
      • 複数のノードの場合、空のNodeSeqを返します。
// 単一のノード
<a href="http://qiita.com/">Qiita</a> \ "@href" // = scala.xml.NodeSeq = http://qiita.com/

// 複数のノード
<a href="http://qiita.com/">Qiita</a>
<a href="https://teams.qiita.com/">Qiita:Team</a> \ "@href" // = scala.xml.NodeSeq = NodeSeq()
val project =
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/maven-4.0.0.xsd"></project>

// "xsi:schemaLocation"の値を取得
project \ "@{http://www.w3.org/2001/XMLSchema-instance}schemaLocation"

// = scala.xml.NodeSeq =
// http://maven.apache.org/POM/4.0.0
//                       http://maven.apache.org/xsd/maven-4.0.0.xsd

\\

XPath//に相当し、すべての子孫ノードを対象に抽出を行います。

\@

  def \@(attributeName: String): String = (this \ ("@" + attributeName)).text

Nodeの指定した属性名の値を取得する (\ "@attributeName"と動作は等価ですが、戻り値はString型です)

CachedFileStorage

+=

  /** adds a node, setting this.dirty to true as a side effect */
  def +=(e: Node): Unit

-=

  /** removes a tree, setting this.dirty to true as a side effect */
  def -=(e: Node): Unit

SetStorage (extends CachedFileStorage)

+=

  def +=(e: Node): Unit = synchronized { this.dirty = true; theSet += e }

-=

  def -=(e: Node): Unit = synchronized { this.dirty = true; theSet -= e }

*1:IntelliJはCtrl+B、EclipseはF3

*2:(読んで理解できるとは言っていない)

*3:本来、要素の順序は保証されませんが、わかりやすさのためソートして書いています

*4:REPLはscala.xmlパッケージが含まれているため、そのまま使用することができます

*5:PCDATA: Parsed Character Data