退職のお知らせ
今年いっぱいで株式会社オンザロード(以下OTR)を退職することになりました。 有給消化は9月からずっと半休という形で取るという形だったのはちょっと珍しいかもしれません。
OTRに入ったのは2010年1月1日ですから、11年勤めたことになります。 企業のIT化を進める会社(とか開発スタイル)のIT化が当時の自分としては全然進んでいないと感じていたところ、 「そういうこと自分で好きにやっていいよ」と誘われて入社しました。
「少なくともそういう環境が自分の手で作れるまではこの会社にいよう」と思っていたので、 実際に開発面でやりたいことを一通りやった後も留まっていたのは、ふりかえるに次の2点があったからです。
- 自分にとって理想的なチームで働ける
- 出来るなら手段は問わないという雰囲気
今回はチーム移籍ということで、1点目の利点は引き続き得られます。 2点目については、OTRにいる中である意味において成長したため、相対的に比重が小さくなっていました。
OTRに入ってからは本当にいろいろなことをやりました。 OTRはSIerなので、当然プロジェクトにアサインされはするのですが、入った経緯もあり入社直後から3~5割程度はプロジェクトに直接関係のないことを自由にやらせてもらいました。 当時はソースコードをSubversionで管理していたのですが、それをGitに移行する作業すべてを入社直後からやりました。 そのあとはJenkins(当時Hudson)を導入したり、新規参入者向けのPC選定やキッティング、社内LANの設計から実装、運用など、開発インフラの整備を継続的にやっていきました。
プロジェクトの方でも、技術力を買われて入社したからか、プロジェクトの初期段階から意見を求められることが多く、普通の中小SIerにしてはかなりの無茶を色々としました。 あるプロジェクトで設定ファイルで簡単なループをさせたい、みたいな要件があったときに、最初出てきた設計が1行ずつ解釈していくタイプのインタプリターが出てきたので、 「それやりたいならそれっぽい構文持たせてAST走査するインタプリターにしましょう!」と言ったらあっさり採用されたのでビビりました。 その時はC#だったのでNParsecで実装しました。
また、時間があれば自動化できる部分はないか考え、色々と試行錯誤しました。 その中で、Officeドキュメントの自動生成に関する技術はかなり役に立ちました。 OTRに入ってExcel方眼紙で苦しめられたことはありません。むしろ楽しんで自動化して倒しました。
この「勝手に楽しむ力」みたいなものがOTRで得られたある意味もっとも大きな部分でした。
去年、外部の大規模アジャイル開発のスクラムマスターとして働いたのですが、そこでの経験というか自分の中での気づきがありました。 そこではCOBOLの資産があり、それが大きな意味を持っていたのですが、その時点ではCOBOLなんて読めませんでした。 SMに割り当てられた環境にはVisual Studioは入っておらず、VSCodeしかありませんでした。 しかし、Windows 10にはcsc.exeがあります。 ・・・休憩時間にCOBOLのパーサーを書き始めました。
前職では「ABAPですら無理なのに、COBOLとか絶対無理だわー」と思っていたのですが、 休憩時間に勉強を兼ねてcsc.exe使ってCOBOLのパーサー書いてしかも楽しんでいる自分に気づいたのです。
このとき、「出来るなら手段は問わない雰囲気」が自分の中で急激に重要でなくなりました。 だいたいどんな環境でも勝手に楽しめそうなこと見つけて勝手にやれる、ということに気づいたのです。
この「勝手に楽しむ力」ですが、実にSIer向きの力だとは思います。 それを育ててくれたのはまず間違いなく「出来るなら手段は問わないという雰囲気」だと思っています。 その面でも、OTRは自分にとってとてもいい環境でした。
ですが、今回チーム移籍という形でOTRを離れることになりました。 今の自分にとっては、今のチームはいい環境です。 自分の好きな書籍にMy Job Went To India*1というものがあるのですが、 そのなかで「どんなバンドで演るときも一番下手なプレイヤーでいろ」というジャズギタリストの言葉あります。 この本を読んだときは確かにそうだ、と思ったものですが、今はちょっと違う意見を持っています。
少なくとも今のチームにおいて、「どの分野においても一番上手」な人なんていませんし、同時に「どの分野においても一番下手」な人もいません。 足りないところを補い合えるからこそのチームであり、「自分が一番下手」な分野がある限り大きな学びはあります。 チームという塊で見たときに「下手なこと」がある限り、チーム自体の伸びしろは残っています。 そして今のチームは(その原動力が id:kyon_mm に偏っているという問題はあれど*2 )成長を続けよう、というモチベーションが高いです。
正直な話、自分はSIer向いてると思っているし、ひとりでもやっていける自信はあります。 が、チームとしての成長は一人ではできませんし、同じようなチームをまた最初から作れる自信は全くありません。 なので、実は自分にはOTRをやめる積極的な理由はありません。 きっかけは他のメンバーの選択によるところが大きくはありますが、チームメンバーとして移籍を決めました。
次の職場についてはまた別のエントリーにする予定です。 チーム移籍活動自体の話も書く予定ではいます。
例のリストも貼っておきます。
次の職場でもがんばります!
re:僕にとってMaybe / Nullable / Optional が、どうしてもしっくりこないわけ。
元ネタ: 僕にとってMaybe / Nullable / Optional が、どうしてもしっくりこないわけ。 - 亀岡的プログラマ日記
OOPの文脈で見ると、元の記事が言っていることもわからなくはないのですが、対象が広すぎていろいろと不正確になってしまっているので、ちょっとまとめてみます。
元の記事が対象にしているのは、Maybe / Optional / Nullableの3つです。 対応する型を持つ言語を見てみると、下記のようになります。
これらは、「値がないこと」を表すもの、という見方では同じですが、それぞれ異なる価値観の元に作られています。
Maybe/OptionalとNullable
これらはすべて型パラメータを取ります*1。
しかし、この中でNullable
だけは型パラメータに値型のみという制約が付きます。
これは、C#のNullable
は他のものとかなり性質が違うことを意味しています。
元の記事では並べて書かれていましたが、そもそもNullable
に関してはここに並べるべきものではありません。
C#のNullable
C#のNullable
は、値型にもnull
が使いたいという動機で導入された機能です。
そのため、Nullable
はMaybe
やOptional
と違って、むしろnull
を積極的に使えるようにするための機能と言えるでしょう。
これによってC#の上ではNullable
を使うことでコード上の見た目だけに関しては、値型でもnull
が使えるように見せています。
見た目だけの話であれば、SwiftのOptional
と同じように型名の後ろに?
が付くため、同じような言語機能に見えますが、実際には全くの別物と考えたほうがいいでしょう。
JavaのOptional
JavaのOptional
は、メソッドの戻り値として使うことが想定されているらしく、引数やフィールドに使うことは想定されていないそうです。
JavaのOptional
は参照型なので、Optional
自体がnull
になりうるという点を考えると、引数に使うべきではないというのもある程度納得できます*2。
Javaでは現状、型パラメータに指定できるのは参照型だけであり、null
になりうるのも参照型だけです。
そのため、JavaのOptional
はC#のNullable
と違って、(戻り値としての)null
を排除しよう(少なくとも、減らそう)という動機で導入されたとみていいでしょう。
SwiftのOptional
SwiftのOptional
は、Javaと名前は同じですが、より言語の仕組みに根差しています。
Javaでは、Optional<T>
型の変数にT
型の値を代入できません。
Integer i = 42; Optional<Integer> x = i; // コンパイルエラー
それに対して、Swiftではそれが可能です。
var i: Int = 42 var x: Optional<Int> = i // OK // Optional<Int>はInt?とも書ける
SwiftではOptional
ではない型にnull
*3は代入できません。
ですが、Optional
という仕組みによって今までの知識からあまり外れない書き方でnull
を扱えるようになっています。
HaskellのMaybe
HaskellのMaybe
は実装としてはSwiftのOptional
に一番近くなっています。
ただ、他の言語と違ってHaskellではそもそも他の言語のようなnull
はなく、Swiftのように既存言語ユーザーを取り込むために「今までの知識からあまり外れない書き方」というのも求めていません。
結果だけ見ると、JavaのOptional
と同じように、Maybe t
型の変数にt
型の値は代入できません。
Nullのダメな理由
元記事では、「Nullは何がダメなんだっけ?」について、
- Nullは型ではない
- あるメソッドがNullになるかどうかの判断ができない
が挙げられています。 細かい点ですが、ここにも誤解があります。
(少なくともJavaでは)Nullは型を持つ
まず、Javaの話であればnull
は型を持っています*4。
そのため、「ClassHoge
のnull
」という表現は、少なくともJavaにおいては正確ではなく、正確には「ClassHoge
型に暗黙変換されたnull
」とでもなるでしょうか。
詳しくは言語仕様を参照してください。
ここで言いたかったのはおそらく、「null
はどんな型にも変換できてしまう」ということでしょう。
あるメソッドの結果(もしくは変数)がNullかどうかの判断が型だけからできない
これも細かいですが、あるメソッド(の結果)がNullになるかどうかの判断ができないというのは、正確には「型を見ただけでは」できないのであり、元記事中にあるように、if
文によって実行時にはチェックできます。
本題
さて、ここからが本題です。
元記事では、Optional
が解決するのは2だけ、としていますが・・・
Javaの場合
JavaのOptional
では、Optional<T>
型とT
型は全くの別物であり、相互に暗黙変換できません。
そのため、
- (
null
に相当する)Optional.empty()
はOptional
型の変数にしか入れることはできない*5 - あるメソッドの結果が
Optional.empty()
になりうるかどうか型だけで判断できる
となります。
Swiftの場合
SwiftのOptional
は、Optional<T>
型の変数にT
型の値を代入できますが、その逆はできません。
そのため、
- (
null
に相当する)nil
はOptional
型の変数にしか入れることはできない - あるメソッドの結果が
nil
になりうるかどうか型だけで判断できる
となります。
Haskellの場合
HaskellのMaybe
は、Maybe t
型とt
型は全くの別物であり、相互に暗黙変換できません。
そのため、
- (
null
に相当する)Nothing
はMaybe
型の変数にしか入れることはできない - ある関数の結果が
Nothins
になりうるかどうか型だけで判断できる
となります。
元記事が言いたかったこと
このように、Optional
もしくはMaybe
は、1も2も解決できています。
これは十分なメリットであり、このメリットだけでもOptional/Maybeは有用です。
ただ、元記事を見ると、
本当に解決したいのは「Nullに型独自の処理をさせたい」なので、残ったままです
とあります。 つまり、本当に問題にしていたのは、「型Aの値がない場合と型Bの値がない場合で別の処理をさせたい」となります。
if文相当の処理を書く必要性
Optional/Maybeを使ったコードで頻繁にif
相当の処理を書いてしまっている場合、それはそもそもOptional/Maybeの使い方を間違っている可能性が大きいです。
リストを操作する関数にリストを返す関数が多く含まれるのと同じように、あるいはTask
を返す関数を内部で呼び出している関数がTask
を返す関数になるのと同じように、Optional
/Maybe
を返す関数を内部で呼び出している関数は、その関数の戻り値もOptional
になることがよくあります。
この場合、if
相当の処理を書くのは間違っています。
// Javaだと思う(Swiftは書いたことないので) Optional<User> user = findUser(pred); if (user.isPresent()) { // 何かする return Optional.of(何か処理の結果); } else { return Optional.empty(); }
例えば、「何かする」の部分がuser
の中身を使った関数を呼び出すような処理の場合、
// 値がなかった場合のことは気にしない return findUser(pred).map(user -> 何かする関数(user)); // ラムダ式を使わずに直接関数を渡す、でも可
のようになるでしょう。
map
の他にも、様々なメソッドが用意されているため、if
による分岐に頼る場面は少なくなります。
Haskellの場合、do
構文によってさらに複雑な例でもすっきり書けますが、それはまた別の話。
手続き的には見えない
さて、上のコードが手続き的に見えるでしょうか?
呼び出し側はOptional
のチェックを行っておらず、コードも短く簡潔です。
ではオブジェクト指向プログラミングではどのようになるでしょうか?
User
に対してNullObject
を用意して、このようになるでしょう。
public interface User { 戻り値の型 何かする処理(); } public class UserImpl implements User { @Override public 戻り値の型 何かする処理() { // 何かする return 何かした結果; } // ... } public class EmptyUser implements User { @Override public 戻り値の型 何かする処理() { return 戻り値の型のNullObject; } // ... }
// 呼び出し側は意識しない(意識できない) return findUser(pred).何かする処理();
呼び出し側だけ見ると、Optional
のときとそんなに変わってませんよね*6。
つまり、元のコードも手続き的には見えないと言っていいでしょう。
NullObjectをちゃんと使いたい、というよりは、Optionalをどう使うか考えたい
OOPの文脈で、どこまでOptional
を使えばいいのかというのはよくわかりません。
Optionalを使うと、どうしてもコードはOOPから離れて行ってしまうという感覚は確かにあります。
どのあたりでバランスを取るのがいいのかはケースバイケースでしょうし、一般化できるものではないと思っています。
コメント欄では id:kyon_mm が(やはりOOPの文脈で)
Optionを返していいのはむしろprivateやprotected的なものだけだと思っていて、それ以外はオブジェクトだったり、バリアント(判別共用体)的な感じで返すのが「オブジェクトとやり取りをしていて、それぞれに責務が割あたっている」といえるのではないだろうか。と思っています。
と言っています。
これはこれで一つの態度ではありますが、基本的なライブラリでは Optional
を返す関数*7というのはもっと積極的に作られることになると思います。
FPに軸足を置くか、OOPに軸足を置くかは、対象の性質やメンバーのスキル等によって決めていくしかないでしょう。 ただし、間違ったOptionalの使い方をもって「しっくりこない」というのであれば、まずはOptionalの正しい使い方を学び、実践すべきです。 そのための言語としては、F#なんていかがでしょうか。
引っ越した
バイバイ名古屋 pic.twitter.com/QJkDERqHhV
— ふ''れいす (@bleis) 2015, 9月 27
ということで、引っ越しました。 新住所は愛知のどっかです。
引っ越してほしくなったものです。 届いたら喜びます。
「変数に型がないということの利点について考える」の問題について考える
id:perlcodesample さんの 変数に型がないということの利点について考える - サンプルコードによるPerl入門 から。
ううむ。
けれども、型がないということは、本当に素晴らしいことです。
型がないことによって、たくさんの面倒から解放されるからです。
冒頭のこれが、「静的型付き言語にはメリットが(ほとんど)ない」と言っているように思えてしまいます。 コメントのやり取りを見ても、ある程度そう考えているように受け取れます。
勘違いなどが多く見られたので、補足というか、反論というか、そんな感じのことを書きます。
追記:
ごく一部、このエントリを「動的型付き言語と静的型付き言語を比べて、静的型付き言語の方が素晴らしい言語である」ということを言うためのものだと勘違いしている人を見かけました。
このエントリは、そこについては言及していません。
あくまで、元記事で「動的型付き言語のメリット」とされている部分について、「そうではないよ」と指摘するためのエントリです。
どのような型の値でも代入できる?
これは、「変数に型を明示する必要がない」ということですよね。 型推論によって、静的型付き言語であっても変数の型は明示不要です。 例えば、Perlで
my $str = 'Hello'; my $num = 1; my $nums = [1, 2, 3]; my $person = {age => 2, name => 'taro'}; my $ua = LWP::UserAgent->new;
という例を挙げていますが、静的型付き言語であるF#でも、
let str = "Hello" let num = 1 let nums = [1; 2; 3] (* 追記: Perlの方の$personをクラス的な何かと勘違いしてたので、連想配列に合わせました。 let person = { Age = 2; Name = "taro" } *) let person = Map.ofList [("age", box 2); ("name", box "taro")] let ua = LWP.UserAgent()
と、ほとんど変わらない記述が可能です。
記述量がとても短くなる?
これは、上の例で説明したように、型推論機能さえあればほとんど問題になりません。 型推論が不完全のくだりは取り下げているので触れません。
また、コンパイル時間のことを挙げていますが、対話環境を持った処理系であれば、 一部を手軽に確認することが可能です。 そして、ScalaやF#など、対話環境を持った静的型付き言語はいろいろと存在します。
統合開発環境でのメソッドの自動補完機能を実装したことがないので、 その実装が難しくなるかどうかは判断がつかないです。
変数に型がないと変更に強い?
ここで言っているのは、おそらくこういうことでしょう。
ClientA ua = c.client();
こういうJavaのコードがあったとして、clientの返す型がClientBに変更された場合、
ClientB ua = c.client();
という変更が必要になる、と。 そうであれば、ここでも型推論により解決されます。
let ua = c.client()
それはともかくとして、このように局所的な例だけで変更に強い、としてしまうのには違和感が残ります。 「変更に強い場合がある」程度ならいいのですが、それだとメリットになりませんよね。
関数のオーバーロードが不要になる?
動的型付き言語ではオーバーロードが不要になるのではなく、(型による)オーバーロードが実現できないのでは・・・
というのは置いといて、
変数に型がないことによって、関数の重複を減らすことができるという大きなメリットがあります。
これはどういうことかよくわかりません。 関数のシグネチャのことを言っている・・・?
public T sum(A a) { ... } // このpublic T sumと、 public T sum(B b) { ... } // こっちのpublic T sumが重複している?
これを「大きなメリット」と呼ぶのはつらい気がします。
それと、オーバーロードを持たない静的型付き言語のことも、たまには思い出してあげてください。 ちなみに、F#ではメソッドでのみオーバーロードが可能で、関数でのオーバーロードはできません。 ではどうするか?判別共用体というものを使います。
(* sumに渡せる判別共用体を定義する *) type SumType = | A of int list | B of float list let sum = function | A value -> (* Aの場合の処理 *) | B value -> (* Bの場合の処理 *)
「sumに渡すための型を定義しなければならず面倒」と言われてしまうかもしれませんが、 型を定義したことによって大きなメリットが生まれます。 それは、考慮漏れや、一致しない条件をコンパイル時に発見してもらえるようになることです。
例えば、「Aの処理は汎用的でいいんだけど、(高速化のために)2要素以下の時は直接計算したい」という要望が上がったとします。
(* sumはSumTypeを受け取って、intを返す関数だとする *) let sum = function | A value -> (* Aの場合の処理 *) | B value -> (* Bの場合の処理 *) | A [x; y] -> x + y | A [x] -> x | A [ ] -> 0
こう書いてしまった場合、後ろ3行のケースにはどうやってもたどり着けません。
なぜならば、| A value -> ...
のケースがそれらのケースでも当てはまってしまうからです。
これを、F#コンパイラは警告として知らせてくれます。
では修正しましょう。
let sum = function | A [x; y] -> x + y | A [x] -> x | A [ ] -> 0 | A value -> (* Aの場合の処理 *)
今度は、修正を間違ってBのケースを消してしまいました。 この場合でも、F#コンパイラは「Bのケースが考慮されていない」という警告で知らせてくれます。
これに対しても「発見が早いか遅いかの違いだ」と言うことはできるでしょう。 しかし、たとえ「何かバグを埋め込んだとしても絶対にテストで検知できる」というありえない仮定をしても、 バグの発見は早ければ早いほどその修正は楽であることが多いのです。
条件の考慮漏れや、一致しない条件を書いてしまうことがあるのと同じくらい、 その条件分岐に対するテストを書き忘れるというミスも起こりえます。 こういうミスをはじくことができる言語があり、 そういう言語を使うことである種のバグはなくなるのです。
その中にはほかの言語では「値レベルの問題」である、NullPointerExceptionの問題も含まれます。 OptionやMaybeについて調べてみるといいでしょう。
複数の型を受け取りたいときに、インターフェースを実装する必要がない?
ここでは「Java」と限定しているので深くは踏み込みませんが、
変数に型がないことによって、クラスの実装が重複がなくとてもシンプルになります。
は気になります。 変数に型があるなしと、クラスの実装の重複にどのような関係があるのでしょうか?
C++のテンプレートのような機能も必要がない?
ここは、「何でもかんでも1つの関数に詰め込むことができる」と言っているように思えます。 これをメリットに含めるのは無理があります。
変数に型がないとどのような型の値が代入されているかわからないという批判に答える?
静的型付き言語のメリットの一つとして、「型がドキュメントとして使える」というものがあります *1。 型があれば「その関数にどんなものが渡せ、どんなものが返ってくるのか」程度の情報は得られます。
例えば、
val f: 'a list -> int
であれば、「リスト(入っている要素は何でもいい)を受け取って、intを返す」ことがわかります。 他にも、「(よほどマジカルなことをしていない限りは)この関数はリストの各要素には用はないのだな」ということもわかりますし、「引数は一つしかとらない」という確信を得ることもできます。
ここではf
なんていう適当な名前を付けましたが、これも適切な名前にすればさらにドキュメント性は高まります。
この種の判断を、動的型付き言語ではドキュメントに頼るか、自分で関数を読み解く必要があります。 記事中では後者の方法を紹介していましたが、関数が複雑になればなるほど、その作業は難しくなり、 また間違いも犯しやすくなっていきます。
変数に型がないことのメリットは重複を少なくソースコードがかけること?
ここでの重複は何を指しているのでしょうか? それに、
静的言語はインターフェースやクラスをそのたびに実装しなければならないので、修正や変更が行いづらいです。その点では、保守性は低いといえます。
というのは色々と勘違いが含まれています。
インターフェイスやクラスを実装することは必須ではない
静的型付き言語であってもインターフェイスやクラスをそのたびに実装しなければいけないわけではありません。 例えば、Scalaには構造的部分型があるので、
def getName(x: { def name: String }) = x.name
のように、「nameを持つ型」という指定が可能です。 nameさえ持っていれば、どんなものでもこのメソッドに渡せます。 SML#には、多相レコードがあり、
fun getName x = #name x
のように、同じく「nameを持つ型」という指定が可能です。
静的型付き言語の方が修正が容易なものも多い
静的型付きの言語は、外部に影響を与えるような修正は動的型付き言語に比べて容易です。 例えば、
- クラス名を変更する
- メソッド名を変更する
- メソッドの引数を変更する
- メソッドの戻り値を変更する
のような修正は、静的型付き言語では影響の及ぶ範囲をコンパイラがチェックしてくれますので、 変更してエラーになった部分を潰していけば修正作業は終了です。 コンパイルのチェックはテストにも及ぶため、テスト側の修正漏れの恐れもありません。 IDEによっては、そもそもこの種の変更をIDEが自動でやってくれるものもあります。
それに対して、動的型付き言語の場合、どこかのテストに不備があった場合、バグを埋め込んでしまうことになります。
思うに、動的型付き言語の「修正の行いやすさ」は、メソッド内や、クラス内に閉じている場合のみに言えるのではないでしょうか。
その「変更の強さ」は「バグの埋め込みやすさ」に直結している
例えばあるインターフェイスにメソッドを追加した場合、その実装クラスすべてにメソッドを実装する必要があります*2。 そして、これを指して「静的型付き言語は変更に弱く、動的型付き言語は変更に強い」としているのであれば、「変更の強さ」についての考えが甘いでしょう。
確かに、動的型付き言語であれば実装クラスにメソッドを追加するだけで「そのまま動かす」ことは可能です。 が、それは「正しく」動き続けているわけではありません。 実装クラスにメソッドを追加したということは、それがどこかで使われる、ということです。 どこかでその追加したメソッドを呼び出すようにして、そのメソッドを呼び出すためのレシーバとして使っているインスタンスがほとんどの場合メソッドを追加した実装クラスだったとしても、たった1パターンでも違うクラスのインスタンスが入ってくるような場合、それをどうやって修正するのでしょうか。
静的型付き言語では、確かにインターフェイスにメソッドを追加しただけでは「そのまま動かす」ことは無理ですが、コンパイルエラーを潰せば「正しく」することは容易ですし、IDEの力によって「このメソッドを呼び出している個所を洗い出す」ことも容易です。
全体を通して
コメント欄含め、全体を通してみると、テストを過信しすぎです。 全数テストでもやらない限り、テストで「保証」を得ることはできません。
また、静的な型があればバグが減るのかどうかですが、最終的なバグの数(システムに残ってしまう見つからなかったor放置されたバグの数)は同じくらいになるかもしれません。 ですが、バグの絶対数(システムが死に絶えるまでに見つかったバグの数)は減るはずです。 なぜなら、動的型付き言語であれば埋め込んだであろうバグを、そもそも入れ込まなくなるから。 品質どうこうは置いておいたとしても、この点は非常に重要です。
例えば、例に挙げられていたsum関数のテストですが、sum関数自体のテストはそれほど手間は変わらないでしょう。 しかし、sum関数を呼び出している側がsum関数が対応している型を渡しているかどうか、という確認は、 直接的でないにしても行う必要があります。 この確認に一つでも漏れがあった場合、それは静的型付き言語では埋め込みえなかったバグです。
静的型付き言語では、sum関数呼び出し時の型に関するテストは不要ですし、漏れもないことが保証されます。 こういった言語を実際に使ってそれなりの大きさのシステムを構築してみれば、テストの絶対数が少なくなることを感じれるはずですよ。
第一回 関数型言語勉強会 大阪に行ってきた
44人の枠に一時期80人以上が申し込むという人気ぶりの勉強会、第一回 関数型言語勉強会 大阪に行ってきました。
とても面白い内容の発表ばかりで、行ってよかったです。
勉強会自体について
大阪は関数型言語に関する勉強会を開く土壌はある*1と思うのですが、今まであまり聞かなかったので、今後も続いてくれるとうれしいです。
もともとの主催者が多忙により勉強会に来れなかったのもありますが、運営面ではちょっとこなれてないな、という印象を受けたました。
今後続いていってほしい、という思いも込めて、気になった点を列挙します。
用語があばばばば
誤用すんな!とか言うつもりはないですが、曖昧に使ってると初心者をより混乱させることになるのではないのかなぁ、とか思うのです。
関数
「手続型言語での関数と関数型言語での関数は別物で、関数型言語の関数は数学的な関数だよ」的なアレ。
まぁより近くはあるだろうけど、それはそれでどうなんだ、という話もあります。
その関数って部分関数を言っているのか全域関数を言っているのか、そもそも停止性は・・・とかそういう。
数学「的」だからそのものじゃないよって意見はあるかもしれません。数学「の」関数だよと言ってしまうとどうなんだろ。
言葉尻をとらえすぎてますかね。
カリー化
その「カリー化」って何を指して言っていますか?というのがあります。
- 複数引数の関数を、一引数関数のみで表す「カリー化」(currying)
- 「カリー化」された関数(curried function)
- タプル形式の関数を「カリー化」すること(curry)
- タプル形式の関数を「カリー化」するための curry 関数(let curry f = fun a b -> f (a, b)とかそんな)
とりあえず、カリー化って言葉には色々と意味がある、ってことは頭の片隅にでも置いておくといいでしょう。
詳しい意味や、カリー化の利点については各自調べるということで。
末尾再帰
「末尾再帰に対応」とかって表現をされるとこう・・・
末尾再帰自体は何かの機能を指すわけではなく、単に再帰関数の形式の一つにすぎません。
乱暴に説明すると、「再帰呼び出しから戻ってきた後にすることが何も無いような再帰」のことです。
例えば Java で、
// 末尾再帰ではないfact static int fact(int n) { if (n == 0) return 1; else return n * fact(n - 1); // 再帰部 }
は、再帰から戻ってきた後に、その結果と n を乗算する、という処理が残っています。
それに対して、
static int fact(int n) { return factImpl(n, 1); } // 末尾再帰なfactImpl static int factImpl(int n, int acc) { if (n == 0) return acc; else return factImpl(n - 1, acc * n); // 再帰部 }
では、再帰から戻ってきた後はその結果を返すだけになっており、処理は残っていません。
で、この「末尾再帰」になっている再帰はループに変換することができ、それを「末尾最適化」と言います。
「末尾再帰に対応」という表現があった場合、まず間違いなく「末尾最適化に対応」のことを言っていると思っていいでしょう。
末尾最適化されると何が嬉しいか、というとそれはスタックオーバーフローが起きなくなるという一点に尽きます。
再帰呼び出しによるスタック消費がループになることでなくなるわけですから当然ですね。
なんで末尾再帰をループに変換できるのか、という話は各自調べるということで。
証明
証明って言葉を安易に使うとこわい人たちがこわいので、気を付けましょう。
同様に、検証とかもこわい人たちがこわいので、気を付けましょう。
いけがみさんの発表
初心者へのフォローという意味では、唯一といってもいい発表でした。
が、これは初心者向けどうこうを抜きにして素敵な発表でした。
Haskell が好きであるにもかかわらず、「Haskell に影響されすぎだ。自重しろ」とたしなめ*7、いけがみさんの思う関数型言語につて熱く語ってくれました。
発表資料は公開されており、これなかった人向けに書いた記事も用意するという素晴らしさ。
これ ust とか録画とかなかったのは本当に残念・・・
uskz さんの発表
おそらく一番置いてけぼりの人を出した発表であることは間違いないでしょう*8。
あれをもって「関数型無理だわー」となってしまうのは色々と残念なので、とりあえずは「突っ走っていくとああいう世界もある」という程度の認識でお願いしたいところです。
「関数型こわい」ではなく、「uskz さんこわい」あたりでどうでしょうか。
このセッションでの発表資料は後でじっくり読み解きたいですね。
しゃみのさんの発表
まだ高専 5 年生って、俺の弟と同い年だー!
にもかかわらず何とも言い難いこの・・・えっと、その。弟にもっとがんばれと言いたい。いやマジで。
このセッションも uskz さんの発表に次いで、置いてけぼりの人を出した発表であるように思います。
これは前提条件色々とすっ飛ばしているためですので、まぁこの発表もとりあえず置いておいてもいいです。
このセッションの理解のためには、前提知識として
- 代数的データ型 (一応 Bool と Either という説明はあったので、そのあたりから調べていくといいでしょう)
- ラムダ計算 (ラムダ式ではなく)
の理解 (なんでそういうものが必要なの?という問いに答えられるだけの理解) が最低限必要でしょう。
代数的データ型はまだしも、ラムダ計算を Wikipedia とかで真面目にやっても、そういう理解を得るのは難しいような気がします。
なのでこれも、とりあえずは「そんな世界もある」程度でいいのではないでしょうか。
関数型の利点?
マルチコア時代には状態を考えなくていい関数型が有利!的な話が一部あったのですが、正直これに関してはかなり懐疑的です。
タダ飯を食える時代は終わった、これからはマルチコア時代だからアプリケーションでの対応が必須!とか言われ始めてかなり経ちますが、実際どうですかね?
未だにタダ飯食ってる人多んじゃないですか?
確かにタダ飯食えなくなった人もいるでしょう。ですが、大多数の業務系プログラマはいまだにタダ飯を食らい続けているのです。
なんでか。
結局、当初の想定ほどマルチコアが普及していない、というのと、PC のユーザは現状の性能に満足しつつある、というのが大きいのではないでしょうか?
まわりを見てみると、ようやっとデュアルコアが普通になったかな、程度の普及率です。メインストリームはたぶんもうすぐクアッドコアが普通になっていくでしょうが、でも当初の想定よりかなり遅い速度ですよこれは。
これは CPU 性能向上の停滞を意味しているわけではありません。CPU の性能は年々向上しています。どこが? (もう伸びない、と言われて久しい)シングルスレッド性能が、です。
そしてほとんどのユーザ現状の性能に満足しつつあります。正しくは、マルチコア CPU を使わなければならないような用途に PC を使ってはいない、かもしれませんがまぁ同じことです。
CPU の性能は上がり続けているし、ユーザは満足しつつあるし、正直 IO 周りを何とかした方がよっぽど性能の向上を体感できる(HDDをSSDにするとか)。
こうして我々はタダ飯を食らい続けているのです。
ただ、マルチスレッドのプログラムをより安全に簡潔に間違いも少なく組める、というのは大きな利点ではないものの、確かな利点です。
今後、業務プログラムでも徐々にマルチスレッド対応が必要になる部分というのは徐々に増えていくかもしれません。
もしそうなったときには、このあたりはメリットとして見ることができるようになるでしょう。
でも、これはすぐにどうこう、という話ではないですよ、とだけ。
まとめ
今日はなごやこわいの要素は全くなくて、むしろみんなが大阪すごいを体感したひなのではないか。 #おおさかすごい #fpstudy
いいこと言いますね。
別に、最初から今回の発表全部を理解しなきゃ関数型言語使っちゃいけない、とかって規則はないわけですし、そんなこと言ったら誰も関数型言語使えなくなっちゃう。
もっとこう、軽い気持ちで始めてみてもいいと思うんです。
そのためにはいけがみさんの発表で出てきたような本を読んでみるのがいいでしょう。
分からなかったら、人に聞いてみればいいでしょう。
聞く人がいなければ、勉強会でも開催してみればいいでしょう。
最後に、関数型プログラミングやってみたい人に向けて (主に静的型付きの) 関数型の利点を伝えている Web 上の資料を紹介しておこうと思います。
- [http
- //www.itpl.co.jp/tech/func/essense_of_fp(sea0305).pdf:title=函数プログラミングのエッセンスと考え方]:いけがみさんの記事で参考にされていた資料で、名古屋の小笠原さん作の資料です。
- [http
- //www.slideshare.net/osiire/3-8660480:title=Start F#!]:これも小笠原さんの資料で、非関数型プログラマ向けに丁寧に「F# は何が嬉しいか」を解説しています。
- [http
- //www.slideshare.net/bleistift/cvbf:title=C#/VBプログラマのためのF#入門]:拙作の資料で、F#の紹介になっています。
- [http
- //www.atmarkit.co.jp/fdotnet/special/introfs_01/introfs_01_01.html:title=F#で初めての関数型プログラミング]:ちょっと前に書いた記事で、関数型プログラミングで割と勘違いされがちな部分を重点的に解説しました。
F# に偏ってる?いや、名古屋に偏ってるんだ!(何
*1:某大学とか某大学とか某大学とかあるので
*2:概要くらいは開催前までに埋めてほしいかも
*3:この勉強会をどのような位置づけに考えているかでやり方は変わるだろうけど、入門レベルの人をどうするか、というのは考えた方がいいとは思う
*4:このあたりはわんくま勉強会の発表毎のレベル表記はそれなりにいいと思う。3段階くらいでいいと思うけど
*5:使っていいのであれば、開催前にホワイトボードなどに書いておいてくれた方がありがたい
*6:他の勉強会の宣伝など、関西のコミュニティをかき混ぜるための時間にするなどすればよかったのでは
*7:確かに、「関数型言語」と言いつつ Haskell のことを言っている発表は多かった
*8:あれをあの発表の中だけで理解できた人はいないのでは
Agile Japan 2012 行ってきた
有給取って行ってきました。
午前
午前のセッションで一番心に残ったのは、「全体最適のマネジメント改革」〜変えるのは現場ではない、マネジメントである〜でした。
全体最適に関しては、ERP 大嫌い人間なので毛嫌いしてたんですが、この人の「全体最適」は ERP などが実現しようとしている「全体最適」とは考え方が違っていて、興味深かったです。
このセッションの中で、複数の作業を並行して行うよりも一個一個片付けた方が効率がいいことを示す実験をしました。
内容としては、
- 1〜20 までを順番に縦に書く作業
- a〜t までを順番に縦に書く作業
- △、○、◇ を繰り返して 20 個縦に書く作業
を、最初は「1 を書いたら次に a を横に書き、それが終わったら△を書き、それが終わったら 1 の下に 2 を・・・」というように 3 つの作業を並行してどれだけかかるかを計りました。
次に、1〜20 まで書く作業を終わらせた後に a〜t まで書く作業を終わらせ、最後に 20 個の記号を書く作業を終わらせる、というようにしてどれだけかかるかを計りました。
自分の場合、3 倍程度後者の方が早く終わりました。
で、この結果を持って「複数の作業を並行で行うよりも、一つ一つ片付けた方が全体としても早く終わる」という結論に持っていったわけですが・・・
これ、かなり恣意的な実験ですよね。
一つの作業が単純すぎるので、コンテキストスイッチの速度を計っているようなものです。
例えば「最初の作業を 5 まで進めたら次の作業を同じだけ進めて・・・」みたいにすれば、それほど大きな差にはならないはずです。
こういう実験をやるなら、コンテキストスイッチの回数を変化させて、全体の終了時間がどうなるか、というものを計ったほうがおもしろい結果になると思います。
午後
午後は
- チケット駆動開発の課題と展望
- DEEP AGILE PEOPLE〜本には書かれていない、アジャイル開発の本気の討論会〜
- 現場に続く Agile の道を語ろう〜アジャイルサムライ読書会が変えてきたこと〜
に参加しました。
一つ目のは ITS/BTS 中心の話になってしまって、あまり展望とかは見えませんでした。
そういう話になってしまうのが課題な気がしなくもないです。
二つ目のは、とりとめがなさ過ぎてなんというかまぁ、ノーコメントで。
三つ目のは段取りの悪さ以外はとても素晴らしいセッションでした。
疑問に思っていることや、問題と思っていることについて話し合えたり、ヒントをもらったりできたので、非常にためになったセッションでもあります。
立場の違う人たちの参加者の話もとても興味深く聞かせてもらえました。
懇親会
懇親会はやる夫の人とお寿司ばかり食べていた気がします。
有給使ってでも行ってよかったです。
楽しいだけではなく、非常にためになるイベントでした。