oinume journal

Scratchpad of what I learned

Go1.17beta1でGenericsに触れてみた

Go1.17beta1がダウンロードできるようになったので、Generics(Type Parameters)でStackを書いて軽く遊んだメモ。

Generics (Type Parameters)について

最新の仕様のProposalは以下を見ると良い。

実際にどんな感じのコードになるのかは、GitHubのdev.typeparams branchのコードを見ると良さそう。

Go1.17beta1のダウンロード

普段使っているgoコマンドから以下のようにGo1.17beta1をダウンロードできる。

$ go get golang.org/dl/go1.17beta1
$ go1.17beta1 download

これで ~/sdk/go1.17beta1 にインストールされる。

FYI: Goは標準の機能で複数のバージョンをインストールできる。より詳しくは以下のドキュメントを読むと良い。

Genericsのサンプルコード

Go1.17beta1に付属している以下のディレクトリに結構ある。

$ ls $(go1.17beta1 env GOROOT)/test/typeparam
absdiff.go      fact.go         issue45738.go   min.go          slices.go       tparam1.go
adder.go        graph.go        list.go         ordered.go      smallest.go     typelist.go
append.go       importtest.go   list2.go        orderedmap.go   smoketest.go    value.go
chans.go        index.go        lockable.go     pair.go         stringable.go
combine.go      interfacearg.go map.go          pragma.go       stringer.go
cons.go         issue45547.go   maps.go         sets.go         struct.go
double.go       issue45722.go   metrics.go      settable.go     sum.go

GenericsでStackを書いてみる

こんな感じになった。現時点ではType Parametersの型をexportすることができないっぽいので、stackと小文字で定義してmainパッケージに書いている。

// run -gcflags=-G=3
package main

import "fmt"

var (
    ErrEmptyStack = fmt.Errorf("stack is empty")
)

type stack[T any] struct {
    data     []T
    capacity int
}

func newStack[T any](capacity int) *stack[T] {
    if capacity <= 0 {
        panic("must be 'capacity' > 0")
    }
    data := make([]T, 0, capacity)
    return &stack[T]{data: data, capacity: capacity}
}

func (s *stack[T]) Push(v T) {
    s.data = append(s.data, v)
}

func (s *stack[T]) Pop() (T, error) {
    if s.Size() == 0 {
        var zero T
        return zero, ErrEmptyStack
    }
    v := s.data[len(s.data)-1]
    s.data = s.data[:len(s.data)-1]
    return v, nil
}

func (s *stack[T]) Peek() (T, error) {
    if s.Size() == 0 {
        var zero T
        return zero, ErrEmptyStack
    }
    return s.data[len(s.data)-1], nil
}

func (s *stack[T]) Size() int {
    return len(s.data)
}

func main() {
    s := newStack[int](10)
    s.Push(1)
    s.Push(2)
    s.Push(3)
    fmt.Printf("s = %+v\n", s)

    _, err := s.Pop()
    if err != nil {
        panic(err)
    }
    fmt.Printf("s = %+v\n", s)
}

コード書いてて少し悩んだのは、func (s *stack[T]) Pop() (T, error) のようなメソッドでerrorを返す時にT型のゼロ値をどうやって返すのか、という点だった。どうやら以下のように var zero T を定義してそれを返すのが良いらしい。return nil, ErrEmptyStackという風に書くと、Tはポインタ型ではないのでコンパイルエラーになる。

func (s *stack[T]) Pop() (T, error) {
    if s.Size() == 0 {
        var zero T
        return zero, ErrEmptyStack
    }
    v := s.data[len(s.data)-1]
    s.data = s.data[:len(s.data)-1]
    return v, nil
}

実行

main関数があれば普通にgo1.17beta1 runコマンドで実行できる。-gcflags=-G=3を付けないとgenericsのコードはコンパイルできないので注意。

$ go1.17beta1 run -gcflags=-G=3 ./typeparam.go
s = &{data:[1 2 3] capacity:10}
s = &{data:[1 2] capacity:10}

感想

Type Parametersの機能が入るリリースは2022年の2月のGo 1.18だと思うけど、現時点でもモリモリ実装されているのでとても楽しみである。

Apple Silicon向けDocker Desktop for Macでコンテナがハングしないようにする

TL;DR

  • Apple Silicon(M1) Docker Desktop for Macでgoogle/cloud-sdkのDocker Imageを使ってコンテナ内でCloud Datastore Emulatorを立ち上げると、CPUが100%で張り付いてハングするという問題があった。
  • 解決方法として、該当のDocker Imageをarm64v8アーキテクチャでビルドして、そのイメージでコンテナを立ち上げるようにしたら問題は起きなくなったので、その方法の紹介

問題の詳細

Apple Silicon版のDocker Desktop for MacはRosetta2を使いインテルアーキテクチャ(amd64 / x86_64)をエミュレーションして実行されている。エミュレーションが行われているため、ネイティブでarm64v8のコンテナを実行するよりCPUを使ってしまうという問題がある。これはKnown Issuesとして以下のように書かれている。

However, attempts to run Intel-based containers on Apple Silicon machines under emulation can crash as qemu sometimes fails to run the container. In addition, filesystem change notification APIs (inotify) do not work under qemu emulation. Even when the containers do run correctly under emulation, they will be slower and use more memory than the native equivalent.

In summary, running Intel-based containers on Arm-based machines should be regarded as “best effort” only. We recommend running arm64 containers on Apple Silicon machines whenever possible, and encouraging container authors to produce arm64, or multi-arch, versions of their containers.

自分が遭遇した現象としては、google/cloud-sdkのイメージを使ってgcloudコマンドでDatastore Emulatorを立ち上げようとすると、3回に1回ぐらいの確率でCPUが100%になり、コンテナが全く応答しなくなるという問題だった。

We recommend running arm64 containers on Apple Silicon machines whenever possible

とDocker Desktop on MacのKnown Issuesに書かれているので、これにしたがってgoogle/cloud-sdkのarm64v8のDocker Imageをビルドしてみた。

google/cloud-sdk arm64v8 Imageをビルドする

公式には提供されていないので、自分でDockerfileを作ってイメージをビルドする必要がある。 https://hub.docker.com/u/arm64v8 には様々なarm64v8のDocker Imageがあるので、debian:buster-slimをベースにして以下のようなDockerfileを作る。ちなみに今回はgoogle-cloud-sdk-datastore-emulatorしか使わないのでこれだけをインストールしているが、他のEmulatorが必要であれば公式のDockerfileを参考にインストールすれば良い。

FROM arm64v8/debian:buster-slim

ARG CLOUD_SDK_VERSION=341.0.0
ENV CLOUD_SDK_VERSION=$CLOUD_SDK_VERSION

RUN groupadd -r -g 1000 cloudsdk && \
    useradd -r -u 1000 -m -s /bin/bash -g cloudsdk cloudsdk

RUN mkdir -p /usr/share/man/man1/ && \
    apt-get update && \
    apt-get -y install \
        curl \
        gnupg \
        sudo \
        python3 \
        python3-crcmod \
        bash \
        openjdk-11-jre-headless

RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
RUN apt-get update && \
    apt-get install -y \
    google-cloud-sdk \
    google-cloud-sdk-datastore-emulator
RUN gcloud config set core/disable_usage_reporting true && \
    gcloud config set component_manager/disable_update_check true && \
    gcloud config set metrics/environment github_docker_image_emulator

そしてDocker Imageをビルドする。

$ docker build -t cloud-sdk-emulators-arm64v8:341.0.0 .

最後に

今回はGoのユニットテストからory/dockertestを使ってDatastore Emulatorを立ち上げていたのだが、コンテナを何個立ち上げても全くハングしなくなりとても安定した。今回はDatastore Emulatorだったけど、他のImageでも起こりうる問題だとは思うので、もしApple siliconのマシンでDockerを動かしていて同じような症状で困っている人は参考にしてもらえると良いと思う。

2021年の抱負

年初に新年の抱負を書こうと思っていたがずっと後回しになってしまい、1月もそろそろ終わってしまう時期になった。というわけで明けましておめでとうございます。

例によって全ては達成できないと思うけど、今年やりたいことを抱負としてまとめてみる。

習慣化

これは抱負というよりかはよりメタなものだけど、継続していきたいことは習慣化させないと続かないため、習慣化を努力しようと思っている。昨年の11月からHabitifyというiOSアプリを試していてなかなか良いなと思ったので、年初に1年単位で課金してみた。このアプリは、例えば「ジョギング」のような習慣化したいものを登録しておいて、それを通知でリマインドしたり、実際にやったことを記録して週や月単位で振り返りするようなアプリだ。自分の場合は以下のような習慣を登録している。

  • プロテインを飲む
  • 階段ジョギング
  • 筋トレ
  • iKnow(英単語学習)

このアプリ、特に何かすごい機能があるわけではないけど、リマインドを1日に2回するなど割と気が利いているのとストレスなく使えるので気に入っている。今年はこのアプリとともに習慣化に一番力を入れたいなと思っている。

ReactでSPA作れるようになる

仕事では専任のFrontend Engineerがいないため、自分もFrontのコードを触らなくてはいけないし、もともと自分ひとりでサービスを作ったりしたいので、今年はTypeScript + ReactでSPAのサービスを作れるようになりたい。

  • TypeScript
  • React
  • React Hooks
  • React Context
  • React Router
  • Jest

あたりを学べばいいのかなと漠然と思っている。余裕があったらNext.jsにもトライしてみたい。

英語

毎日iKnowで英単語をやるのと、週に1回はSkype英会話のレッスンをやるようにして、英語力を下げない努力をしようと思っている。昨年まではDMM英会話をやっていたが

  • 値段が高くなってきている
  • 英会話をやる時間を捻出するのが難しい
  • 単語を発声して覚える方が英語身についてそう(自分が発音できない単語は聞き取れないらしいので)

と思ったのでiKnowをなるべくやるようにして、週1のSkypeレッスンに切り替えた。あと、実力値を安価に測るためにTOEICを年に2回受けるつもり。

アウトプット

1ヶ月に1つはちゃんとした記事を書く。昨年に引き続き継続は力なり、ということで。

というわけで

2021年もやっていくぞい

2020年の振り返り

早いもので2020も終わってしまう。歳をとるごとに1年が過ぎていくのが早く感じる気がするけど、ここまで早く過ぎ去ってかつ内容が薄い年は初めてなのではないかと思う。ただ、多くの人にとって大変な年になったのは間違いないと思うけど、自分はソフトウェアエンジニアという職業だったおかげで、相対的に見ればCOVID-19の経済的な影響は少なかったので、ラッキーだったとは思う。あらためてテクノロジーの進化とソフトウェアエンジニアになろうと決めた2000年頃の自分に感謝したい。

1月

2019年末からインフルエンザにかかって実家に帰ったりもできず最悪のスタート。しかし、初めて家族でスキーに行ったり、その直後にGoDaysというベルリンで開催されたGoのカンファレンスに参加できたのはとてもラッキーだった。GoDaysのついでにワルシャワに1泊2日で個人旅行してきて、ポーランドという国の大変な歴史を学びつつ、社会主義から資本主義に移行している勢いも感じられたのでよかった。

振り返ると2020年は私生活ではこの1月が最高に充実していたと思う。スタートアップへの転職について考え始めたのもこの1月からだったと思う。

2月

会社の炎上プロジェクトが本格的に炎上し始めて仕事が大変になってきたのがこの時期だった。ただ、コロナも未だこの頃は猛威を奮っていなくて呑気に出社して仕事をしていた気がする。

3月〜6月

会社が原則WFHに移行し、さらに子供が保育園に行けなくなり精神的に削られる日々が続いた。子供が家にいる状態で集中して作業できる時間が確保できなくなったため、自分でコードを書くのは諦めて、担当しているmicroserviceのproductivity teamのPM的な役割に徹した。コードが書けないのは個人的にはストレスだったけど、とても強い人たちが支えてくれたので、チームとしては結構うまく立ち回れたのではないかと思っている。

また、GW前の仕事が一番炎上している時にSoftware Designの記事の締め切りがあり、生きている心地がしないぐらい忙しかった記憶がある。(みんな買ってくれ)

gihyo.jp

7月〜8月

メルペイを退職する意向を固めて、本格的に転職活動をしていた時期。コロナ渦での最終出社は寂しかったけど、あまり盛大に送り出されるのも苦手なので、あのぐらいがちょうどよかった。たまたま会社に10人ぐらい人が来ていたので久しぶりに同僚といい話ができた気がする。

8月後半にはグラファーとドクターズプライムからオファーをもらい、どちらに行くか決めきれなかったので9月と10月でそれぞれで働いてどちらかに決めることになった。悩みまくってとりあえず横浜のみなとみらいまで海を見に行ったりした。

9月

この1ヶ月はグラファーで業務委託としてお試しで働かせてもらっていた。地方自治体向けのSaaS開発の裏側と急成長しているスタートアップの裏側を何一つ隠すところなく見せてもらい、とても有意義だった。「こういうことをやって欲しい」というお題をある程度与えてもらいつつ、自分的に課題だと感じたところを見つけて直していくようなことをやった。結果としてグラファーからのオファーは断ってしまったけど、CEOの石井さんの考え方がとても好きでファンなので、副業で少しだけ手伝わせてもらっている。

10月

10月はドクターズプライムで1ヶ月働いてみた。グラファーはリモート中心で、こっちはオフィスに出社するのが中心であり、久々にオフィスメインで仕事することもあってか、人と雑談しながら仕事をするのがとても楽しかった。この頃は自分以外にまだエンジニアが誰もいなかったので、どういう風に仕事を進めればいいか悩ましいところもあったけど、他の人々が優しく接してくれたのであまり肩肘貼らずに仕事できたように思う。

11月〜12月

最終的にドクターズプライムに決めて社員として働き始めた。正直コードを書く量は前職に比べて減った気がするけど、プロダクト開発やセキュリティ強化、採用など様々なことをやらなくてはいけないカオスな状況は楽しい。メルペイで働き続けていた方がバックエンドエンジニアとしてのキャリアは積めるだろうけど、社員10名規模で技術スタックが完全一致しているスタートアップで働ける機会なんてそうそうないと思ったので転職してよかったと思う。

ライブ

今年はこれらのライブに行くはずだったけど当然全部キャンセル or リスケされた。来年こそはと思うけどワクチン次第なのでどうなることやら。

  • Knotfest
  • Green Day
  • Avril Lavigne
  • Viva La Rock

しかしストリーミングでDragon Ashのライブを3個も見れたので、これはこれでありなのでは?とも思い始めている。

最後に

何だかんだで今年は激動の1年だった。他にも大事なことがいろいろあった気がするけど思い出せないので気にしないことにします。 新型コロナに負けずに来年もよい年を迎えられると良いなと思っております。皆さんも良いお年を!!

O'Reilly Online Learningで日本語の本を読む方法

O'ReillyのOnline Learning(旧O'Reilly Safari Books Online)は月額$49でオライリーの本や動画などが見放題になるエンジニア向けのサブスクを提供している。以前は英語の本しか読めなかったが、いつからか日本語の本も読めるようになっていたのでメモ。

www.oreilly.com

Sign Inして、左のメニューのSettingsをクリックするとLanguage Preferencesがあるので、ここでJapaneseにチェックを入れて下のUpdate Preferencesをクリックして保存する。

これでHomeに行き、例えばGraphQLで検索すると検索結果の画面でBooksのタブがあるのでこれを選択する。そうするとLanguageの選択ができるので、ここでJapaneseを選ぶと日本語の本だけに絞ることができる。(手っ取り早く検索結果を表示したい時はこちら)

目的の本が見つかったらそれをPlaylistに入れるなりすればOK。なお、AndroidのO'Reillyのアプリからだと日本語の本をダウンロードして読むことができたが、iOSはエラーになって読めなかった。

2021年2月時点では合計57冊の日本語の本が読めるらしい。個人的に良さそうだなと思ったものをいくつか書いておく。

なお、裏技としてACMの会員になると年$99でO'Reilly Online Learningにアクセスできるようになるので、4冊ぐらい読めば元は取れることになる。ACMの登録方法についてはここによくまとまっているので参考になると思う。

それでは素敵なO'Reillyライフを!