生涯未熟

生涯未熟

プログラミングをちょこちょこと。

関数の自己参照とオプションデザイン

この記事はRob Pike氏の

commandcenter.blogspot.jp

を翻訳したものになります。
※Rob Pike氏より翻訳の許可は頂いております。氏に多大な感謝を。


私は自分が書いているGoのパッケージのオプション設定に対して良い方法を度々試してきました。 オプションを型として扱う、などがそうです。 パッケージは複雑で、様々なオプションがあることでしょう。

オプションに対してのアプローチは数多くありますが、使い勝手が良いものや、あまりAPIを必要としないことや(少なくともユーザーが苦にならない程度)、肥大化することなく必要に応じてスケールするようにしたいと考えていました。
しかし、オプション構造体・大量のメソッド・バリアントコンストラクタ等、様々な既存の方法を試しましたが、満足のいく結果を得られませんでした。

1年ほどの多々の試行とGopher達との議論を経て、ついに満足のいく手法を発見しました。
あなたにとってその手法が好まれるかもしれないし、そうでもないかもしれませんが、関数の自己参照の興味深い使い方の一つとして示しています。
読んで頂けると幸いです。

それでは、まずは簡単なものから始めていきましょう。
完成形に向かって徐々に修正していく形で進めていきます。

最初にOption型を定義します。
1つの引数、つまりFooが動作する関数です。

オプションがそのオプション自身の状態を設定するために呼び出す関数として実装されているというアイデアです。 奇妙に見えるかもしれませんが、狂気の中にこそ方法があります。

次は、Option型の関数呼び出し時に渡すオプションを設定する*FooのOptionメソッドを定義します。
このメソッドは、Fooが定義されているpkgパッケージと同じパッケージに定義されています。

Goを使っているのでメソッドを可変長引数にして、多くのオプションを設定することができます。

オプションを提供するために、pkgパッケージに適切な名前とシグネチャを持つ関数を定義します。

冗長性を制御するためにFooの冗長性を表すフィールドに整数値を設定します。
わかりやすい名前の関数を作り、冗長性を設定したオプションを返すようにします。
これはクロージャーを意味し、そのクロージャー内でフィールドを設定します。

何故、単純に設定する代わりにクロージャーを返すのでしょうか?
なぜなら、ユーザーはクロージャーをわざわざ書く必要がなく、Optionメソッドも使いやすくなるからです。
(ここからどんどん良くなっていきますよ)

パッケージのクライアントでは、次のように書いてFooオブジェクトの冗長性オプションを設定できます。

これは簡単なもので目的はほとんど満たしていますが、Optionのメカニズムを使用して一時的な値を設定したい。
つまり、Optionメソッドが前の状態に戻るようにしたいのです。

これを実現するのは簡単で、Optionメソッドの関数型の基礎型から返される空のinterfaceとして保存するだけです。
interfaceの値はコード内を流れていきます。

クライアントは前のコードと同じように使用することが出来ますが、以前の状態を復元したい場合は、
最初の呼び出しの戻り値を保存し、それを引数として実行するだけです。

オプションの復元を実行する際の型アサーションは不器用な作りになっています。
この辺りの設計をもう少し良くすることで、更に良いコードになるでしょう。

まず、オプションのフィールドを設定する関数を再定義し、前の状態を復元するための別のオプションを返すようにします。

この関数の自己参照の定義は、ステートマシンを想起させます。
ここでは少し違った使い方をしており、関数が逆に関数を返す形になります。
(補足:リンク先の映像で使われているスライドのここの辺りを読めば理解が早いと思います)

次に、*FooのOptionメソッドの戻り値の型(と意味)をinterface{]からoptionに変更します。

最後の実装は、実際のオプション機能の実装です。

クロージャーの内部は、interfaceの値ではなくoptionを返されなければいけません。
つまり、状態を復元するにはクロージャーを返却する必要があります。

しかし、これは簡単なことです。
状態を復元するのにクロージャーを準備し、それを繰り返すことで可能になるということです。

これは以下のようになります。

クロージャーの内部の最後の行が return previous から return Verbosity(previous) に変更されていることに注目してください。
単に古い値を返すのではなく、Verbosity関数を呼び出して元に戻すクロージャーを作成し、これを返します。

今のクライアントを見れば、これは凄く良いということがわかりますね。

最後に、Goの遅延メカニズムを利用してクライアントですべてを整理します。

返される verbosity は冗長性の値ではなく、クロージャーになっているので冗長性の値自体は隠蔽されていることは注目に値します。
もし、隠蔽された冗長性の値が必要な場合は、もう少し"マジック"が必要になりますが、もう既に充分なほど"マジック"があります。

これらの実装が過度なものに見えるかもしれませんが、実際には各々の実装は数行でありながら、高い普遍性を持っています。
最も重要なのは、パッケージのクライアントの観点から見ると非常に使いやすいということです。
ついに満足できるデザインが出来上がりました。
また、私はGoのクロージャーを使って目標を達成出来たことに満足しています。

Golang Weekly #171

A Million WebSockets and Go – freeCodeCamp

ロシアの電子メールサービスを扱っている巨大企業、Mail.RuでのWebSockets ✕ Golangの話。

約300万のオンライン接続があるようなサービスで、メールのポーリングには1秒間に約50,000件のHTTPリクエストが含まれるような巨大システムに対して、 WebSocketによるアプローチをGolangで実装し、その時の学びがつらつらと書かれています。

netpollの独自実装・リソース制御・goroutineのプーリングなどなどがコードと一緒に説明されていて分かりやすいです。

ただ、ちょっと英語力の無さから細かいニュアンスが分からなかったので、今POSTDに翻訳リクエストを出しています!

github.com

Building a Worker Pool in Golang – uniplacesgeeks

UniplacesというサービスのAPISalesforceAPIを繋ぐ処理を実装した際に、キューのポーリング処理などの高速化におけるワーカープールの説明記事。

ワーカープールで実現した機能は以下の2つで、

  • 動的スケーラビリティ
  • グレースフルシャットダウン

それぞれの実装方法が事細かに書いてあります。

Using functional options instead of method chaining in Go

オブジェクトの構築にメソッドチェーン(またはBuilderパターン)を使う代わりに、functional optionを使おうという内容。

メソッドチェーンの例外周りについてはGORMを参考にしたコード例を紹介しているが、以下のような問題があるということを挙げている。

  • WhereFirst でエラーが呼ばれることがない
  • エラー発生が多くなり、適切にハンドリングを行わなくてはならない

それらの問題に対して、functional optionを使うと良いよという流れで、GORMに対してはfunctional optionを使ったラッパーを使用することで、更に使い勝手が良くなるのでは?ということが書かれている。

Go1.9に追加されるtype aliasを使って遊んでみる

Go1.9で追加されるtype aliasに無限の可能性を感じたので、ちょっと遊んでみます。

type aliasとは?

types aliasは型のエイリアスです。
ちょっとそれだけでは雑な説明なので、コードを見てみましょう。

基本的なシンタックスはこんな感じです。

named type(名前付き型)に似てますね。
で、type aliasの特性として 同じ型の名前付き型に代入可能 というものがあります。

例えば以下のような感じのことが実現できます。

元の型が同じならどんな名前付き型にも代入可能ということが分かりますね。

これを見た時に「package毎に分かれた構造体を上手いこと扱えるんじゃないか」と思いました。

遊んでみる

どういう事かというと、まず以下のようなストラクチャを考えてみます。

f:id:syossan:20170730032126p:plain

ここでの main.go の役割は、

  • config.goから設定を取得する
  • action.goへ設定を渡す

といったものとします。
config.go は言わずもがな、設定ファイルの config.toml から設定の取得。
action.go は設定を受け取ってほげほげする、という感じですね。

では、まずはtype aliasを使わない形でザクザクっと組んでいきます。

実行すると sample と表示されます。
type aliasを使わないパターンだと、一度構造体をjson形式にして各パッケージでやり取りするってのが一番良いのかな〜?と今のところ思っとります。(何か他に妙案がある方は教えてください)

これをtype alias使って書き直してみましょう。

こんな感じですかね。
type aliasであれば、あるパッケージから取得した構造体をそのまま別のパッケージの構造体に引き渡せれるので、結構便利ですね。

ただし、 config.go の構造体を以下のようにした時はエラーになりました。

f:id:syossan:20170730044808p:plain

どうも、type aliasのネストされた構造体を使っても上手く変換してくれないのか、色々試してもエラーが解消されずでした🤔

まとめ

こんな感じで、type aliasを見た時に閃いた使い方がこれでした。
ただ、Goの開発陣として想定している使い方とはかなり違うと思うのですが、他にも色々と活用できそうな感じがするので早くGo1.9来てくれ〜

Golang Weekly #170

Go 1.9 Release Candidate 1 is released

Go1.9のrelease candidateがリリースされました!

インストール方法はこちらから

godoc.org

Go1.9には大きな言語仕様の変更として、型エイリアスFMA(融合積和演算)が実装されます。

エイリアス

エイリアスについては例を見た方がパッと判りやすいです。
例えば、

のコードの場合、定義したそもそもの型が違うためエラーとなります。
しかし、型エイリアスを使うことで以下のようなコードが可能になります。

先ほどと違いエラーは出ません。
この型エイリアスによって、パッケージ間の構造体のやり取りが柔和になるので凄く便利に使えそう・・・👌
実装された理由としては「大規模リファクタリングの際に段階的移行を行なうため、リファクタリング前・リファクタリング後のコードの相互運用をしたいから」という感じみたいです(あまり想像がつかない🤔

FMA(融合積和演算)

ザックリ説明をすると r = x * y + z といった計算の際に、 x * y の演算時点で浮動小数点数の丸め処理を行わずに和算を行なう演算方式。
今回の実装では上記のFMAが実装され、逆に丸め処理を行いたい場合は r = float64(x * y) + z のように明示的にfloat型でキャストさせます。

多分この実装のProposalとしてはここかな?🤔

github.com

本当にGo1.9のリリースが楽しみですね!!

Should Go 2.0 support generics? | Dave Cheney

Go2.0にジェネリクスのサポートは必要?という記事。

  • 最近の主流言語ではサポートされているため、多言語を扱うプログラマーにとってのコンテクストスイッチに関するコストを少なくするためにも入れるべき
  • しかし、Goの簡潔性やその他諸々の独自性を保ち、この「ジェネリクスは必要か否か?」の議論を無くすためにも、「ジェネリクスはGoで実装されるべきものではない」と声明するべき

というのが記事内での主張。(訳間違ってたらすみません🙇)
確かに自分自身はジェネリクス無くてもいいんじゃないか?と思っていたりするので(少なくともGoには)、開発陣による何らかの声明が欲しいところ。

GitHub - gophercon/2017-talks: Slides and links for 2017 talks

今年開催されたGoperConの資料まとめ。ありがたし🙏

ほぼ全てが映像による資料なので、英語の達者な方でないとちとキツい・・・

Golang Weekly #169

Toward Go 2 - The Go Blog

来ましたGo2!

1年後にリリース予定のGo 1.12から徐々に後方互換性のある機能をリリースしていき、1.20から後方互換性のない機能をGo2.0に入れ、1.20から2.0に移行しやすくしたいという流れになるみたいです。
他にもGoの開発プロセスについての解説もあるので、一読の価値ありです。

この記事は和訳されたものもありますので、英語が苦手な方はこちらを読んでみてください。

www.ymotongpoo.com

Strace in 60 lines of Go – Hacker Noon

LinuxのstraceコマンドをGoで実装してみた、という記事。

60行で実装できるものなのかと驚き。syscall packageはなかなか使うことがないので、使用例としてとても参考になる。

GitHub - 360EntSecGroup-Skylar/goreporter: A Golang tool that does static analysis, unit testing, code review and generate code quality report.

Goの静的解析・ユニットテスト・コードレビュー・コード品質チェックを一括で行い、結果をHTML形式で出力してくれるツール。
コードグラフなども出してくれて、なかなかに便利。

こちらに出力例があるので気になる方はチェック!

http://fiisio.me/pages/goreporter-report.html

Docker => Moby: Go Dependencies

DockerからMobyに名前が変更された結果、開発環境が動かなくなったよ事案。

元々は go get のみを使って開発をしていたようなのだが、

  • gvt を使う
  • GOPATHを各プロジェクト毎に設定する
  • /vendor を使わずに、 /src でベンダリングするようにMakefileを書く

で対処したという内容。

しかし、ここまで複雑なことをする必要が果たしてあるのだろうか・・・?🤔

dotGo

フランスで11月にGoConferenceが開催される模様。
Go関連でよく見る人達が登壇してますね!行きたい!

Go Reliability and Durability at Dropbox

DropboxでのGoの使用例、新しくジョインしたエンジニアへのアプローチなどなどが書いてあります。
最初は、Rate LimitingとThrottlingのツールをGoで作り、そこから社内にGoが広まっていったという。