Swiftの列挙型をループで舐めたかった話

技術,プログラミング言語

TL;DR

  • プログラミング中に「こんなことしたいけどどう書くっけ?」はよくある
  • そのため、ググって探すわけだが…言語によっては古い方法に辿り着いて、無駄な苦労をすることに
  • AIでの検索を使うと、こういうことが回避できる???という話

列挙型のすべてのcaseをループで舐めたい

The Swift Programming LanguageのSwiftツアーをやってます。その「列挙型と構造体」の項の最後の課題で、Card型の全種のカードの配列を返す関数を書け、と言うものがあります。名前で想像できるかと思いますが、Card型はトランプを表す構造体で、SuitとRankという2つの列挙型をメンバに持ちます。目的の関数はこの全てのSuitとRankを組み合わせた52個の配列を作るものになります。

当然、SuitとRankのそれぞれの要素でループする必要があるかと思います。例えば、Suitを例にします。列挙型Suitの定義はこんなものです。

enum Suit {
    case spades, hearts, diamonds, clubs

    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
    // add by Experiment
    func color() -> String {
        switch self {
        case .spades,.clubs:
            return "black"
        case .hearts, .diamonds:
            return "red"
        default:
            return ""
        }
    }
}

このスペード、ハート、ダイヤモンド、クラブをいちいち指定しないでループしたいわけです。

先に回答を書いてしまうと、列挙型を定義する時にCaseIterableプロトコルを使うことで、.allCasesというプロパティが使えるようになります。これを使うと、以下のように書けます。これは便利ですよね。

for in suit in Suit.allCases {
   // 何らかの処理
}

この結論に至るまでの道筋

先に答えを書いてしまいましたが、これに最初から行き着いたわけではありません。そこまでには紆余曲折がありました、というのが今回の話の趣旨です。

最初に見つけた面倒そうな方法

課題の回答を最初に書いた時には、とりあえず「for rank in 1…13」みたいなループを書きました。もちろん、これで動きますが、こんなハードコーディングは当然避けたいです。

そんなわけで、Google先生に「swift 列挙型 ループ」というキーワードで聞いてみました。それで先頭に出てきたのが以下の技術記事でした。

この記事の先頭に「この記事は最終更新日から5年以上が経過しています。」と書いてあることからわかるように、これは2015年5月の記事です。当時のSwiftのバージョンは2.2だったよう。現在は5.7(もうすぐ5.8が出る)です。試してませんが、ちょっとループを回したいだけの割に、なかなか面倒ですよね。

ChatGPTに聞いてみた

そんなわけで、もうちょっと何かいい方法がないかと、ChatGPTに聞いてみました。

問い:Swiftで列挙型の全ての値をforでループする方法はありますか?

回答:はい、Swiftの列挙型の全ての値をforでループすることができます。以下はその方法です。まず、Swiftの列挙型には CaseIterable プロトコルがあります。このプロトコルを採用することで、列挙型の全てのケースをループするために、allCases プロパティが提供されます。

ここでは省略しますが、サンプルコードを挙げて説明してくれます。
ただ、ソースに書いてみたらエラーになって「allCasesというプロパティは知らない」と言われます。あれ?また、ChatGPTが嘘ついてるのかな?と思ったのですが、ここでふと気づきます。

Swiftでこういう質問をウェブ検索すると、昔のやり方が出てきてしまうことがあります。Swiftはメジャーバージョンが変わるとかなり言語機能も変わってくるので、検索した結果を試すとうまく動かなかったり、もっと良い方法があったりします。

今回もそんなことかと思い、まずはどのバージョンで使えるか確認しました。

問い:allCacesはどのバージョンのSwiftで使えますか?

回答:allCases プロパティは、Swift 4.2 以降で利用可能です。Swift 4.2 以前では、allCases プロパティは組み込みではなく、手動で定義する必要がありました。
(中略)
ただし、Swift 4.2 以降では、列挙型が CaseIterable プロトコルに適合している場合、コンパイラが自動的に allCases プロパティを生成します。従って、手動で定義する必要はありません。

なるほど、CaseIterableプロトコルというのが鍵のようです。続けて問いかけます。

問い:自分で定義した列挙型をCaseIterbleに適合させるにはどうしたら良いですか?

回答:自分で定義した列挙型を CaseIterable プロトコルに適合させるには、以下の手順を実行してください。
(後略)

回答は省略しますが、こういう風に書けば良いというサンプルを示してくれたので、それを反映したのが正解の話です。

Googleでは2番目にあった

なお、ウェブ検索の2番目以降の内容を確認してみたところ、2番目にちゃんと今のバージョンで使える事例がありました。

ChatGPTがどうやって上記の結論に至ったのかは分かりません。この記事のような現在のバージョンで使える事例を学習しており、それを適切に回答してくれたのかもしれませんし、たまたまかもしれません。また、ウェブ検索の方も、今回は古い方法が1番目に出ましたが、場合によっては2番目の方が上になることもあるかもしれません。

もしAI(ChatGPT)が時系列を意識してくれているなら…

正直、今回の件は偶然なのではないかという気はします。たまたま、Google先生の回答が古いものをSEO的にか何かの事情で上に挙げてしまい、ChatGPTもたまたま何らかの理由で新しいバージョンで使える手法を的確に出してくれた、と。

ただ、もしもAI(ChatGPT)が時系列を考慮して古い手法を除外して新しめのものを提示してくれたのだとしたら。これはなかなか気が利いていると思います。

Swiftや、あるいはJavaScriptなど、バージョンによってかなり違ってくることはよくあります。CやC++なども、自分が最初におぼえた90年台のものと今のものではだいぶ違います。困るのはそういう場合に古い情報が出てきてしまうと、今回のようにハマってしまうこと。実際、昔Swiftを勉強しようと思った時も、動かしてみると動かないというものがよくありました。アップルが下位互換性のない変更を入れまくるせいですけど。

いちいちこういうのにぶち当たっていると学習意欲が減退するので、それをAIで回避できるならばプログラミング学習の効率も上がるんじゃないかと感じます。でもまあ、実際問題どうなのでしょうね。

蛇足:一応、ChatGPT本人に聞いてみた

この疑問をChatGPT自身にぶつけてみました。Web版の無料ChatGPTのレスポンスがよろしくなかったので、この質問はLINEの「ChatGPT研究所」さんのチャットボットを利用させていただきました。

まとめると、ChatGPTが持っている情報が古いケースもあるが、できるだけ最新版を元に回答しているとのこと。これを完全に真に受けて良いのかは分かりませんが、本当に考慮してくれているならば、プログラミング学習に関してはウェブ検索よりAIの活用が有効かもしれません。いや、本当にSwiftの勉強していた時には古い情報に振り回されたので…。

試しに、簡単な数当てゲームをJavaScriptで書いてもらったところ、constとlet の使い分けは適切で、varを使ったりもしませんでした。Rustの場合はmutを適切に使っています。ただ、どの言語でもエラー処理が微妙でしょうか。競プロみたいな入力が正しい前提の書き方をします。とは言え、最低限ランタイムエラーにならないようにはしてくれています。

まあ、あくまでその方向で努力してくれるという話ですけど、複数の情報をまとめてより一般的な回答をしてくれるのであれば、単なる検索よりは役に立ちそうです。

技術swift,プログラミング言語

Posted by woinary