退職のお知らせ

今年いっぱいで株式会社オンザロード(以下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をやめる積極的な理由はありません。 きっかけは他のメンバーの選択によるところが大きくはありますが、チームメンバーとして移籍を決めました。

次の職場についてはまた別のエントリーにする予定です。 チーム移籍活動自体の話も書く予定ではいます。

例のリストも貼っておきます。

次の職場でもがんばります!

*1:今は情熱プログラマーという微妙な名前になってしまった。この題の方がよくないですか?ちなみにサブタイトルは「オフショア時代のソフトウェア開発者サバイバルガイド」です。こっちもいい。

*2:問題があるということは改善の余地があるということですね!

コンピュテーション式の壊れた部分がなおりそう

F# Advent Calendar 2020の6日目のエントリーです。

今日は将来のF#の話です。

RFC FS-1087

RFC FS-1087は3つのFeatureをまとめたRFCとなっています。

  • Tasks
  • Resumable state machines
  • Respecting Zero methods in computation expressions

ひとつめはコンピュテーション式で Task が扱えるビルダーを追加しましょうという話です。 これは特に面白みはありません。

ふたつめはF# vNextは何が "ヤバい" のか: Monadic Programming の新時代という素晴らしいエントリーで言及されている話で、 とてもワクワクする話ですが、今回はこれの話ではありません。

みっつめの「Respecting Zero methods in computation expressions」についてです。

Respecting Zero methods in computation expressions

これは4年前に書いたComputation Expression Problemの解決です。

RFCではこうあります。

このRFC以前では、 do! expr がコンピュテーション式の最後の位置にある場合、次のように処理された。

  1. Return メソッドがある場合、 builder.Bind(expr, fun () -> builder.Return())
  2. そうでない場合、 builder.Bind(expr, fun () -> builder.Zero())

新しいルールは追加の条件を持ち、次のようになる。

  1. Return メソッドがあり、DefaultValue 属性が付いた Zero メソッドがない場合、 builder.Bind(expr, fun () -> builder.Return())
  2. そうでない場合、 builder.Bind(expr, fun () -> builder.Zero())

つまり、 DefaultValue 属性付きの Zero メソッドがある場合、今までは Return に変換されていたのが Zero に変換されるようになるわけです。

DefaultValue 属性を使わない場合は既存の挙動と同じになるというのはうまい回避方法だと思います。

これによって、より使いやすいコンピュテーション式ビルダーが提供できるようになるでしょう。 将来の楽しみが増えますね!

nameofについて

F# Advent Calendar 2020の5日目のエントリーです。

F#5.0で導入された nameof は便利なんですが、いくつか制限があります。

まず、 nameof はファーストクラス関数ではありません。 そのため、

  • let f = nameof はできない
  • x |> nameof はできない

のような制限があります。

ひとつめは出来たら面白いですが、もし実装しようとすると関数呼び出しのたびに実体が nameof かどうか見ないといけなくなりそうで、 おそらく実現されることはないでしょう。

ふたつめは例えばinline関数のみに限って緩和されるようなことはあるかもしれませんが、どうなんでしょうか。

他にも、昨日のエントリーで紹介した nameof パターンにも制限があります。

nameofnameof symbolnameof<'T> の2つの形式がありますが、 nameof パターンで使えるのは前者のみです。

// こうは書けない
let f<'a> = function
| nameof<'a> -> "yes"
| _ -> "no"

おまけ

nameof はファーストクラスの関数ではないのは上で紹介した通りですが、実はシンボルではあります。 そのため、 nameof nameof は合法で、 "nameof" が返されます。

また、演算子もシンボルなので、 nameof (+) のようにすれば "+" が返されます。 面白いですね。

F# 5.0の新機能 その2

F# Advent Calendar 2020の1日目の記事であるF# 5.0の新機能で大体書かれているのですが、2つ抜けているものがあるので紹介します。

nameofパターン

RFC FS-1085

nameof の値でパターンマッチできます。

RFCにはタグを nameof で取り出しておいて、それを元にデシリアライズする例が載っています。

// 使う型

type RecordedEvent = { EventType: string; Data: byte[] }

type MyEvent =
  | A of AData
  | B of BData
// シリアライズ用関数

let serialize (e: MyEvent) : RecordedEvent =
  match e with
  | A adata -> { EventType = nameof A; Data = JsonSerializer.Serialize<AData>(adata) }
  | B bdata -> { EventType = nameof B; Data = JsonSerializer.Serialize<BData>(bdata) }
// デシリアライズ用関数

let deserialize (e: RecordedEvent) : MyEvent =
  match e.EventType with
  | nameof A -> A (JsonSerializer.Deserialize<AData>(e.Data))
  | nameof B -> B (JsonSerializer.Deserialize<BData>(e.Data))
  | t -> failwithf "Invalid EventType: %s" t

地味に便利ですね。

string関数の実装変更

RFC FS-1089

string は今まで静的に解決される型パラメーターを取るinline関数だったのですが、F#5.0からは普通の型パラメーターを取るinline関数になります。 また、今までデッドコードだったコードを有効化し、より多くの型に対するケースを追加しています。

これは例を見てもらうとびっくりするかもしれません。

// 今まではエラーだったが、F#5.0からはエラーにならない
type Either<'L, 'R> =
  | Left of 'L
  | Right of 'R
  override this.ToString() =
    match this with
    | Left x -> string x
    | Right x -> string x

他にもフォーマットに "g" を指定したのを null に変更したりもしています。 diffを見てもらった方が分かりやすいかもしれません。

Optionalは引数に使うべきでない、という幻想について

継続渡しすると戻り値は引数になるから「Optional は戻り値にのみ使うべき」というルールは無意味だよ、という話。 あ、そういう話ね、と分かった方はこれ以上読む必要はありません。

MonoAsync + Optional + 例外という欲張りパック状態なのも問題ですが、それについてはまた今度(Mono<Optional<T>> 使わずに Mono<T> を使え、という指摘があり得る。ただ、そっちもそっちで言いたいことはある、という程度)。 今回は、 MonoAsync くらいの意図として使っています*1

まず、こんなメソッドがあったとします。

Mono<Optional<String>> f();

これ自体は戻り値に Optional を使っているだけなので、「Optional は戻り値にのみ使うべき」は守っています。

しかし、これを使う側はそうはいきません。 例えば、値が取ってこれなかった場合はログして404を返し、取れてきた場合はログして200を返すような処理を考えます。

Mono<ServerResponse> g(ServerRequest req) {
    return f().map(strOpt -> {
        if (strOpt.isEmpty()) {
            logger.info("str is not found.");
            return ServerResponse.notFound();
        }
        var str = strOpt.get();
        logger.info("str is {}.", str);
        return ServerResponse.ok();
    });
}

ここで、map に渡すラムダ式の引数は Optional<String> になります。 おおっと、引数に Optional が出てきてしまいました。

このように、Mono など「後続処理をラムダ式として受け取る」ようなスタイルの設計においては、戻り値を引数として受け取ることになります。

これを「ラムダ式の引数は例外とする」というルールを加えてしまうと、メソッド参照によるラムダ式の置き換えが出来なくなってしまいます。

Mono<ServerResponse> g(ServerRequest req) {
    return f().map(this::h);
}

ServerResponse h(Optional<String> strOpt) {
    if (strOpt.isEmpty()) {
        logger.info("str is not found.");
        return ServerResponse.notFound();
    }
    var str = strOpt.get();
    logger.info("str is {}.", str);
    return ServerResponse.ok();
}

これは先ほどのラムダ式をメソッドとして抽出しただけで、やっていることは同じです。 ラムダ式の中身が複雑になる場合は自然にこういう書き換えはやりたくなるとおもいますが、ラムダ式を例外とするだけでは足りないことが分かります。 まぁ、 public 以外にはルールは適用しない、みたいな例外を追加するようなことは考えられますが、いっそあきらめて Optional を引数に使わない、なんてルールやめてしまった方がいいと思います。

いやいや、複雑になったとしても取得処理を呼び出し側でやって、メソッドの引数には Optional を許さない!という態度もなくはないです。

Mono<ServerResponse> g(ServerRequest req) {
    return f().map(strOpt -> {
        if (strOpt.isEmpty()) {
            logger.info("str is not found.");
            return ServerResponse.notFound();
        }
        var str = strOpt.get();
        logger.info("str is {}.", str);
        return h(str);
    });
}

ServerResponse h(String str) {
    // strを使った複雑な処理
    return ServerResponse.ok();
}

こんな感じですね。 値の有り無しの振り分けをラムダ式側、処理自体をメソッド側と役割を分けられるため、なくはないです。 ただ、今回の例は Optional が1つでしたが、これが2つ、3つと増えてくると、ラムダ式内が複雑になっていくため、そこにテストを書くためにメソッド化したくなります。 その際は、やはり Optional を引数に持つメソッドが欲しくなります。

このように、 「Optional を引数に使わない」というルールは実践的には無意味です。捨てましょう。

「フィールドに Optional を使わない」というルールの方はもうちょっとだけ分からなくもない理由付けがあります*2が、 こちらもすべてのクラスがシリアル化対象なわけでもないので、個人的には無意味だと思っています。

*1:CompletableFutureでもよかったけど長いしCFの語彙が気に入らないので・・・

*2:Optionalがシリアル化可能ではない

Terraformの文法について?

最近Terraformを勉強してたんですが、日本語でTerraformの文法について説明しているものが見当たらなかったのではまった(?)ところまで書いてみました。

Terraformの文法?

正確にはHCL(HashiCorp Configuration Language)の文法です。 TerraformというツールにHCLという文法に従って書かれたファイルを食わせると、 その内容(意味)をツールが読み取って実行します。

設定ファイル

設定ファイルのトップレベルに書けるのは、コメントを除くと次の3つの要素です。

  • 属性
  • ブロック
  • 1行ブロック

設定ファイルにはこれらを複数(0個でもいい)並べられます。 例えば、ブロック、属性、属性、ブロック、1行ブロック・・・のような書き方が許されているということです。

これを公式ドキュメントでは

ConfigFile = Body;
Body       = (Attribute | Block | OneLineBlock)*;

と表しています。 正規表現とかBNFっぽいですね*1

ブロック

ブロックの定義はこのようになっています。

Block = Identifier (StringLit|Identifier)* "{" Newline Body "}" Newline;

さっきより複雑ですが、要は

terraform {
  required_version = "0.12.26"
}

とか

provider "aws" {
  region = "ap-northeast-1"
}

とか

resource "aws_instance" "hoge" {
  ...
}

とかしているのはすべてこの「ブロック」です。 terraform 構文や provider 構文や resource 構文などと個別に定義されてはいませんでした。 ということで、HCLは大体がブロックでできています。 ブロックの中身に Body が出てきますが、 Body は属性かブロックか1行ブロックのいずれかの複数の並びでした。 つまり、ブロックはネスト出来ます。

# 外側のブロック
resource "null_resource" "hoge" {
  # 内側のブロック
  provisioner "local-exec" {
    # これは属性(後述)
    command = "..."
  }
}

そして個人的に衝撃だったのは、例えば resource ブロック、こうも書けます。

resource aws_instance hoge {
  ...
}

今まで打ってきたダブルクォートを生まれる前に消し去りたい。

もう一度ブロックの定義を見てみましょう。

Block = Identifier (StringLit|Identifier)* "{" Newline Body "}" Newline;

(StringLit|Identifier)* となっている部分に注目してほしいのですが、 これは「(文字列リテラルか識別子)が0個以上並ぶ」ことを意味しています。

provider "aws" {
  region = "ap-northeast-1"
}

この "aws" が文字列リテラルで、

provider aws {
  region = "ap-northeast-1"
}

こっちの aws が識別子です(provider も識別子ですね)。 そしてこの部分を文字列リテラルで書いても識別子で書いても同じ意味です。 もっと早く公式の仕様を確認すべきでした。

他に面白い構文も特にないしやる気も尽きたので、ここまでにしておきます。 Terraform、ダブルクォートがだるいと思って避けている人もいるんじゃないかなと思うんですが、 別に書かなくていい場所になぜか書いている人が多いだけだよ、ということは広まってほしいですね。 公式のExample Usageがダブルクォート付きで書いているので、 お作法としては書くべきなのかもしれないですが面倒だし芋っぽいので自分は書かないことにします。

余談

Terraformは独自言語にもかかわらず、構文自体の説明は驚くほど少なくてびっくりします。 構文がシンプルなので説明不要でなんとなく使えるというのもあるんでしょうね。

CloudFormationはその点、文法はYAMLです、で説明がほぼ終わるのは利点ですよね。 関数とか独自機能もありますけど、YAML的にはvalidを保ったままの拡張なので、YAMLさえ知っていれば構文について覚えることは少なくて済みます*2

*1:ちなみにSyntax Notationを見ると + は定義されていないんですが、普通に使ってます。「へぇ、硬派(?)だ」とか思ったんですが・・・

*2:YAML自体の複雑さは見ないものとする

WSLへのOchaCamlのインストール方法

基本的にはOchaCamlの公式ページに従えばいいんですが、環境をWSL(Ubuntu)に固定し、日本語でまとめておきます。

準備

makeとgccが入っていない場合はインストールしておきます。

sudo apt install make gcc

インストール方法

ホームディレクトリで作業します。

cd
wget http://caml.inria.fr/pub/distrib/caml-light-0.75/cl75unix.tar.gz
tar xpfvz cl75unix.tar.gz
wget http://pllab.is.ocha.ac.jp/~asai/OchaCaml/download/OchaCaml.tar.gz
tar xpfvz OchaCaml.tar.gz
patch -p0 < OchaCaml/OchaCaml.diff
cd cl75/src
sed -i -e 's/ -no-cpp-precomp$//' Makefile
make configure
make world

起動方法

cd
./OchaCaml/ochacaml

これでREPLが立ち上がります。