- この記事は 2016年07月03日 にQiitaへ投稿した内容を転記したものです。
- 本記事は執筆から1年以上が経過しています。
これなんなの?
「Scalaの記号はググラビリティ低すぎて調べようがないし全くわからん…せめて定義位置とか実装がわかれば…」という自分のためのメモ、いわゆるチラシの裏です。
とりあえずScalaの記号については、ソースコードをダウンロードして下記の正規表現でgrepしました。
^[^*/]* (?:def|class|object|type|val) ([^0-9A-Za-z" \(\[]+)(?:[ \(\[]|$)
基本、わからない記号見つけたら定義元に飛んで ((IntelliJはCtrl+B、EclipseはF3))、ソースを読んでます。(読んで理解できるとは言っていない)
- IDEで定義元に飛べない記号(
_*
など)は、言語仕様の可能性が高いので言語仕様を読みましょう。 - 記号の使用例がわからない場合、そのライブラリのテストコードを読むと良いケースがあります。
- 急に
()
が出てきた場合、カリー化された引数かapply()
です。apply()
の場合、一時的に書き換えてショートカットキーで飛ぶか、検索してapply()
の定義を確認します。
https://twitter.com/kmizu/status/724913410962558976
Language Specification
バッカス・ナウア記法
Scalaの言語仕様では、文法の定義にバッカス・ナウア記法(BNF: Backus-Naur form)が使用されています。使用されているBNFの記号と意味は下記の通りです。
記号 | 意味 |
---|---|
::= |
定義 |
‘…’ |
文字列 |
`…' |
文字列 |
| | OR |
( … ) |
グループ化 |
{ … } |
0回以上の繰り返し |
[ … ] |
オプション (0回 or 1回) |
// |
コメント (注釈) |
“…” |
コメント (注釈) |
記号ではありませんが、nl
とsemi
の意味は下記の通りです。
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
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.x
をy
という名前でimportすることができます。
Session
やModule
等のかぶりやすい名前のtypeを複数importする際に便利なことがあります。
-
java.sql.Date
をSQLDate
として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での使用例です。
sealed abstract class Option[+A] extends Product with Serializable {
self =>
/** 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/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
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
???
/** `???` 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 thatA
is a subtype ofB
.
<:<を使ってメソッド引数の型を限定する (例: IntもしくはBooleanもしくはString
http://qiita.com/mtoyoshi/items/fbf2c744e3c7fd69a438続<:<を使ってメソッド引数の型を限定する(厳密に)
http://qiita.com/mtoyoshi/items/13ac0500aa23fb4fa0d4
=:=
An instance of
A =:= B
witnesses that the typesA
andB
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を取る ((本来、要素の順序は保証されませんが、わかりやすさのためソートして書いています))
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 cons
のapply()
が実行され、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
ScalaはXML(XMLリテラル)を直接扱うことができます。ただし、前述のとおりscala.xmlが標準ライブラリから外されたため、クラスパスに追加しないとコンパイルできません。 ((REPLはscala.xmlパッケージが含まれているため、そのまま使用することができます))
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.
XMLでScalaの変数や式を参照する場合は{}
を使用します。
参照ではなく単純に波括弧を使用したい場合は{{
や}}
を使用します。
<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
((PCDATA: Parsed Character Data)) であるTextノードです。
例えば、XML patternsのサンプルコードで一致した改行や空白のTextノードは、isAtom
でtrue
を返します。
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
}
下記のような処理になっています。
-
Iterable
やArray
のようなコレクションはiterator.foreach
で要素個別に処理 -
Node
であればそのまま追加、それ以外はAtom
(Textノード)として追加 -
null
、Unit
、空文字の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)に一致するノードを返します。
XPathのnode/foo
に相当します。
this \ "_"
直下の子ノードでAtom以外のノードすべてを返します。(ワイルドカード)
XPathのnode/*
に相当します。
ns \ "@foo"
自身のノードから一致する属性名の値を返します。
XPathのnode/@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()
ns \ "@{uri}foo"
名前空間プレフィックスのついた属性名(prefix:attribute
)の値を返します。
Mavenのpom.xmlを例にすると下記のようになります。
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 }
Top comments (0)