第6話 : メソッドをどこに備えよう? #ムックムックラジオ

熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ

mookmook radio 第6話では『メソッドをどこに備えよう?』というテーマで繪面さんと談笑しました。

そんな話の中で触れた話題や具体的なコードをここで補足していますので、このブログを見ながら放送を聴くと、より楽しく聴いてもらえるかもしれません。


ラジオステーション mookmook radio で配信中の 熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ にて、プログラミングに関してあれこれ楽しく話をさせてもらってますけど、そんな番組と一緒に 番組内で話したことの補足があったらもっと楽しいかな って思って、綴ってみることにしました。

コンセプトとしては、プログラミングが大好きな2人で、プログラムの作り方というよりは、プログラミングで遊ぶ中で、見つけたことや感じたこと、いろんなことに花を咲かせて楽しもう、みたいな番組になっています。今は基本的に Swift 言語を中心に談笑してますけれど、基本的にはプログラミングのことならなんでもアリな番組です。

第6話『メソッドをどこに備えよう?』

今回のテーマは 『メソッドをどこに備えよう?』 です。Swift の private とか public といったアクセス修飾子の話から広がった、メソッドをどこに備えるべきかということについて2人であれこれ思いを巡らせてみました。

そんな番組の中で話した事柄について、ここでいろいろ補足してみたいと思います。番組の流れに沿いながら、気になるところをピックアップして綴っていくので、番組を聴きながら眺めていくと、きっとより楽しめると思います。

なお、ここでは僕自身が1人で放送を聞き返しながら、感じたことを綴っていくので、番組内で2人で話しているときとはまた違った方向に話が進んでいくと思います。同じように、番組を聴いたりここを眺めてくれる方々も、それぞれの想いを自由に重ねてもらえたら嬉しいです。

01:48 ~ オープニング

今回は、第5回『アクセスコントロール』で広がった話題の 『プライベートメソッドをどこに持たせるか』 について、いろいろ想像を膨らませてみました。

状況としては、とある View Controller が nickname プロパティーを持っていて、その View Controller 自身が isNicknameValid というプライベートメソッドを持ち、それを使って nickname プロパティーの妥当性を判定する。そんな isNicknameValid を View Controller が持たせていいのか、それとも nickname プロパティーの String 型に所属させるのか、そもそもこういう Validation メソッドを持たせるとしたらどこが適切なのか、そんな感じです。

ちなみに private で検討しているのは、それを外側に公開する必要がない状況を想定 しているためです。

class ViewController {

	var nickname: String

	// ここに private メソッドとして持たせる?
	private func isNicknameValid() -> Bool { ... }
}

// それとも nickname プロパティーの型に拡張して搭載する?
private extension String {

	func isValid() -> Bool { ... }
}

03:32 ~ メソッドをどこに備えよう?

まずは前半、テーマの通り 『メソッドをどこに備えると適切なのかな?』 という観点で話を膨らませてみました。

03:36 ~ メソッドを "備える"

どこにメソッドを持たせようーという話を始めて、まずはすぐの指摘ですけど『メソッドを "置く" というより "備える" ていう感じ?』っていうところ、なんかとってもいいなって思いました。

そう、とりわけ構造体や列挙型などの値型に焦点を当てたとき、型にとってのメソッドって その型がどう振る舞えるか を表現するもののひとつな気が自分はしていて、そう捉えると、持たされたりするよりは、備わっていると捉えた方が、しっくりくるような感じがします。今回はクラス型、参照型が中心になっていて、それでもこの考え方が通用するかはわかりませんけど、それでも基本の方向性として "備える" を意識して考えると、良い方向にイメージが膨らんでいきそうな予感がします。

それと、考える上での方向性ね。現実ばかりに気を取られないで、理想ならここ、現実ならここ、そんな風に検討していけたらいいねっていうのも、個人的には大事なところかなって思いました。

05:32 ~ hasValidNickname

今回の話をするために、第5話『アクセスコントロール』を何度か聞き直していたんですけど、その中で繪面さんがつぶやいた『has』というのが、とても印象に残りました。

private extension ViewController {
    
	func hasValidNickname() -> Bool { ... }
}

自分は もしかしてこれで完璧なんじゃないかな? って思ったんですけど、確かに、繪面さんの言うように『View Controller に置くのであれば』『ここに備えるのが正しいかは別』という視点を忘れないところ、とっても良いですね。こういう視点を忘れないことって 他の可能性を潰さない とっても大事な着眼点だと思うんですよね。

09:23 ~ 所属がないと仮定したとき

仮にこの Validation メソッドに 所属がないと仮定 したとき、フリーの関数、すなわちグローバル関数になると思うけれど、どうだろうというお話。

private func isNicknameValid(_ nickname: String) -> Bool { ... }

Swift の API Design Guidelines には 所属がある限り所属させるのが望ましい という方針はあるものの、とりあえず、今回の話が湧いた背景にある『実務での現実問題として、なかなか nickname プロパティーを、独自の Nickname 型で定義するのも難しい』というところがあるので、仮に所属がないものとして、まずは その雰囲気を感じてみたら何か発見があるかもしれない というのが趣旨のお話です。

本当に所属が見えなかったとしたら、たぶん、これでかなり妥当な線を突いているはず。

11:26 ~ 欲を言えば Nickname に所属させたい

そして続いて、やっぱり nickname 自体に備えたいという話。ここもすごく良いなって思って、やっぱりそう、理想的に感じる方向へどうにかすり寄せる方法はないか、そんな風に考えるのって、とっても楽しいところです。

private extension String {

	var isValid: Bool { ... }
}

// こうして nickname そのものに対して、それが有効かどうかを問い合わせられる。
vc.nickname.isValid

この話の中で『isValid だけだと何の有効性判定かわからないから』っていうのは、こういうことです。

private extension String {

	var isValidAsNickname: Bool { ... }
}

// こうして nickname そのものに対して、それが有効かどうかを問い合わせられる。
vc.nickname.isValidAsNickname

なんでここで分かりにくさが出てくるかというと、今回の拡張が String に対して行われているためですね。これが Nickname 型を作れる環境であれば、その型に対して isValid を備えればとても明瞭なのですけど、今回は諸事情により、汎用的な『文字列 (String)』に対して isValid を備えているため『その文字列を、何としてみたときに、正当か』という情報が抜けてしまうんですよね。

こういう風に 型自身の説明力が弱いとき には、メソッドなどの名前を使って不足情報を補うことはけっこう重要みたいです。

12:55 ~ Objective-C の発想でみると?

View Controller が hasValidNickname を持つことについて、以前 Objective-C の感覚で見ると、もしかしてけっこう自然? 慣習的にその手法が選ばれていた? っていう印象がしたので、そんなところについても話してみました。

なぜ、慣習的にそうだったように感じるのか。

Objective-C ならカテゴリ拡張という手法が取れるけれど、まずそれは一般的ではなかった。そして C++ だと std::string を拡張することはできないし、継承して Nickname 構造体を作るみたいなことって、少なくとも自分の身近の感覚ですけど、あまりしなかったように思えるんですよね。それよりは、そこまでオブジェクト指向にこだわらないで、フリーな関数で説明していたような気もして。

自分は本当、String に isValidAsNickname メソッドを拡張しようという発想が、Swift と出会うまでは全くなくて、だからなおさら、ときどき目にする View Controller に private func を持たせるという判断が、必ずしも『これが最適解』と判断されて載せられたものとは限らないかな? という印象でした。そして今回の談笑を踏まえて、主体にメソッドを備えるという発想に慣れ親しんでいる人もたくさんいるわけで、そういう人が『これが最適解』と判断して載せている可能性もあるかも、そんな話に広がって、とても楽しかったです。

想像が至らないのも、また楽しいですよね。

19:22 ~ 静的メソッドとして持たせる

ここで、どうしても繪面さんに聞いてみたかったことがあって、この isNicknameValid を View Controller の静的メソッドとして持たせたらどうだろうということについて、尋ねてみました。個人的にはけっこう 爆弾投下 なつもりで聞いたところでもあるし、これから先に "名前空間" というテーマで話せたらいいなと思うところでもあったりとかして。

具体的には、こういうことです。

private extension ViewController {
    
	class func isNicknameValid(_ name: String) -> Bool { ... }
}

実際に投げかけてみると 期待通りの反応 で面白かったです。

自分も最初、これを思いついたとき 『いかん… これはヤバい…』 って思ったところなんですよね。でもなんか、そう思った後で次みたいに、それをフリーな関数に書き換えたときに 『あれ?何この既視感…』 って思ってしまって。そう、なんか 同じに 見えたんですよね。

private func isNicknameValid(_ name: String) -> Bool { ... }

これに対して『フリーな関数になった瞬間、オブジェクト指向みたいな考えが外れる』という返答をもらって、この感覚もなんか良いなって思いました。そう、そんなふうに ちょっと視点が変わるだけでともすると全然違う世界が広がったり して、そんなところもプログラミングの醍醐味かなって思います。

20:52 ~ フリーな関数の印象

ちなみにこの話の途中で出てきた abs というのは、絶対値を求める関数で、C 言語の時代とかからお馴染みなものです。

let absoluteValue = abs(value)

所属というのを意識すれば、次みたいに absoluted みたいなものを備えることになるはずですけど、こういう文化的、常識的なものについてはフリーな関数で表現するのが、今の Swift の方針的にも推奨される方法です。

// 主体に対して、絶対値を取る振る舞いを備える。
extension SignedNumber {
    
	var absoluted: Self {

		return -self
	}
}

// 主体が、絶対値という表現力を持つ。
let value = -10

value.absoluted

AbsolutedValuable

もっとも、今の Swift 3.0.1 でも AbsoluteValuable というプロトコルがあって、そこに abs というメソッドが備えられているのですけれどね。なぜだか static func で定義されていて、なぜだか FloatingPoint 系の型にしか備えられていないのですけれど。

protocol AbsoluteValuable : SignedNumber {

	static func abs(_ x: Self) -> Self
}

そしてしかも、フリーな関数の abs は、なぜだか AbsoluteValuable ではなく SignedNumber を想定していたりして面白いところです。

func abs<T : SignedNumber>(_ x: T) -> T

実際のところ、SignedNumber を abs に通した流れとしては、次のような流れになっているようです。

  1. ExpressibleByIntegerLiteral でもあるので、リテラル 0 からインスタンスを生成できる。
  2. Comparable でもあるので、0 から作ったインスタンスと大小関係を比較できる。
  3. SignedNumber は prefix な - 演算子を持つので、0 より小さければ符号を反転できる。

これが AbsoluteValuable を持ったインスタンスを通した場合は、こういった一般的な解法は、サクッと static func abs でバイパスされる様子でした。

21:30 ~ 静的メソッドって何者?

これも面白いところで、ずっと気になっていたんですけど、こんな話ができたのも良かったです。これについては、この放送でも興味深い話ができましたけど、より広く話を膨らませるのは、この先のたぶん近いうち、名前空間をテーマにして、話していきたいなって思ってます。

それにしても、なるほど、改めて放送を聞いていると、静的メソッドと静的な保存型プロパティーの間にも、もしかすると意味合い的な違いが何かあるかもしれませんね。そんなところも、これから想像を膨らませてみたい心地がしました。

25:35 ~ 静的メソッドを考える

後半は、ひとまずメソッドを備える場所を考えるのを休憩して、前半で話が膨らんだ 静的メソッド について、2人で認識を合わせてみることにしました。見解がずれたまま話を進めるその怖さを見逃さずに感じられる、これもすごく良い感性に感じます。

というのも、自分は static funcfinal class func"同じもの" と認識していて、そんな前提で前半の話をしていたんですよね。でも良く考えたら、それが本当に同じものかは分からない。もし違っていたとしたら、それも絡むメソッドの所属についての話が、適切な方にたどり着けない可能性は大いにあり得ます。

25:50 ~ 普通のメソッドと静的メソッドの所在

とりあえず、何かを掴むきっかけとして、ざっくりと、普通のメソッドと静的メソッドとが 何に関連づけられているか、そんなところを考えてみると、次のような感じでしょうか。

対象 定義キーワード 関連付いている対象
普通のメソッド func インスタンス
静的メソッド static func

これをたたき台にしたときに、どういう違いが見えてくるか、番組内ではそんな視点で眺めてみることにしました。

それにしても、このブログを書き終えた後、読み返してみていても思うのですけど、自分はやっぱり static funcclass func同じ気分で "静的メソッド" と表現する癖 が抜けていなそうですね。この辺りをちゃんと意識すると、前者が "静的メソッド" で、後者が "クラスメソッド" で、ともするとそれ以外はないくらいの勢いなような気もしますけれど、もしかすると両方をまとめて "静的メソッド" って表現できる可能性も捨てきれないところだったりもして、気にしつつも今回は少し緩めに表現してみることにします。それが功を奏すかはわからないですけれど。

26:42 ~ クラスメソッドのオーバーライド

話の中で出てきた クラスメソッドのオーバーライド は、具体的にはこのようなコードです。

class Base {
        
	class func something() -> Any { return "Base" }
}
    
class Sub : Base {
        
	override class func something() -> Any { return "Base" }
}
    
Base.something()    // "Base"
Sub.something()     // "Sub"

// Sub のメタタイプを Base のメタタイプに入れられ、そこから class func を実行すると、継承先が正しく呼ばれる。
let meta = Sub.self as Base.Type

type(of: meta)      // "Base.Type.Type"
meta.something()    // "Sub"

30:00 ~ class func と static func の区別

自分は final class func を継承させようとしてしまって、かつてのその時に static func はオーバーライドできない みたいにエラーメッセージで指摘された記憶があって、それ以来、どちらもひっくるめて 『静的メソッド』 って呼ぶようになっていたんですよね。今はそれを再現する方法を忘れてしまって、本当にそうだったのかはわからないですけど。

その辺り、良い認識をさせてもらえて良かったなって思いながら、話を進めていたら、さっそくまた混同していることを指摘されて、相変わらず無自覚でしゃべっていたものだから、ついつい面白かったです。

32:37 ~ class func ってなんだろう

そしてここの指摘、すごく面白かった。

まあ、正解がどれとかみたいな、考えの違いをわざわざ間違いと指摘してたら談義にならないと思うのと、想像を膨らませる上ではどうでもいいかなって思うので、そういうの抜きに『class func はクラスに寄った感じ、static func もクラスそのものの概念に居るものとしては近いけれど、でも class func よりは遠く感じる』という話が聞けて嬉しかったのと、そしてとっても興味深いところでした。

もし、コードを見て『static func と final class func をみた時に、印象が違う』という話、ここもとても興味深い視点です。

収録を終えて、それからひとりで名前空間というものも含めて考えを膨らませていた時に、そんな違いを確かに感じられるような気がしたりしました。型を名前空間という視点で見たときには、確かに static func という視点も class func という視点も同じように見える、そしてクラス、とりわけオブジェクト指向という観点で見たとき、class func はポリモーフィズムを想定した存在、要は "名前空間" ではなく "型の一環" として存在している様子が感じられる心地がしました。

そうしたら、確かにクラスに寄り添っている、そして static func とは全くニュアンスが違う、でも一緒、そんな直感通りの良いバランスを描いてくれていそう。ちなみに名前空間については、ここでは詳しく語りませんけど、今後の番組内で改めて2人で探検していくつもりです。

35:00 ~ final class func と static func の違いは、実際から垣間見れるのか

ちなみに 内部的な違いはどうだろう? という話、番組中で試すには時間がもったいなくて省きましたけど、それについて、ここで紹介しておきますね。とりあえず、次のコードを Swift 中間言語にする過程で、その違いが現れるかを調べてみます。

クラスメソッド

class Object {

	class func something() { }
}

静的メソッド (static func)

class Object {

	static func something() { }
}

Final クラスメソッド

class Object {

	final class func something() { }
}

違いの現れ方

こうした時に、まず、型情報を含まない AST での違いは全く見られませんでした。該当箇所に限ったコードを抜粋すると、どれも次のようになります。

(func_decl "something()" type='<null type>' type

そして、これにタイプチェッカーで型情報を付加した時に、次のように少し違いが見られました。具体的には static func と final class func は同じで、class func が少し違ったコードになります。該当箇所はこのような感じです。

// static func と final class func は access に final が付与される。
(func_decl "something()" type='(Object.Type) -> () -> ()' interface type='(Object.Type) -> () -> ()' access=internal final type

// class func の access には final が付与されない。
(func_decl "something()" type='(Object.Type) -> () -> ()' interface type='(Object.Type) -> () -> ()' access=internal type

そして SIL では、仮想テーブルに違いが見られました。具体的には static func と final class func は仮想テーブルに登録されず、class func は仮想テーブルに登録されます。ところで メタタイプにも仮想テーブルがある の、そういえば面白いかもしれないですね。

// static func と final class func は仮想テーブルに登録されない。
sil_vtable Object {
  #Object.deinit!deallocator: _TFC1_6ObjectD    // Object.__deallocating_deinit
  #Object.init!initializer.1: _TFC1_6ObjectcfT_S0_  // Object.init() -> Object
}

// class func は、仮想テーブルに登録される。
sil_vtable Object {
  #Object.something!1: _TZFC1_6Object9somethingfT_T_    // static Object.something() -> ()
  #Object.deinit!deallocator: _TFC1_6ObjectD    // Object.__deallocating_deinit
  #Object.init!initializer.1: _TFC1_6ObjectcfT_S0_  // Object.init() -> Object
}

以上から、少なくとも内部的な解釈としては static func も class func もかなり近い扱いになっている様子が窺えました。継承性以外の決定的な違いは見られなかったので、ここから大きな解釈の違いを発見するのは難しいかもしれません。そして static func と final class func は違いが見られませんでしたけど、これを以って『同じ』と解釈するのは早そうです。

37:36 ~ プロトコル定義からの観察

そう、ここもとっても面白いところです。プロトコル定義の観点で見ると static funcclass func も、どちらとも境なく static func で表現するんですよね。

ちなみにここの『クラスに限定した時にどうなる?』という話の中で書いたコードは、次のものでした。プロトコルをクラスに限定したからといって、プロトコルの中で class func を規定できるようになるわけではないというのが要点です。

protocol Something : class {

	// ERROR: Class methods are only allowed within classes; use 'static' to declare a static method
	class func doSomething()
}

他にも、プロパティーをプロトコルでは var で規定するけれど、これについても話の中にある通り、様々な表現に適用できたりするものだから、話を戻すとこれを以って、両者は同じ とは判断できない ところが、面白いところだったりします。

40:53 ~ インスタンスメソッドがクラスメソッドっぽく存在すること

ここでは、まだいまいち静的メソッドが何者なのかが窺えない中で、判断材料を増やすべく、似たような面持ちを持つものを挙げてみています。ここでまた、自分が static func と class func の言葉を一緒にしてしまってますが、喋っていることは class func についてでした。

ここの インスタンスメソッドがクラスメソッドっぽく使える 例は、具体的にはこのようなコードです。これがちゃんと、親のメタタイプから継承先の機能を呼び出せるところから見ても class func と同じように振舞っている様子が窺えます。

struct Value {
    
	func action() -> Any { return self }
}

// `func action() -> Any` が `class func action(_:Self) -> () -> Any` として使える。
let value = Value()

// 次の二つは同等の呼び出し方
Value.action(value)()
value.action()

そしてイニシャライザーも、似たような様子が窺えるものとして挙げてみました。まず、型との結びつきの話は次の通りです。普通は型名にそのまま引数を渡してイニシャライザーを呼び出しますけど、こうして 明示的に init を指定して呼び出すことも可能 というお話でした。

class Base {
        
	init(source: Int) { }
}
    
let obj = Base.init(source: 10)

この話だけだと、いわゆる static func と同じ性質です。そして イニシャライザーが隠蔽される というのは、次のようにクラスを継承して、継承先で異なるイニシャライザーを載せると親のイニシャライザーは呼べなくなるというお話です。こんな性質があるから class func とはちょっと性格が変わってくるんですよね。

class Base {
        
	init(source: Int) { }
}
    
class Sub : Base {
        
	init(data: Int) { ... }
}
    
let base = Base.init(source: 10)

// Sub では init(source:) が隠蔽される。
// error: incorrect argument label in call (have 'source:', expected 'data:')
let sub = Sub.init(source: 10)

そして、ここで required init を使うと、まるで class func のような動きを見せてくれるようになる、そんな話で使ったコードは次の通りです。

class Base {
        
	required init(source: Int) { }
}
    
class Sub : Base {
        
	init(data: Int) { super.init(source: data) }
        
	required init(source: Int) { super.init(source: source) }
}

let meta = Sub.self
    
// 普通のイニシャライザーはメタタイプから呼び出せませんが、
// error: constructing an object of class type 'Sub' with a metatype value must use a 'required' initializer
//meta.init(data: 10)
    
// required init ならメタタイプから呼び出せます。
meta.init(source: 10)

ほんとこの辺り、面白いですよね。

こうやって遊んでみて、収録の中では「もうちょっといろいろ遊んでみよう」みたいな話をしましたけれど、この世界観と名前空間、そしてさらにオブジェクト指向を含めて想像を膨らませてみると、この static func と class func とのなんとなく意識というか表情の違いみたいなものが、もしかするとはっきりと見えてきたような心地もします。

class func を static func でオーバーライド

ちなみに余談ですけれど、class func を static func でオーバーライドできたりするので、簡単ながら紹介しておきますね。

class Base {
        
	class func something() -> Any { return "Base" }
}
    
class Sub : Base {
        
	override static func something() -> Any { return "Sub" }
}

// この場合でも、ちゃんと Base のメタタイプから Sub の静的メソッドを呼び出せます。
let meta = Sub.self as Base.Type
    
meta.something()    // "Sub"

収録の感想と、次回のお知らせ

こんな感じで、当初の『メソッドをどこに備えよう?』という話からの広がりがとっても面白かったです。

後半の静的メソッドの話も、単なる脱線に留まらなくて、そこからちゃんと静的メソッドが何者という謎を探るための足がかりとしてとっても役に立ちました。もっとも、メソッドを備える上で静的メソッドを見つめる必要に迫られたわけで、そこでの疑問を見逃さずに見つめられたからこそ、その存在意義を想像できそうな予感がするまでに至れたこと、そして巡ってきっとメソッドをどこに備えたらいいかを静的メソッドという観点も含めて想像できるようになりつつある気がするところ、すごくいいなって思いました。

改めて、こういう些細なところに疑問を覚えて立ち止まれることの凄さ、素晴らしさみたいなものを、体験させてもらえた心地がしました。こういうの、とっても難しいんですよね。


それでは、熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ 、次回もどうぞ聴いてくださいね。次回、第7話は本日 の 18:30 頃に配信が開始されました。テーマは『実際のアプリ開発現場の世界の列挙型たち』です。

どうもありがとうございました。