第4話 : コメントの続きと整数型 #ムックムックラジオ
熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ
mookmook radio 第4話では『どんなコメントを書こう』と『整数型の使い分け』という2つのテーマで繪面さんと談笑しました。
そんな話の中で触れた話題や具体的なコードをここで補足していますので、このブログを見ながら放送を聴くと、より楽しく聴いてもらえるかもしれません。
ラジオステーション mookmook radio で配信中の 熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ にて、プログラミングに関してあれこれ楽しく話をさせてもらってますけど、そんな番組と一緒に 番組内で話したことの補足があったらもっと楽しいかな って思って、綴ってみることにしました。
コンセプトとしては、プログラミングが大好きな2人で、プログラムの作り方というよりは、プログラミングで遊ぶ中で、見つけたことや感じたこと、いろんなことに花を咲かせて楽しもう、みたいな番組になっています。
今は2人とも Swift 言語がお気に入りなので、そちらに大きく偏りますけど、基本的にはプログラミングのことならなんでもアリです。
第4話『コメントの続き & 整数型』
今回のテーマは、前半と後半とに分かれていて、前半は、第3話でお話しした『コメント』の続きの話です。そして第4話は気分を変えて『整数型』についてたっぷり話してみました。特に後半、良い感じに話が展開したかなって思います。
そんな番組の中で話した事柄について、ここでいろいろ補足してみたいと思います。番組の流れに沿いながら、気になるところをピックアップして綴っていくので、番組を聴きながら眺めていくと、きっとより楽しめると思います。
なお、ここでは僕自身が1人で放送を聞き返しながら、感じたことを綴っていくので、番組内で2人で話しているときとはまた違った方向に話が進んでいくと思います。同じように、番組を聴いたりここを眺めてくれる方々も、それぞれの想いを自由に重ねてもらえたら嬉しいです。
02:51 ~ コメントの続き
前半は、第3話でお話ししていた『コメント』の続きの話でした。
今回は、前回の収録で話しきれなかったコメントの話、とりわけ『どういったコメントを書くべきか』みたいなところに焦点を当てて話してみました。
02:52 ~ 印象の転換
そう、コメントって漠然と言ってもなかなか、その真意って、気にしないとつい疎かにしてしまうんですよね。
今回こうしてお話ししてみて、少なくとも自分はつい最近、Swift 3 の API Design Guidelines を眺めるまでは、コメントって『コードに説明を添えるもの』程度にしか思ってなかったように感じます。
幸い自分も、コメントについて気にする機会は幾らかあって、そんな中で思ったことは コメントは書くべき、でも要らないコメントってよく見るような、とりあえず原則書かないみたいなことはないよね
みたいな程度でしたけど、繪面さんが話していたように 議論になっているのは、どういうコメントを書いて、どういうコメントは書かなくていいか
というところ、つまりコメントを書くことが前提にあって、そしてそこから書くべきものを見定めていく感じ。そんなところを、今回の話の中で気がつくことができたのは、自分にとってすごく良い機会でした。
たしかにこの辺り、コメントとは何を意味するものなのか、それを描いているか否かによって『当たり前』が変わるくらいに、見方・使い方が根本から大きく別れてきそうですね。そしてそれって、プログラムコード自体、とりわけ表現力豊かな Swift のコードそのものにも、同じように言えるのかなって感じてみたり。
03:40 ~ コメントもひっくるめて、コード
説明的なコメントはいらない、どうしてそういうコードになったか。
それってすなわち、説明的なものはコードで説明して、コードには落とし込めない意図みたいなものをコメントで説明する。その境目をどう的確に見定めるのか、そんなテーマに行き着くのかなって感じました。
そうしたときに、Swift API Design Guidelines にも記載のあった『すべての言葉それぞれが、冗長なく、不足なく、表現する』、それがいわゆるコードに限らず、コメントも含めてそうなんだなっていう心地。その観点で、どこまでコードに意図を込められるか、そんなところに注目するのも面白いなって思いました。
それが抜けると自分みたいに、書くべきか否か、みたいな主眼でついつい話してしまいそうです。
04:45 ~ なんかよくわからないけれど、動いた。
どうしてそうなったか、そういうのを強く感じるのが、次のようなコメント。
var integerValue: Int {
// なんかよくわからないけれど、動いた。
return (rawValue as AnyObject).intValue!
}
このコメントがなかったとしたら、次のような感じ。
var integerValue: Int {
return (rawValue as AnyObject).intValue!
}
なんか、ぜんぜん印象が違いますよね。
このコメントがあることによって、どこが weak ポイントに成り得るのかみたいなのが、第三者がみて分かるか否かの境目にもなりそう。
実際には、この例みたいなのだと『コードを書いた本人の技量不足』感が強いのでコメントよりもコードの完成がひとまずの優先課題な感じがしますね。こういうコメントを必要とするのはもっとワークアラウンド寄りなもの、利用する API にバグがあってそれを回避するために採った手段みたいなものをコードの中に埋め込むのに特に威力を発揮する、そんなあたりも話す中で感じられて良かった。
そんな話の途中に出てきた "書いた人の考えた頭の中が書かれたコメント" っていうのも面白いですね。上のコード例なら、例えばこんな感じでしょうか。
var integerValue: Int {
// rawValue プロパティは Any 型で保存されていたけれど、これに何型が入っているか分からない。
// でも、このプロパティから Int の値が取り出せるらしい。
// そこで `AnyObject` 型にキャストしたら、何故か `intValue` プロパティを呼び出すことができるようになった。
// ただ、何故か Int? が得られ流みたいで、Xcode が `!` を付けろと言ってきたので、つけたらちゃんと Int で値が取れた。
return (rawValue as AnyObject).intValue!
}
おや … 途端に背筋の凍るコードに変わった。
今回の例は完全に闇雲に手探り的なコードなので、もうちょっと書く本人が理解しないとヤバそうな感も漂いますけど、それでも効果は絶大な印象。そして、どうにもならないワークアラウンド的なところで書いたものなら、これとは違ってキラリと輝くコメントになってくれるはず。
11:50 ~ コードレビューで指摘されたポイントが、コメントを必要とする絶好の場面?
なるほど、たしかに。コードレビューで突っ込まれるということは、コードだけでは意図を汲み取れなかったポイント、こういう場面こそコメントが効果を発揮する出番、そんな感じではありそうですね。
そしてたしかに、コードレビューという文化とコメントって、とても相性良さそうですね。
自分がプログラミングをやってた幼い頃の、チームをあまり意識しないとか、変数名や関数名が数文字しかつけられないとか、乗算命令がなくて左ビットシフトで2倍するとか、そういう、プログラムコード自身の表現力の低さも手伝って、コメントが手続きを補足するものみたいな風になっていたのかもしれない。
その時代であれば、そんな説明的なコメントも威力を発揮したのかもしれないけれど、プログラム言語自体が表現力を持った今だと、たしかにコメントの在り方というのも考え直されて然るべきなのかもしれないですね。特に自分みたいな、だいぶ前からプログラミングに親しんでいる人にこそ、いったん『コメントとは何者なのか』みたいな視点で振り返ってみるといいのかなって思ったりしました。
17:47 ~ インデントやコメントがもたらす成長
この話がとっても面白かった。
インデントやコメントをどれくらい書くかで、その後のプログラミングの伸びがどうなるのかっていう研究、そういうのがあるって聞いて、すごく良い教育だなって感心しました。そういう教育が大事にされる環境の中で育てるプログラマーって幸せね、きっと。
そしてほんと、関数とか変数とかの適切な名前を探すことも適切なコメントを選ぶことと同じくらいの重みだろうと思うんですけど、そうやって適切な変数名を見つけることで、それから先のコードの安定感が高まる感じ、設計の道筋が明瞭にまっすぐ引かれる感じ、あれもすごく良い体験をさせてもらえたなって感じました。
22:15 ~ 整数型
後半は、整数型の使い分けのお話でした。
すごい小さいことなんですけど、正負の整数を表現できる Int
と、正の整数だけを表現できる UInt
という二つの型、この辺りの使い分けとか、捉え方とか、そういったところを考えてみたいなって思ったのでした。
23:54 ~ 配列のインデックス
話し始めて、さっそく繪面さんから指摘してもらった『後ろから数えて』というのは、たとえばこういう感じかな。Swift の場合、標準ではそういうものってなさそうなので独自に、開始点(インデックス)に負の数も採る subrange
を実装した場合の例ですけれど。
let values = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 自作した、指定したインデックスから末尾までを取得する subrange メソッドの実行例
values.subrange(from: 3) // [4, 5, 6, 7, 8, 9]
values.subrange(from: -3) // [7, 8, 9]
こんな風に from
で指定できるのを Int
にすることによって、柔軟に末尾からの部分配列取得に切り替えられるという方法。この方法は JavaScript の substr
など、さまざまな言語で見られる手法なのですけれど、それらの言語では自然に見えても Swift でこういうのを書くとなんとなく不自然に感じられるの、面白いですね。
実際に実装してみると…
ところで、これを実際に実装してみたら、なんか面白いことが見えてきました。
まずは簡単に Int
で制約をつけて実装してみたのですけど、インデックスを動かすときに Index
ではなく IndexDistance
を使うんですね。
// 前提が "Index が Int だったら" なので、話が簡単になるように制約で縛ってみました。
extension Collection where Index == Int, IndexDistance == Int {
// このメソッドでは引数に Index すなわち Int を採ります。
func subrange(from start: Index) -> SubSequence {
if start >= 0 {
return suffix(distance(from: start, to: endIndex))
}
else {
return suffix(distance(from: endIndex.advanced(by: start), to: endIndex))
}
}
}
values.subrange(from: 3) // [4, 5, 6, 7, 8, 9]
values.subrange(from: -3) // [7, 8, 9]
ということは、つまり開始点を IndexDistance
で扱ってあげれば、次のように制約なしで書くことができます。
extension Collection {
// 先ほどのコードで、引数は実は Index ではなく IndexDistance だったのに気づいたので、そう変えてみます。
func subrange(from offset: IndexDistance) -> SubSequence {
if offset >= 0 {
return suffix(from: index(startIndex, offsetBy: offset))
}
else {
return suffix(from: index(endIndex, offsetBy: offset))
}
}
}
values.subrange(from: 3) // [4, 5, 6, 7, 8, 9]
values.subrange(from: -3) // [7, 8, 9]
ここで面白いのが、IndexDistance
は Collection
の規定の中で SignedInteger
に制約されていて、対して Index
は IndexableBase
の中で Comparable
とだけ制約されています。
つまり "マイナスでのインデックス (Index)" と思われるものは、Swift では "距離 (IndexDistance)" で表現するもの、だったりするのかもしれないですね。それに応じて名前も index
から offset
に変わって、思いがけず、適切な名前を当てる大切さも同時に感じられる例にもなったみたいです。
すべての場面でそう扱えるかは分からないですけど、とりあえず今回の例で捉え違えたのは、たまたま、インデックスの開始地点 (startIndex) の 0 と、距離のニュートラルを意味する 0 が一致していたというのが大きかったりしそうです。
たしかに、もう少し抽象的に捉えると -startIndex
ってなんだろう。もう少し極端な例で考えて、配列のインデックスが 5 ..< 10
だったとしたときに、例えば、インデックス -5
ってどこだろう。
そんなふうに考えてみると、なるほど IndexDistance
という心地。面白いですね。
インデックスが UInt で、Int で移動可能なコレクションを作ってみる
この Index
と IndexDistance
の違いを感じられるようになって、それならもしかして、インデックスが UInt
で、その位置指定を Int
でできるコレクションも作れるのではないか、と思い立って試してみました。
// 任意の要素を格納できる、インデックスが UInt 型で、負数を使って位置指定ができる配列です。
struct MyArray<Element> {
fileprivate var items: Array<Element>
init() {
items = []
}
init<S : Sequence>(_ elements: S) where S.Iterator.Element == Element {
items = Array(elements)
}
}
// 前後移動を実現可能にするために、ランダムアクセス可能とします。
extension MyArray : RandomAccessCollection {
var startIndex: UInt {
return UInt(items.startIndex)
}
var endIndex: UInt {
return UInt(items.endIndex)
}
subscript (position: UInt) -> Element {
return items[Int(position)]
}
func index(after i: UInt) -> UInt {
return i + 1
}
func index(before i: UInt) -> UInt {
return i - 1
}
}
// おまけ: 標準の配列っぽく扱えるように、配列リテラルを受け入れます。
extension MyArray : ExpressibleByArrayLiteral {
init(arrayLiteral elements: Element...) {
self.init(elements)
}
}
// おまけ: せっかく値型なので、自身を表現する文字列に変換できるようにしておきます。
extension MyArray : CustomStringConvertible {
var description: String {
return items.description
}
}
このようにして作ってあげると、次のように、インデックスは UInt
で使えます。ちなみに count
は、話の中でもでてきたように、諸事情により Int
ですけれど。
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9] as MyArray
array.startIndex // 0 (UInt)
array.endIndex // 9 (UInt)
array[UInt(1)] // 2
array[UInt(5)] // 6
array.count // 9 (Int)
さて、これに対して、先ほど extension
で作成した subrange
がそのまま適用されて、次のように使うことが可能です。
Array(array.subrange(from: 3)) // [4, 5, 6, 7, 8, 9]
Array(array.subrange(from: -3)) // [7, 8, 9]
こんな感じで、インデックスは UInt
だけれど、位置指定は Int
を使って後ろから数える、みたいなことも実現できました。
上の例で subrange メソッドの結果を Array に変換しているのは、subrange が RandomAccessSlice を返し、これが CustomStringConvertible を備えないため、具体的な値が Playground で確認できないためです。
インデックスと距離の違いを感じてみる
なんとなくこの IndexDistance
の感覚には不慣れだったので、もうひとつ、はっきりと Index
と IndexDistance
との違いを体感できそうな例を考えてみました。
extension Collection {
// 距離によって要素にアクセスする添え字構文 (subscript) を追加します。
subscript (offset n: IndexDistance) -> Iterator.Element {
if n >= 0 {
return self[index(startIndex, offsetBy: n)]
}
else {
return self[index(endIndex, offsetBy: n)]
}
}
}
// インデックス (Index) による添え字アクセス
array[3] // 4
//array[-3] // error: Negative interger '-3' overflows when stored into unsigned type 'UInt'
// 距離 (IndexDistance) による添え字アクセス
array[offset: 3] // 4
array[offset: -3] // 7
このような感じで、今回の MyArray
が 0 から始まるインデックスなので、移動距離と混同しやすくなっている感はありますけれど、それでもこのコードを眺めていると、インデックスと距離の違いが感じ取れてくるように思います。
なるほど "マイナスでインデックス指定している" と思っていたところは、本当は距離で位置指定していたと言えるのかもしれないですね。
24:37 ~ 絶対に正の整数しかとらない引数
話の中で出てきた、0以上の引数を想定している関数のお話。まず、引数を Int
で採って、エラーチェックをするという話は、こんなコードでした。
// フィボナッチ数を取得します。
//
// - Parameter n: 取得したい番号を指定します。
// - Returns: 指定した番号のフィボナッチ数です。
// - Precondition: n は 0 以上の整数です。
func fibonacci(_ n: Int) -> Int {
// 前提条件を満たさなければ、例外とします。
guard n >= 0 else {
fatalError()
}
return Int(abs(pow(Double.goldenRatio, Double(n)) / sqrt(5) + 0.5))
}
extension Double {
static let goldenRatio: Double = 1.6180339887498948
}
だったらこの fibonacci
関数を、最初から UInt
にしてしまえば、エラーチェックが要らなくなるし、それによって fatalError
も発生しなくなるよね、というのが次のコード。
// フィボナッチ数を取得します。
//
// - Parameter n: 取得したい番号を指定します。
// - Returns: 指定した番号のフィボナッチ数です。
func fibonacci(_ n: UInt) -> Int {
return Int(abs(pow(Double.goldenRatio, Double(n)) / Double.squareRootOfFive + 0.5))
}
26:10 ~ Swift 標準の、絶対に正の整数しかとらない箇所
そんな話の中で挙げた Swift 標準ライブラリに見られる、絶対に正の整数しかとらないところのコードはこちら。
Array(repeating: <#Element#>, count: <#Int#>)
指定した Element
を count
個もつ配列を作るイニシャライザーですけど、ここの count
が Int
なんですよね。
マイナス 1 個の数みたいなことってありえないと思うのに、なぜだか Int
になっていて。実際、ここに -1
とか渡すと、ランタイムエラーで強制終了です。
27:26 ~ ループカウンター
そしてそう、普通に整数しかとらない場面で、なかなか UInt
って見ないんですよね。例えば、C 言語におけるループカウンターとか。
for (int i = 0; i < 11; ++i) {
}
こういうところでなんで uint を使わないんだろうというお話。
そして放送の中でお話しした失敗談、タイミング時間的には 29:30 のところの『uint を使った降順での繰り返し』のコードなのですけど、番組内では終了条件のところを言い間違えていて、正しくは次のコードでした。
for (uint i = 10; i >= 0; --i) {
}
こういう風にして、うっかり無限ループにハマってしまったことがあったんですよね。
C 言語の uint
の仕様上、0 の次に UIntMax
になってしまう。Swift の場合は、もうこういう書き方はしなくなりましたけど、標準の計算だとオーバーフローのチェックがあるのでエラーで落ちてくれるのですけれどね。
そして今、試しに Objective-C を使って、先ほどのループを書いてみると、なんとちゃんとビルド時に警告してくれるんですね。そんなことを知ってちょっと嬉しかったりしました。
ちなみに Swift で、C 言語みたいな演算をしたい場合は -演算子 の代わりに &-演算子 を使います。
33:10 ~ 外側にリカバリーを求める
柔軟に受け取って、範囲外の値がきたら外側にリカバリーを求めるというのは、こういうお話。
// フィボナッチ数を取得します。
//
// - Parameter n: 取得したい番号を指定します。
// - Returns: 指定した番号のフィボナッチ数です。
// - Throws: n に負数が渡された場合に NegativeNumberIsNotAllowed エラーを返します。
func fibonacci(_ n: Int) throws -> Int {
guard n >= 0 else {
throw NegativeNumberIsNotAllowed()
}
return Int(abs(pow(Double.goldenRatio, Double(n)) / Double.squareRootOfFive + 0.5))
}
// 負数を許容しないことを示すエラーです。
struct NegativeNumberIsNotAllowed : Error {
}
こうすることで、ランタイムで範囲外の値を受け取ったときに、呼び出し元にそれが無効であることを通知して、臨機応変に代わりの動きを選んでもらうことが可能になります。
最近の自分はついつい型で適切な方向へ導くことを主眼に置きすぎていたかもしれないなって、この話をしていて感じました。ランタイムならではの良し悪しも弁えて、状況に合わせて適切に選択する力を養うことも大切そうです。
34:37 ~ 型でどこまで説明するか
それにしても、特に UInt
だけで表現できる場面で Int
を使うかどうか、そんな話の中で出てきた『絶対』という判断基準。
これって話の中でも出てきた通り、けっこう『課題のバックグラウンド』って重要で、それを考慮しなければ『絶対』と言い切れるくらいの判断基準になるものであったとしても、いざとある状況に置かれたときには、普通に別の判断にしたりするものですよね。
この感覚がけっこう面白くて、ほんとそう、状況に応じて選択肢って山ほどあって、そういう、しっかり基本の選択肢を持ちつつ、状況に応じて選択肢を思い描いて選べるかどうかが、プログラマーにとっての大事な力量なのかもしれないなって思ったりしました。
37:00 ~ 受け入れるときは柔軟に、渡すときには厳密に
こういうのも、考え方としてありそうですよね。自分もかつてこういうことを試みたことがあって、そういう使い分けもいいのかなって思ってたりします。
ただし、こちらも番組内での話の通り、戻すときと受け取るときとみたいに、同じ表現なのに状況によって "型が変わる" というのは、ともすると許容できない大きな問題なのかもしれないなって思ったりして。そんなあたりも気にしながら、論理的な秩序を乱さない限りで、厳密さを突き詰めていくのは価値のあることのようにも感じたりして。
型とは何か
こんなあたりに『型とは何か』『型でどこまで説明するか』を感じ取るヒントがあったりするのかなって、思ったりもしてみました。
もしかして『表現できるか』という観点で見たとき、たとえば『 0 から 100 まで』みたいなのを表現するとき、ここで Int
にするか Int8
にするかみたいなのと、Int
にするか UInt
にするかみたいな議論って、もしかして同じ重みだったりするのかなって思ったりして。
そう考えたとき、もしかすると、プロトコルでいう Integer
と SignedInteger
という括りでの判断というのもありそうですよね。たとえば前者なら Int
も UInt
も どちらでも 該当するのに対して、後者は Int
しか該当しない。
今回の収録では、いばわ SignedInteger
と UnsignedInteger
に注目して話していた感じでしたけど、こんなあたりも考慮に入れると、もしかしてまた違った話に広がるのかなって、改めて聴き直しながら思ったりしました。
収録の感想と、次回のお知らせ
こんな感じで、今回は特に『整数型』のお話がとっても面白かったです。
お互いに知ってる知識を持ち寄って、知らないことを思い描くのって楽しいですね。特にこういう初歩的な部分って、なかなか話に挙がらないし、挙がっても、話にがっつり乗ってきてくれる人っていうのはなかなか稀で。ほんと、とっても楽しい時間でした。
こういう小さいところ、とっても大事な基礎的なところ。こういうところを疎かにしないで、それぞれなりの信念みたいなのを持つことが、コード全体の安定性に繋がるんじゃないかなって、少なくとも自分は思ってます。
それでは、熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ 、次回もどうぞ聴いてくださいね。次回配信は の 18:00 頃を予定していて、テーマは『アクセスコントロール』です。
どうもありがとうございました。