第7話 : 実際のアプリ開発現場の世界の列挙型たち #ムックムックラジオ

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

mookmook radio 第7話では『実際のアプリ開発現場の世界の列挙型たち』というテーマで繪面さんと談笑しました。

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


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

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

第7話『実際のアプリ開発現場の世界の列挙型たち』

今回は、繪面さんが日常の中で感じた列挙型の姿について、もしかしてこの扱い方もぜんぜんありなんじゃないかな?っていう感じで、角度を変えて改めて Swift の列挙型を見つめ直す、そんなお話になりました。理想と現実、その狭間でさまざまな考え方に触れて、そしてそこから妥協ではなく最良の手を見つけようと模索して迷うって、きっと エンジニアとして大事な資質なのかもしれないな って感じさせてもらえたひと時でした。

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

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

03:10 ~ 列挙型の Raw 型について

開幕からテーマに沿ってさっそくあれこれ想像を膨らませていましたけれど、前半は特に Swift の列挙型における Raw 型 について、その存在意義的なものを見つめ直す感じになりました。

03:20 ~ 列挙型のおさらい

せっかくなのでここでも列挙型について補足しておきますね。これらを軽く踏まえておくと話を想像しやすいかもしれません。まず、Swift の列挙型の基本的な定義の仕方はこんな感じです。

enum 型の名前 {

	case 候補1
	case 候補2
	case 候補3
}

そして 列挙子に任意の値を添えられる という話は、こんな感じです。これは付属値といって、列挙子ごとに自由に添える値を決められて、使うときにはそれを添えて扱えるようになっています。これは今回の話の主軸になる Raw 値とは 別のお話 です。

enum Device {
        
	case iPhone(model: String)
	case iPad(model: String, cellular: Bool)
	case appleWatch(series: Int)
	case applePen
}
    
let device = Device.appleWatch(series: 2)

そして Raw Value を割り当てることができる というのはこういう感じ。こちらが 今回の話で特に注目しているところ になります。

enum Language : Int {
    
	case z80 = 0
	case c = 1
}

そのあとの、メソッドや計算型プロパティーで振る舞いを添えたり、静的な保存型プロパティーを持たせるという話は、今、脈絡なくざっくりと書いたので的確な表現にはなっていないと思いますが、次のような感じです。

enum Compiler {
        
	case swift
	case objc
        
	// 振る舞いをメソッドで添える。
	func build(source: String) {}
        
	// 計算型プロパティーを備える。
	var command: String {
            
		switch self {
                
		case .swift:
			return "swiftc"
                
		case .objc:
			return "clang -x objective-c"
		}
	}
        
	// 静的な保存型プロパティーを持たせる。
	static var environmentVariables = Dictionary<String, Any>()
}

04:25 ~ セクションを表現する

まず、注目することにしたのが、列挙型を用いて、例えばアプリの設定画面などで使う "セクション" を管理するという場面。まず、設定画面のセクションみたいなものについては、あらかじめ決められた有限範囲の事柄を的確に表す 上でも、列挙型をその表現に選ぶことは良い選択な印象でした。

enum Section {
        
	case language
	case notification
}

これの表示順番を表現するために、この列挙型に Raw 型を割り当てて、それを以って順番制御をしていくこと、これについて考察していこうというのが、面白いところに感じました。

enum Section : Int {
        
	case language = 0
	case notification = 1
}

特にここで提起された『設計としては微妙かもしれない、でも使い勝手としては効果的に働いたりする』というところ、とてもいいなって思って、日常を過ごしているとついついどちらかに偏ってしまうような気がするんですよね。それをこうして、それぞれの良さを見つめることで新しい発見があるかもしれない、日頃から大事にしたいところに感じました。

Objective-C の頃だったら、みんな、そして自分たちも、やっていたことだねっていう話と、それがなんで不思議に感じるようになったんだろうという着眼点も 今回の聞きどころ になるはずです。

06:40 ~ Raw Value の割り当て

そんな話を聞いて最初に自分が書いたコードが、先ほどの Section 列挙型なのですけど、ここで指摘されているのは、各列挙子に当てる値を明示しない方が何かと扱いやすい気がする というお話です。

enum Section : Int {
        
	case language
	case notification
}

こんな風に、具体的な Raw Value を添えなくても、たとえば Int 型を Raw 型としてもつ列挙型なら、各候補が最初から順に 0, 1, 2, ... と割り振られていくからなんですよね。

それを聞いていて今に思ったのですけど、なるほど、そうやって 暗黙的に定められる というのも、この後に展開していく Objective-C だと当たり前だったよね という話に感覚が繋がって行きそうな心地がしました。そして自分の中で『なんで String 型を Raw 型にもつ列挙型で、列挙子への文字列割り当てで省略表記を許してしまったんだろう』という疑問がずっとあったんですけど、今回の話を踏まえて今にこうして振り返ってみれば、確かに、そういう判断も必ずしも変とは言えないのかもしれないなって、ちょっと感じてみたりしました。

06:59 ~ 序列の表現方法について

そんな Raw 値を使って序列を表現することについて、まず自分が挙げた 配列を使って表現 というのは、おおよそ こんな感じをイメージして話し始めたところでした。はっきりと思い描いていなかったので、想像に大いにばらつきがあったのと、こうして実際に書いてみると、この自分の考え方だとぜんぜん Raw 値が意味をなしてない(なくても成り立つ)なと思うところではありますけれど。

// 最初はこれを想像していましたけど、これだと表現が冗長すぎそう。
static let sections = [Section.language.rawValue, Section.notification.rawValue]
    
// こんな想像も混ざってましたけど、これだと今度は Raw Value の必要性が消滅する。
static let sections = [Section.language, Section.notification]

そして、ここでの話の方向性は次のような感じでした。たとえば TableCell の描画とかで、どのセクションに対応した情報が欲しいとなった時に、そこに 渡されてきた section の値から目的の列挙子を作る ということに着目しています。

番組内では tableView:cellForRowAtIndexPath: の話をしましたけれど、ここではもっと簡単に tableView:titleForHeaderInSection: で端的に例を挙げてみると、こんな感じになると思います。

final class ViewController : UIViewController, UITableViewDataSource {
       
	// セクションを列挙型で定め、各番号を Raw Value で関連づける
	enum Section : Int {
        
		case language
		case notification
        
		var description: String {
            
			switch self {
			
			case .language:
				return "言語"
                
			case .notification:
				return "通知"
			}
		}
	}

	// UIKit から指定されたセクション番号から、Section 列挙型を生成し、処理を行う。
	func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
            
		let section = Section(rawValue: section)!
            
		return section.description
	}
}

このようにして "セクション番号" を "セクション" に対する Raw Value として割り当て、それを使ってセクションを管理する、そんな話になっていたと思います。

ここまで見て、番組の中ではこのような Raw Value の割り当て方 "そのもの" に違和感を覚えてましたけど、収録が終わって Raw 値というものについてそれなりにイメージが出来上がってくると、この Raw 表現 自体はもしかして、UIKit が前提としている Objective-C の世界観的にみて妥当かもしれない? って思えるようになってみたりとかして。

それでもやっぱり、その次の話で出てくる 意味的に見てどうなんだろう? というところが気になってきます。

08:20 ~ とりあえず Raw Value って何なんだろう。

ここが 今回の番組で特に面白いと感じる ところなので、ぜひ聴いてほしいところなのですけれど。

要点としては Raw Value という言葉に、それ以上の意味を含まない。それを "序列" として扱って良いのだろうか というところです。Raw Value は "生の値" くらいな意味しか持っていないので、これを "序列" として使うことに、大きな違和感を覚える、というお話です。

それでいながら Objective-C だと何で自然に思えるんだろうというところ、きっと 誰もがたしかにそう思うところと思う ので、そんなあたりに焦点を当てて、そもそも Raw Value って何者なんだろう、そんなところを探検してみることにしました。

最初に "数" があった

そんな観点で探求するとき、コンピューターの歴史を振り返ってみると、そもそも 数を起点にして物事は考えられていたのではないか という、よくよく思えば現在の bit 演算を原則にした電子計算機としては至極当たり前なことに遭遇して、そこから Raw Value の意味について、紐解いていく流れになりました。

そんな話の最初に挙がった Z80 のアセンブラとニーモニックは、例えば、次のようなコードです。まずはアセンブリコードから。

3E 0A 3E 20 80 C9

Z80 CPU の場合、最終的には上のコードにして実行しなければならないわけですけれど、これだととても人間にとって読みにくいコードなので、アセンブラコードを1対1で当てて、次のように読めるニーモニックコードにする、ということが行われていった、みたいな経緯があったりして。

LD A, 10
LD B, 32
ADD A, B
RET

ひとつ前に挙げたアセンブリコードと違って、こちらのニーモニックコードなら、だいぶちゃんと読める気になれますよね。

高級言語

ただ、これでもまだ辛いところは残るわけで、例えば、次のようなコードをみると 理解するのがとても大変 なことが一目瞭然と思います。過去の経験を掘り起こしながら動作確認せずに書いたコードなので、ちゃんと動くかはわからないですけど、雰囲気的にはこんな感じです。

MAIN:     EQU 0100H

          ORG MAIN
			
          LD HL, MESSAGE
LOOP:     LD A, (HL)
          OR A
          JR Z, EXIT
          CALL PUTCHR
          INC HL
          JR LOOP
EXIT:     RET

MESSAGE:  DEFM 'HELLO'
          DEFB 00H

PUTCHR:   PUSH AF
          PUSH BC
          PUSH DE
          PUSH HL
          LD C, 02H
          CALL 0005H
          POP HL
          POP DE
          POP BC
          POP AF
          RET
          END

コードに当てられた "言葉" そのものに、動作は割り当てられていても意図が全く込められていないのがその大きな理由なのかなって、今になってみれば そう感じられます。

そして、これを何らかの高級言語では、次のように明瞭に表現できるようになっています。こんなことが可能になるのは、言葉自体が意味を持っていること、具体的な多数の手続きが意味で1つにまとめられ、そこを解釈する必要がなくなったからと言えるような気がします。

PRINT "HELLO"

それと、余談になりますけれど、最初に挙げたニーモニックコードの方は、たとえアセンブラと比べて幾らか読みやすくしたコードであっても、例えば上記の例なら Z80, MSX-DOS 上でしか動かせないなど、機種依存の強い言語になっています。これが直上で挙げた例は BASIC ですけど、まあ、BASIC もけっこう機種依存性の強い(方言の多い)言語ではありますけど、アセンブラよりはだいぶ、いろんな環境で共通するコードになったりします。

そんな環境依存を中和することも、具体的な手続きを隠蔽できる高級言語ならではの特徴と言えるのかなって思います。

数値が先、名前が後の世界から

ちなみに、先ほどのニーモニックコードをハンドアセンブルすると、たぶん、次のようになると思います。本当にハンドアセンブルしてみたので、どこかで間違っているかもわからないですけど。

21 0E 01 7E B7 28 07 CD 13 01 23 18 F5 C9 48 45 4C 4C 4F F5 C5 D5 E5 0E 02 CD 05 00 E1 D1 C1 F1 C9

ともあれ、こんな 数字が先に立って、それに意味を添えていく と言う時代の流れだったときには、番組の中で談笑したように、そこ流れに近い Objective-C にとって "列挙型に数字がついている" というより "数字に列挙子" がついていると捉えると、数値に着目して、数字の性質を生かしたコードを書こうという意識が、かなり適切なものであることが感じられるように思います。

数値、とりわけ具体的に int なものだから、数字で判定できるのはもちろん、数字で大小を比較したり、数字の足し引きをしたって、とても自然です。この数字の足し引きは Swift でも文化として残っていて、OptionSet で使われる考え方だったりします。

名前が先に立つ世界へ

それが Swift になって列挙子が まずは名前ありき になったところ、ほんとすごいなって当初感じたんですよね。抽象的なまま、存在できる世界。だからこそ、具体値がないと存在できなかった頃の文化が Raw Value という概念として 顕れてきた のかもしれないですね。

そして "具体的な値を付けなければいけないとき" というのは、番組内で談笑しているように、具体値を持たないと成り立たない Objective-C の世界に Swift の列挙型をエクスポートするとき みたいな場面です。こういう需要があって初めて Raw Value を その列挙子に他ならない、それ以上でもそれ以下でもない具体値を、割り当てるために 使うことになるのかなって思いました。

16:15 ~ 失敗可能イニシャライザー

談笑で出てきた Raw Value から変換するときに使う失敗可能イニシャライザー を、具体的に書いてみるとこんな感じ。列挙型にたとえば Int 型の Raw 型を設定すると、実際には次のような実装が自動的に追加されることになります。

enum Section : RawRepresentable {
    
	case language
	case notification

	// Raw Value から変換するのに使う、失敗可能イニシャライザー
	init?(rawValue: Int) {
        
		switch rawValue {
            
		case 0:
			self = .language
            
		case 1:
			self = .notification
            
		default:
			return nil
		}
	}

	// 各列挙子に相当する Raw Value を取得するプロパティー
	var rawValue: Int {
        
		switch self {
            
		case .language:
			return 0
            
		case .notification:
			return 1
		}
	}
}

そして、番組内で話していることは、この 失敗可能イニシャライザーの意味合い的なところ について。これって "渡された Raw Value から変換できない可能性を示唆している" というよりは "Raw Value に対応する列挙子がない" みたいなことを主張したいのかなって感じます。

どちらも同じようなものかもしれないですけど、要は Raw Value の表現範囲の方が大きいものだから、どうしても対応づけられないものが出てくる、変換できるかではなく、渡された値がそもそも違う そんな意味合いに寄っていくのかなって思いました。Raw Value から作れるかの検査で使うのではなく、もし仕様と異なる Raw Value が渡されてきたときのリカバリー用、みたいな感じです。

列挙型とその Raw Value の表現範囲が一致するなら、失敗は不要

これがもし、たとえば候補が 256 個の列挙型で Raw 型が UInt8 だったりするようなときには、次のように Raw Value からのイニシャライザーを失敗可能にする必要がそもそもなくなってきたりします。

enum Enumerate256Items : RawRepresentable {
    
	case aa, ba, ca, da, ea, fa, ga, ha, ia, ja, ka, la, ma, na, oa, pa
	case ab, bb, cb, db, eb, fb, gb, hb, ib, jb, kb, lb, mb, nb, ob, pb
	case ac, bc, cc, dc, ec, fc, gc, hc, ic, jc, kc, lc, mc, nc, oc, pc
	case ad, bd, cd, dd, ed, fd, gd, hd, id, jd, kd, ld, md, nd, od, pd
	case ae, be, ce, de, ee, fe, ge, he, ie, je, ke, le, me, ne, oe, pe
	case af, bf, cf, df, ef, ff, gf, hf, `if`, jf, kf, lf, mf, nf, of, pf
	case ag, bg, cg, dg, eg, fg, gg, hg, ig, jg, kg, lg, mg, ng, og, pg
	case ah, bh, ch, dh, eh, fh, gh, hh, ih, jh, kh, lh, mh, nh, oh, ph
	case ai, bi, ci, di, ei, fi, gi, hi, ii, ji, ki, li, mi, ni, oi, pi
	case aj, bj, cj, dj, ej, fj, gj, hj, ij, jj, kj, lj, mj, nj, oj, pj
	case ak, bk, ck, dk, ek, fk, gk, hk, ik, jk, kk, lk, mk, nk, ok, pk
	case al, bl, cl, dl, el, fl, gl, hl, il, jl, kl, ll, ml, nl, ol, pl
	case am, bm, cm, dm, em, fm, gm, hm, im, jm, km, lm, mm, nm, om, pm
	case an, bn, cn, dn, en, fn, gn, hn, `in`, jn, kn, ln, mn, nn, on, pn
	case ao, bo, co, `do`, eo, fo, go, ho, io, jo, ko, lo, mo, no, oo, po
	case ap, bp, cp, dp, ep, fp, gp, hp, ip, jp, kp, lp, mp, np, op, pp
    
	// Raw Value と列挙型が同じ範囲の表現であれば、失敗可能でなくても良い
	init(rawValue: UInt8) {
        
		self = unsafeBitCast(rawValue, to: Enumerate256Items.self)
	}
    
	var rawValue: UInt8 {
        
		return unsafeBitCast(self, to: UInt8.self)
	}
}

残念ながらかわからないけれど、Raw 型を割り当てたときに数が一致したからといって、こういう失敗可能性を計らってイニシャライザーを自動実装してくれることはないですけれど、逆に個数がたまたま一致したときに Raw Value からの変換イニシャライザーの戻り値の型が変わってしまうのは何かと困るので、既定では失敗可能イニシャライザーが搭載されるというのは、良い判断に思います。

ちなみに Raw 型が UInt8 なのに 257 個以上の列挙子を実装したときには、ビルド時に次のようなエラーになってくれます。

error: integer literal '256' overflows when stored into 'UInt8'

17:28 ~ セクション番号、セクション序列

このあたり、ちょっと 自分の言葉が揺れていて難しいかもしれない ので、特に補足しておきますね。

セクションの Raw Value としてセクション番号を与えて適切か、たしかにセクション番号、でもなんか違う気がする、っていう話の流れですけれど、ここで自分がなんか違う気がすると言っているのは、番組ではつい "セクション番号" と言ってしまってますけど、意味合い的には "セクション序列"、もう少し言うと "序列としてセクション番号を使うのってどうなんだろう" という意味合いでした。

純粋に "セクション番号" であれば、それ自体に "序列" の意味は含まれない、そんなあたりを意識してみると、とりあえず Raw Value とは何者なのか、見えてくるような心地がしました。

18:17 ~ Notification.Name

NSNotification は Objective-C の世界観で作られたもので、名前を NSString 型の具体的な値で表現していたんですよね。それが、具体値がなくても成り立つようになった Swift の世界では、文字列で表現するより Notification.Name という型で表現する方が、コード補完の観点なども含めて、安全性が高まると思うんですよね。

まあ、それも重要ですけれど副産物ということにして、とにかく型のままで具体的な文字列による名前がなくても十分存在できるし、そちらの方が Swift らしい表現かなっていう感じもします。

ちなみにこの話、番組内での談笑の通り、自分は最初は Notification.Name が列挙型だったつもりで話を始めてしまい、訂正しつつ RawRepresentable だから大丈夫なはず という話をしましたが、改めて番組を聞いていても大丈夫そうなのと、もしかするとこうやって RawRepresentable に着目できたことで、むしろ今回の話題の 本質的なところを列挙型と混同しないで済んで 良かったかもしれないなって思いました。

23:10 ~ 実装を見ていて感じるところ

前半で Raw 型は何を表現しようとしているか みたいな観点で印象を整理しましたけれど、後半からは 列挙型に Raw 型として Int を当てることも正しい在り方なんじゃないか という観点で、列挙型を眺めてみる運びになりました。

24:11 ~ Raw 値を持った列挙子は比較可能か

その観点でまず気になったのは、Raw 型として Int 型をもたせたときに 列挙子は比較可能になるか というところでした。

Raw Value が先に立つなら、同列でも良いですけれど、それが Int 型であれば "比較可能" になってもおかしくないはず、そんな観点でまずはどれくらい 列挙子と Raw Value とに距離感があるか というところを確かめたかった感じです。距離感がわかれば、同等と見ていいのかとか、先に Raw Value を立てて考えて自然か、などなど、きっと何かが掴めるはず。

そんな談笑の中で確認できた、大小比較はできなかったけれど、等価比較はできた、そんなところの実際のコードは次のようになっていました。

func ==<T : RawRepresentable where T.RawValue : Equatable>(lhs: T, rhs: T) -> Bool

そして、拡張すればできるというのは、こういう風に Comparable を適用して、必要な振る舞いを備えてあげるというやり方です。とりあえず、こうやって Raw Value を序列判定で使うんだと主張して初めて、比較性だけに関していえば列挙子も Raw Value と対等なものになってくれる様子でした。

enum Section : Int, Comparable {
    
	case language
	case notification
    
	static func < (lhs: Section, rhs: Section) -> Bool {
        
		return lhs.rawValue < rhs.rawValue
	}
}

ここも面白かったんですよね。自分は普段、ついつい列挙型にこだわって Raw Value を考えてしまいがちでしたけれど、そう、上記でも記したみたいに RawRepresentable に着目すれば、列挙型での先入観がなくなって、新しいイメージを広げられて良かったです。

確かに 一意な Raw Value があれば、それを識別子として識別することが可能 ですね。逆にいうと、列挙型では一位な Raw Value を与えることが独自実装でない限りは保証されますけど、列挙型でも構造体でも独自実装の場合にうっかり同じ Raw Value を割り当ててしまうことは きっと誤りであること、それとこれは "もしかして" ですけど、等価比較性を考えると、たとえば SourceCode 型の Raw 値としてソースコードのテキストを保存するみたいなのに使うには不釣りあいなのかなっていうのが、思い浮かぶところでした。

Raw Value が同値であれば、そのインスタンスは同値であること。そんなところがきっと着目点になるのでしょう。それ以外については語られていない、そんな感じで。

28:30 ~ 付属値を持つ列挙型で Raw 値を使う

せっかく話題に出てきたので、付属値を持つ列挙型には Raw 値を当てられない という話について。まず、談笑の中で出てきたのは、たとえばこんなコードみたいな場合です。

// error: enum with raw type cannot have cases with arguments
enum Number : Int32 {
    
	case positive(UInt16)
	case negative(UInt16)
	case zero
}

そして談笑の中で自分が "Raw Value を当てられないから" と言いましたけど、間違っていて、厳密には 暗黙的に Raw 表現が備えられない だけで、具体的な Raw Value としての扱い方を RawRepresentable で示してあげれば 扱えるようになるのでした。

enum Number : RawRepresentable {
    
	case positive(UInt16)
	case negative(UInt16)
	case zero
    
	init(rawValue value: Int32) {
        
		if value == 0 {
            
			self = .zero
		}
		else {
            
			self = value > 0 ? .positive(UInt16(value)) : .negative(UInt16(abs(value)))
        }
	}
    
	var rawValue: Int32 {
        
		switch self {
            
		case .positive(let value):
			return Int32(value)
            
		case .negative(let value):
			return -Int32(value)
            
		case .zero:
			return 0
		}
	}
}

そして、これまでに見てきた性質を踏まえると、こうやって Raw Value で表現するためには "どの場合でも一意に決められる" ことが最低限の必須条件であることを弁えて実装する 必要がある、ということが大きな判断基準になりそうです。

こういう実装時の判断、自分はこの収録まで考えたことがなかったので、番組内でこういう談笑ができて本当に良かったです。

29:00 ~ セクション数を表現する Raw Value の印象

そしてこれも面白いのが、列挙子と Raw Value を応用して case count を持たせて数を得るという話題。これも Objective-C の頃は自然だったのに、いつしか Swift に慣れてくると 気持ち悪く見えてくる、とても興味深いところです。

enum Section : Int {
        
	case language
	case notification
        
	case count
}

これを、先ほどの Raw Value を序列として使うことなどに感じる印象を加味すると、やっぱり Raw Value と列挙子は分けて考えるのが基本なのかな、そんな姿勢であれこれ談笑してみました。

そんな話の中で、まず自分が挙げたのが order と、明確な名前を当てる方法でした。丁寧に紹介すると、Raw 型は持たせずに、用途が明確なプロパティーを振る舞いとして備える方法です。

enum Section {
        
	case language
	case notification
}

extension Section {

	var order: Int {
	
		switch self {
		
		case .language:
			return 0
			
		case .notification:
			return 1
		}
	}
}

話題の中の "相互変換するためのコードを書かないといけない" というのは、とりわけ "セクション番号" からセクションへの変換は、具体的にこういう実装になりそうです。そしてこうして実際に書いてみると、ラベルも気になってきて、そういえば "序列" と "セクション番号" も別物 ですね。そう考えると、変換イニシャライザーのラベルも sectionNumber みたいになるのがスマートなのかもしれません。

enum Section {
        
	case language
	case notification
}

extension Section {

	init?(sectionNumber: Int) {
	
		switch self {
		
		case 0:
			self = .language
			
		case 1:
			self = .notification
			
		default:
			return nil
		}
	}
	
	var order: Int {
	
		switch self {
		
		case .language:
			return 0
			
		case .notification:
			return 1
		}
	}
}

こうすると確かに談笑の通り "1対1" という関係性は見えなくなってきますけど、もしかして今回の "列挙子" と "序列" や "セクション番号" との間に1対1を見出すことの必要性もないのかなって思えてきました。むしろこうして "序列" や "セクション番号" とが独立することで、もし序列を変更しても もし並び順の序列が変更されてもセクション番号には影響しない みたいに、依存度が低く済んで良さそうな気もします。

あとは、談笑中にも触れたみたいに、こういう性格をちゃんと弁えた上で活用すれば、たとえばUITableViewDataSource で使うセクションを考えたとき、すなわち『UITableView からセクション番号が指定されてくるのと、その順番で表示される仕様になっていること』を加味すると、UITableView という檻の中では、セクションを表現する列挙型に対してセクション番号が Raw Value として割り当てられることは自然な在り方として、そして、内部表現を Raw Value を使って表現することで、円滑で秩序だった実装を目指すことも正攻法として有り得ることでしょう。

33:08 ~ JSON とかで受けた値を表現するとき

こうやって考えていくと、そうね、話題の中の JSON の生データを列挙型にマップする とき、原則、繪面さんのいう通り Raw Value として JSON の生データを1対1で密接に組み合わさっている、と捉えて大丈夫かもしれませんね。

自分がここで気にしたのは Raw Value の衝突は、UserDefaults を挙げましたけど、考え過ぎかもしれないですね。仮にキーとして使うにしても、確かに、JSON の生データが密接だから、それをそのままキーに使うみたいなことも可能そうですし、そういったあたりをぜんぶ踏まえてコードを書いてみていたときに、自分が番組内で想像していたこういうコードって、そうそう書く機会はなさそう に思えました。

enum Category : String {
    
	case comic = "comic"
	case novel = "novel"
}

extension UserDefaults {
    
	func set(_ value: Int, forKey key: Category) {
        
		set(value, forKey: key.rawValue)
	}
}

let category = Category(rawValue: json["category"] as! String)!

UserDefaults.standard.set(10, forKey: category)

適切な Raw Value を選ぶことで、そうそう矛盾は起こらない、そんなことを感じさせてくれた心地です。もしこれと似たコードを書く機会があったとしても、もしかすると状況に応じて考えを巡らせれば、これとは違ったスタイルのコードに落ち着きそうな予感もしますね。

39:25 ~ 序列を配列で表現する

最後の最後で、談笑した "配列で表現する" というのは、配列が順序性を持っていることを使ってそれで順序を表現して、そうすることによって "列挙型が周りを見せ過ぎてる感" を低減できる、そんなあたりの話のコードをざっくりですけど記載しておきますね。

final class ViewController {

	enum Section {
        
		case language
		case notification
	}
	
	static let sections = [.language, .notification] as [Section]


}

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

今回の『実際のアプリ開発現場の世界の列挙型たち』という話、問題提起に始まって、基本を見直し、問題と照らし、なかなか腑に落ちなくて、そこからいろんな可能性を模索して、今まで以上に視野が広がって。とっても良い感じに彷徨えたような心地がします。

あとは上記で触れた全てを踏まえて、最終的にはどういう表現の仕方がいちばんそこにマッチするか、そういったところを考えていくことになるのでしょう。たぶんきっと単純な正解ってなくて、現場に広がる現況に合わせて実装方法を選んでいくのがいちばん大事なことと思いますし、今回の談笑で見つめたいろんなことが、そんな理想を探すために十分なくらいの材料として整ったように思います。

ほんと、今回は特に、談笑していく中で得られた新しい発見がたくさんあって、とっても楽しかったです。


それでは、熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ 、次回もどうぞ聴いてくださいね。次回、第8話は の 18:00 頃に配信予定、テーマは『名前空間の声が聴きたい』です。

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