oinume journal

Scratchpad of what I learned

TypeScriptのDestructuring assignment

最近仕事でfrontendの開発を少しずつやるようになったのだけど、TypeScriptはGoに比べて演算子や記号を使う記法が多くて読むのに一苦労する。なので「これなんだろう?」と思ったやつをメモしておく。

TypeScriptやっていて一番最初に「ん?」と思ったのは、以下のような構文だった。

const obj = { title: 'hello' }
const  { title: myTitle } = obj // ★
console.log(myTitle) // "hello"

★のコードでは

  1. obj というオブジェクトから title という要素を取り出して
  2. myTitleという定数を宣言
  3. myTitleに 1. で取り出した要素を代入する

ということをやっている。myTitleが定数宣言であるのに対して、キーのtitleは単純にオブジェクトのキーなので特に何かが宣言されているわけでない、ということが初めはわかっていなかったのでこの構文を見るたびに頭が混乱していた。

調べてみると、これはJS由来の構文でDestructuring assignmentと呼ぶらしい。

上の例ではオブジェクトから要素を取り出す例だったが、当然配列から特定の要素を取り出すこともできる。

const array = [1, 2]
const [ first, second ] = array
console.log(first) // 1

さらにスプレッド構文 ... を使って以下のような代入も可能。

[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest); [30, 40, 50] 

Goと違って多値が返せないからこういう構文が必要になるのかなと思ったのだけど、どうなんだろう?

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年だった。他にも大事なことがいろいろあった気がするけど思い出せないので気にしないことにします。 新型コロナに負けずに来年もよい年を迎えられると良いなと思っております。皆さんも良いお年を!!