2024.12.10
“放置系”なのにサイバー攻撃を監視・検知、「統合ログ管理ツール」とは 最先端のログ管理体制を実現する方法
リンクをコピー
記事をブックマーク
中村学氏(以下、中村):次はfor expression、for式についてです。「式」と言うように、Scalaのforは式です。他の言語ではfor文と言われますが、Scalaのforは式です。そのため評価すると値になります。Scalaの式は大きく分けて2種類あります。予約語のyieldが付くものと、付かないものです。
yieldが付かないfor式は、基本的に他の言語のforeach構文とほぼ一緒です。全体の式としての評価結果はユニットという、何の意味ももたない値になります。yieldが付かないfor式の意味合いは、for文や他の言語のforeach文とほとんど一緒です。ここではyieldの付くfor式について、中心に見ていきたいと思います。
yieldの付くfor式をfor comprehensionと呼びます。聞いたことがある方いますか? サンプルコードがありますが、こんな感じでforとyieldを組み合わせて使います。このcomprehensionは、日本語で言うと“for内包表記”と言われたりします。
内包表記とは何かというと、内包表記の反対語として外延的記法、外延表記という表記の仕方があります。それは何かというと、集合を表現するときに、その要素の具体的な値を列挙して書いていくようなかたち。{ }を使ってカンマ区切りで具体的な値を列挙していって、その集合全体を表現するようなものを外延的記法と呼びます。
外延的記法は具体的でわかりやすいですが、無限の要素がある、例えば整数や自然数など、無限の要素がある集合を表現しようとすると、列挙しきれないなどいろいろな問題があるので、省略のようなことが使われます。そうした場合、誤解なく集合全体を表現するのが難しい場合があります。そのため、それを避けるために、内包的記法、内包表記と言われるような書き方が存在しています。
これは具体的に要素の条件を明示して、要素の集合を表すような書き方です。例えばxは整数かつx÷2の剰余は0になるというものの、x全体。それが偶数の集合になる、といったような数学の書き方があります。これを内包表記と言ったりします。
一部のプログラミング言語では、この内包表記を言語としてサポートしています。PythonやHaskellとか使われている方はご存じかもしれませんが、リスト内包表記と言って、リストの値を構築するのに、内包表記のような感じで書けます。
このx in rangeやy in rangeのようなものを書くことで、xとかyの条件を明示して、x * yでその集合全体の構成要素を表します。Haskellも、xが1から3までの間と、yが5から7までの間と。x * yが要素の構成要素なので、集合を表すような書き方ができます。数学の書き方と似ていますね。
Scalaのfor-yieldも、実はこれと同じもので、内包表記になります。Scalaのforを書いて、yieldを付けたものが他の言語で書くのとまったく同じ意味合いを持っている感じです。その要素の一個一個を表すx * yが一番左側に来るのか、それともfor式全体のyieldの後ろにあるのかで、大きく見た目が違いますが、やっている意味合いとしては同じです。
数学の書き方的なものは、そのxの要素が一番条件の左側に来るので、PythonやHaskellなどは数学に親しんでいる人はわかりやすいかと思います。余談ですが、僕なんかはけっこうxとかyとかの条件が先に左側に来て、最後にそれを使ってyield式が来るというので、個人的にはこちらのほうが好きです。
そのforの( )の中ですね。こちらにxとかyとかの条件を書いて、yieldが各要素の実際の計算になります。Scalaのfor式は( )の代わりに{ }を使えます。この{ }を使うと何がうれしいのかというと、複数の条件があったときはだいたいセミコロンで区切りますが、{ }で書くと、改行をセミコロンの代わりにできます。
条件式がxとyの2つぐらいであればそんなに変わりませんが、これが4つ、5つぐらいになると、1行で書くのはなかなかわかりづらくなっていきます。そのため、この{ }を使って改行で表すというのがよく使われます。条件が多くなってくると、こちらの{ }のほうが見やすくなります。ここまでが内包表記の一般的な説明です。
今までリストの作り方を内包表記でみてきましたが、Pythonだとリスト内包表記以外にも、生成したいオブジェクトごとに内包表記が用意されています。
例えばリスト内包表記だとさっきの[ ]ですが、セット内包表記の場合はこの{ }を使うと、全体としてセットになります。その要素の条件式や、その要素の式自体は同じですが、作られるオブジェクトがリストなのかセットなのかで分かれ、構文が用意されています。
Haskellでもリスト内包表記がありますが、HaskellにはGHCという実行環境があり、GHCには内包表記の拡張が用意されていて、その拡張を使うと、リスト以外の要素でも内包表記を使って書けます。例えば、Maybeのようなものを作りたいときに、内包表記を使えます。Scalaの場合、実はけっこうHaskellの内包表記と近いですが、基本的にmapとflatMapというメソッドを持っているオブジェクトなら、全部このfor式、内包表記で書けます。
最初にxの条件式にSeq("1", "2", "abc", "4")のようなものを用意して、yの右辺は全体的にOptionになる感じです。yのTry(x.toInt).toOptionとやると、1とか2とかはIntにできますが、abcがIntにできないので、ここがNoneになります。
yield式でyの2乗をやっていると、1と2と4の2乗としてSeq(1, 4, 16)が計算でき、モナドになってなくてもmapかflatMapを持っていれば、何でもfor式として使えるような構造になっています。なぜそんなことになっているかというと、あくまでfor-yieldは糖衣構文、シンタックスシュガーとして作られています。
for-yieldで書かれた式が、コンパイル時にはmapとflatMapを使った式に変換される感じです。(視聴者の「最初do式の互換だと思ってたら混乱しました」というコメントを受けて)僕も最初はdo式の互換だと思っていましたが、実はdo式よりはモナド内包表記なんです。HaskellのreturnとScalaのyieldが対応しているのかと思っていたら、実はそんなことなくて混乱していたんですが。
Scalaのfor式はHaskellのdo構文だよ、という説明をたまに見かけますが、逆に混乱を生む場合もあります。導入されたモチベーションはHaskellのdo式と似ていますが、for構文としてはdo構文よりも内包表記のほうが意味合いとしては近しいものだと覚えてもらえると、たぶんいいかなという気がしています。
シンタックスシュガー(糖衣構文)でmapかflatMapに変換される話ですが、上のように書いたfor-yieldの式は下のように変換されます。最初のSeq("1", "2", "abc", "4")の部分がflatMapのメソッド呼び出しになって、x =>が付いてさらにtryのtoOptionが付いて、そこにmapの呼び出しになる。
一番最後のところでy * yになるような感じで変換されます。今、二重のネストなのでflatMapとmapになっていますが、基本的に二重、三重、四重になってくると、flatMap、flatMap、flatMapとなって、最後のyield部分がmapに近しい値になる、mapに変換されるような構造になります。
for-yieldの結果がどんな型になるかは、最初のジェネレータ、最初の<ーの右側の型を見ると判明します。ここもSeqのflatMapになるので、全体としてはSeqになるだろうな、という感じです。
最初がVectorであれば、VectorのflatMapになるので、for式全体もVectorになりますよ。逆に、最初のx <- の右側がリストなので、リストのflatMapになり、for式全体がリストになります。こういった感じでfor式全体の型を解釈できます。
たいていはそこを見れば解決しますが、最初の部分で決定できない場合がたまにあります。例えばMapです。Mapの場合、最後のyield式がタプルになっていれば、Mapに変換できますが、タプルになっていないとMapをflatMapにしても、Mapにできないので、Iterableになるケースがあるんですね。
そのため、Mapが使われていると、yield部分を見ればfor式全体の型が何なのかがわかるかな、という感じです。
まとめにいくと、for-yieldはScalaにおける内包表記です。ゆるふわなので、mapとflatMapをもっていれば全部コンパイラは通します。最初の部分の型がわかれば、だいたい結果の型がわかります。
(視聴者の「ポリモフィズムでIterable解決している?」というコメントを受けて)そうですね。MapのflatMapメソッドが戻り値の型をMapにするのかIterableにするのかは、けっこう難しいメカニズムを使っていて。渡されるファンクションの型によって結果の型が変わるので、それはけっこうScalaの型システムのおもしろポイントですね。また2.12と2.13でこの辺の実装がちょっと変わっていたりするので、興味のある方は深追いしてみるといいかもしれません。
(次回につづく)
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
2024.12.09
10点満点中7点の部下に言うべきこと 部下を育成できない上司の特徴トップ5
2024.12.09
国内の有名ホテルでは、マグロ丼がなんと1杯「24,000円」 「良いものをより安く」を追いすぎた日本にとって値上げが重要な理由
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
2024.12.10
職場であえて「不機嫌」を出したほうがいいタイプ NOと言えない人のための人間関係をラクにするヒント
2024.12.12
今までとこれからで、エンジニアに求められる「スキル」の違い AI時代のエンジニアの未来と生存戦略のカギとは
PR | 2024.11.26
なぜ電話営業はなくならない?その要因は「属人化」 通話内容をデータ化するZoomのクラウドサービス活用術
PR | 2024.11.22
「闇雲なAI導入」から脱却せよ Zoom・パーソル・THE GUILD幹部が語る、従業員と顧客体験を高めるAI戦略の要諦
2024.12.11
大企業への転職前に感じた、「なんか違うかも」の違和感の正体 「親が喜ぶ」「モテそう」ではない、自分の判断基準を持つカギ