
サポーターズで働いています、新卒2年目の@fuchio_gtです。 先日、後輩とOJT振り返りをしていた時に、AIを利用した学習方法について話しました。 その時の私のお気持ちを備忘録としてブログに残しておこうと思います。
この記事で伝えたいこと
- アウトプットが知識を定着させる
- 学習時には「疑問の細分化を自分で行う姿勢」が大事
- 思考の主導権をAIに渡してはならない
はじめに
業務中、難解なコードに張り倒されることってありますよね。
Scalaを触り始めてもうすぐ2年経つっていうのに、未知の概念がわんさか出てきます。
苦しい。
そんな時はAIです。 なんでもかんでもブチこんでみて、「なにこれ」って聞けばそれなりの解答が返ってきます。 便利な世の中になりましたね。
なんか昔より知識が身についてない気がするけど、仕事は進むからヨシ!
…それでいいのか?
効率良く知識を身につけるには
一般的に、人間はインプットよりアウトプットの際に効率良く学習することが知られています。
例えば、三角関数の加法定理ってありますよね。 あれは教科書に載ってる公式を暗記するだけでもいいっちゃいいんですけど、公式の導出・証明ができる人は強く記憶に残っていると思います。 (ちなみに私は暗記していた側なので覚えていません)
ほかにも、誰かに説明するつもりでブツブツ喋ってみたり、別の概念で例えてみたり...。 方法はたくさんありますが、とにかく学習におけるアウトプットは記憶の定着にとても効果的だという話です。 つまり、業務においてもアウトプットを強く意識すると学習が捗る、ということですね。
詳しく知りたい方は『科学的根拠に基づく最高の勉強法』という本を読んでみると面白いと思います。 『世界一流エンジニアの思考法』の著者である牛尾さんも絶賛していました。
実は「ググる」のがいいアウトプットになっていたんじゃないか
(いや、ググってんだからインプットじゃ〜んw)
AIが台頭するまでは、皆さん必死でググっていたはずです。 私は「ググるのが上手い人」のことをエンジニアと呼ぶのだとさえ思っていました。
ググって疑問を解消するには、以下の手順が必要です。
- 疑問の細分化
- 各疑問ごとに検索
- 検索で得た抽象的な情報を統合
- 具体的な事象に落とし込む
この抽象と具体の行き来が、いい感じの学習負荷を脳ミソにかけてくれていたんじゃないかと。 アウトプットに近いんじゃないかという感覚があるんですよね。
具体例で話そうぜ
以下のようなScalaのコードがあるとします。 読めなくても大丈夫です。 むしろ読めない方が話がわかりやすいかも。
sealed trait Internship case class Treasure( startAt: ZonedDateTime, endAt: ZonedDateTime ) extends Internship object Treasure { def apply( startAtLDT: LocalDateTime, endAtLDT: LocalDateTime, zoneId: ZoneId ): Treasure = { val startAtZDT = startAtLDT.atZone(zoneId) val endAtZDT = endAtLDT.atZone(zoneId) Treasure(startAtZDT, endAtZDT) } } val startDateTimeZoned: ZonedDateTime = ZonedDateTime.of(2025, 8, 1, 9, 0, 0, 0, ZoneId.of("Asia/Tokyo")) val endDateTimeZoned: ZonedDateTime = ZonedDateTime.of(2025, 8, 31, 17, 0, 0, 0, ZoneId.of("Asia/Tokyo")) val treasure1: Treasure = Treasure(startDateTimeZoned, endDateTimeZoned) val startDateTimeLocal: LocalDateTime = LocalDateTime.of(2025, 9, 1, 9, 0, 0, 0) val endDateTimeLocal: LocalDateTime = LocalDateTime.of(2025, 9, 30, 17, 0, 0, 0) val japanZone: ZoneId = ZoneId.of("Asia/Tokyo") val treasure2: Treasure = Treasure(startDateTimeLocal, endDateTimeLocal, japanZone)
初学者がこのコードをみた時、以下のような疑問を持つんじゃないでしょうか。
sealedってなんやcase classってなんや- なんで
case classと同名のobjectってやつもあるんや - なんで
applyメソッドは1つしかないのに初期化の方法は2通りあるんや
このように疑問を細分化してから、個別にググって解消していきます。
sealedってなんや- ほーん、つけたら同一ファイル内でしか継承できなくなるんやな ↓
case classってなんやequalsとかcopyとか使うであろう基本メソッドを自動生成してくれる便利なclassらしいな ↓
- なんで
case classと同名のobjectってやつもあるんや- コンパニオンオブジェクトって言うんや、
Javaでいう静的メソッド置き場としての役割っぽい ↓
- コンパニオンオブジェクトって言うんや、
- なんで
applyメソッドは1つしかないのに初期化の方法は2通りあるんや- あー!
case classを定義した時点でコンパニオンオブジェクトも自動生成されてるんだ、だから実はapplyメソッドが2つあるのね
- あー!
そして、ググって得た抽象的な知識を組み合わせ、具体的なコードに落とし込んでいく。 ここまでして、ようやくコードが読めるようになります。
かったり〜〜〜AI(Gemini)に聞いてみようぜ
Scalaコードの解説(クリックで展開)
なにこれ
これはScalaというプログラミング言語のコードですね。
コードの概要
このコードは、インターンシップの期間を管理するための
Treasureというデータ構造を定義しています。特に時間帯情報(タイムゾーン)を考慮して、正確な期間を表せるように設計されています。
各部分の説明
sealed trait Internship:
- これは「
Internship」という名前のトレイト(Javaでいうインターフェースに似ています)を定義しています。sealedと付いているので、このトレイトを継承できるクラスは、このファイル内でしか定義できません。これは、インターンシップの種類を制限したい場合に使われます。- 現時点では
Internshipを継承しているのはTreasureだけですが、もし将来的に「SummerInternship」や「WinterInternship」など、異なる種類のインターンシップを追加する場合、それらもこのInternshipトレイトを継承することになるでしょう。
case class Treasure(...) extends Internship:
- これは「
Treasure」という名前のケースクラスを定義しています。Internshipトレイトを継承しています。- ケースクラスはScalaでよく使われるデータ保持用のクラスで、いくつか便利な機能が自動で提供されます(例えば、インスタンスの比較、コピー、
toStringメソッドなど)。TreasureクラスはstartAtとendAtという2つのフィールドを持っています。どちらもZonedDateTime型で、これは「日付、時刻、そしてタイムゾーン情報」を組み合わせたものです。これにより、例えば「日本時間の午前9時」と「UTCの午前9時」を区別して扱うことができます。
object Treasure { def apply(...) }:
- これは
Treasureケースクラスのコンパニオンオブジェクトです。コンパニオンオブジェクトは、対応するクラスと同じ名前を持ち、クラスの静的メソッドのようなものを提供します。- ここでは、
applyメソッドが定義されています。このapplyメソッドは、LocalDateTime(タイムゾーン情報を含まない日付と時刻)とZoneId(タイムゾーンID)を受け取って、内部でZonedDateTimeに変換し、最終的にTreasureインスタンスを生成するヘルパー関数として機能します。- これにより、
LocalDateTimeで日付と時刻を指定し、後からタイムゾーンを適用してTreasureを作成できるようになります。
val startDateTimeZoned: ZonedDateTime = ...とval endDateTimeZoned: ZonedDateTime = ...:
- これらは、2025年8月1日から8月31日までの期間を、最初から「Asia/Tokyo」(日本時間)のタイムゾーン付きで定義している例です。
val treasure1: Treasure = Treasure(startDateTimeZoned, endDateTimeZoned):
- 上記で定義したタイムゾーン付きの日時を使って、最初の
Treasureインスタンスを作成しています。
val startDateTimeLocal: LocalDateTime = ...とval endDateTimeLocal: LocalDateTime = ...:
- これらは、2025年9月1日から9月30日までの期間を、タイムゾーンなしの「ローカル日時」として定義している例です。
val japanZone: ZoneId = ZoneId.of("Asia/Tokyo"):
- 「Asia/Tokyo」というタイムゾーンIDを定義しています。
val treasure2: Treasure = Treasure(startDateTimeLocal, endDateTimeLocal, japanZone):
LocalDateTimeとZoneIdを使って、Treasureコンパニオンオブジェクトのapplyメソッドを呼び出し、2番目のTreasureインスタンスを作成しています。この方法は、日時をまずタイムゾーンなしで扱い、後から特定のタイムゾーンを割り当てたい場合に便利です。
まとめ
このコードは、インターンシップの期間を、タイムゾーンを考慮して正確に表現・管理するための、堅牢な方法を提供しています。特に、タイムゾーンの概念を扱う際に発生しがちな日付や時刻のずれを防ぐことを目的としていると考えられます。
Gemini鬼つええ!
「なにこれ」というプロンプトだけで、疑問の細分化から具体的な事象への落とし込みまで全部やってくれます。 もうググるなんて時代遅れなことやらなくていいっすね。
でもインプットしかしてないから身につかないんだよな。
おわりに
じゃあなんすか、AIなんて使わずにググれっていうんすか、ってわけではないです。 文明の利器から得られる恩恵は最大限に享受すべきだと思います。 幸いCARTAではGoogle Workspaceを利用しており、AI(Gemini)と会話できる環境が用意されているのですから。
一番大事なのは、疑問の細分化を自分で行う姿勢だと思っています。 自身から生まれた疑問に対する知的好奇心は、そうでないものとは比較にならないくらい高いものです。 「内発的動機づけ」ってやつですね。
その意識があれば、AIを利用した学習でもより豊かに、生産的になるんじゃないかなと思っています。 多分。
思考の主導権をAIに渡してはならない、という話でした。



