読者です 読者をやめる 読者になる 読者になる

【Swift】Appleの新言語「Swift」のリファレンスを読む(18) - Advanced Operators

注意

  • あくまでメモ書きなので細かい部分を端折りますし、色々間違ってるかもしれません。ちゃんとした内容は原文を読んでね。
  • コード例は基本的に原文からそのまま引用していますが、ちょっとした注釈をつけたり、統合したりしています。
  • SyntaxHighlighterが対応してないので微妙に読みにくいです。SyntaxHighlighterはこちらのものを使用させて頂いてます。
  • 他言語にそっくりな部分でも指摘しない。(自戒)

Advanced Operators(応用演算子

ようやく最後の章…なんですが、Language Referenceの方にしか説明がないものがちらほら見つかっているので、うーんって感じです。

具体的にはdynamicType、Attribute、Literal Expression(__FILE__とか)あたりです。他にも紹介されてない構文とかキーワード(nonmutatingとかunowned(safe|unsafe))とかちょいちょいあるんですが、左記の三つを一つの記事にまとめて終わり、ってことで〆ようと思っています。

まぁそれはともかくとして、今回はBasic Operatorsの説明では挙げなかったビット演算系の演算子の説明やオーバーフロー演算子演算子オーバーロードの説明をしていきます。

Bitwise Operators

原文に載っている図が中々わかりやすいので、そちらを見ながらどうぞ。

とは言え、ビット演算のやり方が知りたいような人にとっては見なくてもわかるのかもしれないですが。

まずは論理否定(NOT)です。チルダを使います。

let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // equals 11110000

次は論理積(AND)です。これはアンパサンドを使います。

let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // equals 00111100

次は論理和(OR)です。パイプラインを使います。

let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // equals 11111110

そして排他的論理和(XOR)。これはキャロットを使います。

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // equals 00010001

勿論シフト演算も可能です。≪で左シフト、≫で右シフトです。

let shiftBits: UInt8 = 4 // 00000100 in binary
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
shiftBits << 5 // 10000000
shiftBits << 6 // 00000000
shiftBits >> 2 // 00000001

let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16 // redComponent is 0xCC, or 204
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent is 0x66, or 102
let blueComponent = pink & 0x0000FF // blueComponent is 0x99, or 153

このセクションの一番下に符号ビットがどうのこうのとかそう言う話もありますが、日本語で書いてあっても解説できる自信がないので興味がある方はご自分でお読みください…って言うかそもそもこんなブログの記事なんか読まないで普通に原文読んでると思うんですが、どうでしょう。

Overflow Operators

Swiftには意図的にオーバーフローを引き起こす演算子が用意されています。

以下のコードを見てみましょう。

var potentialOverflow = Int16.max
// potentialOverflow equals 32767, which is the largest value an Int16 can hold

potentialOverflow += 1
// this causes an error

コメントにも書いてある通り、Int16.maxに1を足したらエラーになります。まぁそりゃそうだって感じです。

が、+の前にアンパサンドをつけるとエラーになりません。

var willOverflow = UInt8.max
// willOverflow equals 255, which is the largest value a UInt8 can hold

willOverflow = willOverflow &+ 1
// willOverflow is now equal to 0

Int16.maxに対して同じことをやったら-32768になります。

このようにアンパサンドを前につけた四則演算(と%)用の演算子のことをオーバーフロー演算子と呼びます。そのまんまですね。

// アンダーフローも起こせる
var willUnderflow = UInt8.min
// willUnderflow equals 0, which is the smallest value a UInt8 can hold
willUnderflow = willUnderflow &- 1
// willUnderflow is now equal to 255

// Zero Divideも出来てしまう
let x = 1
let y = x &/ 0
// y is equal to 0

Precedence and Associativity

Precedenceは優先度、Associativityは結合、ですね。

まぁ、要は算数のお話です。後で説明する演算子オーバーロードの時に関わってきます。

2 + 3 * 4 % 5
// this equals 4

// 実際には以下の順番で実行される
2 + ((3 * 4) % 5)
2 + (12 % 5)
2 + 2

Operator Functions

じゃあ実際に演算子オーバーロードしてみましょう。

先に言っておきますが、代入演算子(=)と三項演算子(a ? b : c)はオーバーロードできません。悪しからず。

それじゃあまずは中置演算子です。

struct Vector2D {
    var x = 0.0, y = 0.0
}

// 中置演算子の場合は@infixと言うAttributeを使用する
@infix func + (left: Vector2D, right: Vector2D) -> Vector2D {
    return Vector2D(x: left.x + right.x, y: left.y + right.y)
}


let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector is a Vector2D instance with values of (5.0, 5.0)

次は前置演算子です。

// 前置演算子の場合は@prefix
// 後置演算子であれば@postfixを使用する
@prefix func - (vector: Vector2D) -> Vector2D {
    return Vector2D(x: -vector.x, y: -vector.y)
}

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative is a Vector2D instance with values of (-3.0, -4.0)
let alsoPositive = -negative
// alsoPositive is a Vector2D instance with values of (3.0, 4.0)

複合代入演算子の場合はassignmentと言うAttributeを使用します。

@assignment func += (inout left: Vector2D, right: Vector2D) {
    left = left + right
}

var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original now has values of (4.0, 6.0)

prefixやpostfixと併用することでインクリメントやデクリメントを表現することもできます。

@prefix @assignment func ++ (inout vector: Vector2D) -> Vector2D {
    vector += Vector2D(x: 1.0, y: 1.0)
    return vector
}

var toIncrement = Vector2D(x: 3.0, y: 4.0)
let afterIncrement = ++toIncrement
// toIncrement now has values of (4.0, 5.0)
// afterIncrement also has values of (4.0, 5.0)

また、比較演算子オーバーロード可能です。

@infix func == (left: Vector2D, right: Vector2D) -> Bool {
    return (left.x == right.x) && (left.y == right.y)
}

@infix func != (left: Vector2D, right: Vector2D) -> Bool {
    // 事前にVector2Dの「==」をオーバーロードしているのでこう書ける
    return !(left == right)
}

let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)

if twoThree == anotherTwoThree {
    println("These two vectors are equivalent.")
}
// prints "These two vectors are equivalent."

Custom Operators

Swiftで使用できる演算子を組み合わせて自分だけの最強の演算子を作ることもできます。

operator prefix +++ {}
@prefix @assignment func +++ (inout vector: Vector2D) -> Vector2D {
    vector += vector
    return vector
}

var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)

また、自作の演算子に関しても優先度と結合規則を設定することができます。

operator infix +- {
    // precedenceに設定できる値は0〜255(デフォルトは100)
    precedence 140
    // associativityに設定できるのはleft, right, none(デフォルトはnone)
    associativity left
}

func +- (left: Vector2D, right: Vector2D) -> Vector2D {
    return Vector2D(x: left.x + right.x, y: left.y - right.y)
}

let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)

このコード例だとちょっと説明が足りてないので、Wikipediaに書いてあるやつも引用してみます。

// 中置演算子 ** を定義する
operator infix ** { precedence 155 associativity right }
 
// ** 演算子を Double型のべき乗として実装する
@infix func ** (lhs: Double, rhs: Double) -> Double {
    return pow(lhs, rhs)
}
 
2 ** 3  // 8.0(2の3乗)
 
0.5 * 2 ** 3 ** 2  // 256.0
// **演算子は *演算子よりも優先順位が高く、右結合として定義したので
// この式は 0.5 * (2 ** (3 ** 2)) の意味となる

とまぁ、書いてある通りです。優先度に関してはleftならleft同士で、rightならright同士で比較するみたいです。

そこまで面倒なことを考えるならメソッドなり関数なりを作ってしまった方がいいんじゃないかって気もしますが。