💡

演算子を渡すことを表現するのにクロージャを利用する

2022/06/09に公開

前回の記事でenumのケースをランダムに与えたい場合に、CaseIterableを利用して云々ということをまとめました。
https://zenn.dev/toaster/articles/2b1770caabd075


演算子は直接は渡せないということを書いていたのですが、実質的に渡しているような表現があることを思い出しました。省略されたクロージャです。

省略すること自体は本質的ではないので拘泥するものでもありませんが、演算子を渡すという目的に適った上ですっきりとした表現ができるので、前回の記事を踏まえて選択肢としてはこのような書き方もありかもしれないという共有です。

ただし、クロージャ式をぱっと見判別するのが難しい場合もあるため、その書き方が望ましいかどうかはコードの文脈次第であるとは思います。

コード例

enum PlusMinus {
    case plus
    case minus
    
    var calculation: (Int,Int) -> Int {
        switch self {
        case .plus:
            return (+)
        case .minus:
            return (-)
        }
    }
}

struct Problem {
    let plusMinus: PlusMinus
    let lhsNumber: Int
    let rhsNumber: Int
    
    var totalNumberWithClosure: Int {
        plusMinus.calculation(lhsNumber, rhsNumber)
    }
    
    var totalNumberWithSwitch: Int {
        switch plusMinus {
        case .plus: return lhsNumber + rhsNumber
        case .minus: return lhsNumber - rhsNumber
        } 
    }    

説明

enumのPlusMinusに、コンピューテッドプロパティとしてcalculationを設けています。この型は(Int,Int) -> IntというInt型の値を2つ受け取って、返り値を1つのInt型としているクロージャです。従って、プロパティの処理内での返り値はクロージャである必要があります。

 var calculation: (Int,Int) -> Int {
        switch self {
        case .plus:
            return (+)
        case .minus:
            return (-)
        }
    }

switch文ではenum型自身を受け取って、ケースによって分岐しています。
ここで、.plusというのは二つの値の和、.minusというのは差が求められることを表現することを目的としており、それを返り値の(+)(-)で表現しています。返り値はクロージャしか取れないわけで、つまりこれはクロージャの表現なんです。ぱっと見そう見えないのは、これが省略された形だからです。

Swiftの型推論やよくあるクロージャの処理としてダラー表記を利用すると、次のような省略された処理を書くことができるようになります。また、最終的には③のように書けるのですが、これは単に省略されているのではなく、そもそもこの演算子の定義が関数であるためであると思われます。

//クロージャ
var test: (Int, Int) -> Int

//①引数をダラー表記で書ける
test = { return $0 + $1 }
//②1行であればreturnは不要
test = { $0 + $1 }
//③2つの和の計算に曖昧さがなく自明であるため省略できる
test = (+)

//④ちなみに下記の場合はコンパイルエラーとなる
//Unary operator cannot be separated from its operand
test = + 
test = { + }

③の(+)のように書けるのは、次の定義の通り、二つの値を受け取り、それらの合計を返す関数だからですね。

static func + (Int, Int) -> Int
//Adds two values and produces their sum.

https://developer.apple.com/documentation/swift/integer-operators

見るとわかりますが、ここで利用したいクロージャまんまの定義なんですね。クロージャは無名関数とも言われるように、引数と返り値を見れば関数そのものです。そのため、クロージャ型に関数を渡すことも可能です。だから、このように記載できるわけです。

ちなみに④ではエラー文として、次のように表記されています。

Unary operator cannot be separated from its operand

単項演算子(+1とかー2とか)として扱われているため、被演算子(1や2の部分)と切り離すことはできないと書かれているように、このような書き方だと二項演算子として認識されないんですね。括弧で閉じることによって、それが一つの関数として扱われることを示すことができるということなのではないかと思います。

実は括弧で閉じることの意味がわかっていなかったのですが、記事に整理していて気づきました。

Discussion