2022年振り返り

2022年も終わりなので雑に振り返ります。

去年の振り返り

taxio.hatenablog.com

転職→転職→フリーランス

仕事関係で環境が大きく動いた1年でした。

4月末で新卒で入った会社を退職し、スタートアップに転職しました。 退職前にパフォーマンス改善周りで大きな仕事を達成できて良かったです。 この前話しを聞いたら僕の置き土産がまだ活躍しているそうでした。

これで勢いづいたのか ISUCON12 で本選に出場することができました。 本選ではボロボロでしたが...。 来年は優勝するぞ。

転職先ではバックエンドと意思決定のためのデータ分析をやっていました。 前職とやっていることはそこまで変わらないのですが、何も整っていない状況で己の力で環境を整えていくことができたので自力の高まりを感じました。

前職で得た経験値がちゃんとモノになったという感じでしょうか。

会社の状況的にワケあって(喧嘩別れではないです)半年で退職してしまいましたが、シード期のスタートアップならではのヒリヒリと何でもやる感じを経験できて非常に強くなれました。

現在はフリーランスとしていくつかの会社をお手伝いしています。 時間に余裕ができたのでぜひ飲みに行きましょう。

ブロックチェーン界隈に足をつっこむ

以前から興味があったブロックチェーンを本格的に勉強し始め、縁あっていくつかのお仕事を手伝わせてもらっています。 人は何に価値を感じるのか、新しい技術基盤でどんな面白いことができるのか。界隈自体が色々なものを考え発明している時期で勢いがあります。

また、ETH India というインドで開催された Ethereum のハッカソンに参加してきました。 Ethereum Foundation からトッププライズをいただけたので、「この界隈でポジション取れるんじゃね?」と勘違いしているところです。

結婚

しました。

来年は

最初はフリーランスを続けるつもりは無く、自分の尊敬できるエンジニア組織ができている会社を見つけたらパッと転職しようと考えていました。 しかし、前述したとおりブロックチェーン界隈で思ったよりポジションが取れそうなのでしばらくはこのスタイルで活動するつもりです。 界隈が界隈なので3ヶ月後には違うことを言ってるかもしれません。

2021年振り返り

2021年も終わりなので雑に振り返ろうと思います。

去年の振り返りはこちら

taxio.hatenablog.com

仕事

去年から引き続き Wantedly で主に People/Profile に関する開発をしており、大きく以下のプロジェクトをやっていました。

  • グロース施策
  • Wantedly People のタイムラインリニューアル
  • 技術的な基盤改善

これは9月頃までの話で、10月からは社内留学制度を使ってインフラチームでまた別の基盤改善プロジェクトを進めています。 自由にチームを異動できるのは有り難いですね。

また、今年は副業で他の会社の開発をお手伝いすることがありました。 完全リモートによる土日の副業なので、コミュニケーションコストや時間の制約がかなり重くのしかかるなと思いました。

うまく副業をこなしている人はどういう運用をしてるんでしょうか?気になる。

モチベの消失

後ろ向きな話になってしまうのですが、今年に入ってからどんどん仕事のモチベが消えていきました。

これはまずいと思い社内留学制度を使ってチームを異動してみたのですが、結果は変わらずどん底までモチベが下がってしまいました。

具体的には仕事になると集中モードに全く入れず、あらゆるタスクが億劫になるといった形です。

これが Burn out によるものなのか、現職に興味が無くなったことによるものなのかは分かりませんが、とりあえず年末年始と有給を使って2週間ほど休んでいるところです。

Burn out だった場合、本当は1,2ヶ月ほど休むものらしいんですが、そんなに有給も無いので仕方ないですね。

プライベート

引っ越し

なんか色々あって8月に引っ越ししました。 ここはめちゃくちゃ話のタネだらけなので、飲み会で会ったら話しましょう。

資産運用

ちょいちょい手は出してたんですが、今年からガバっと株の運用を始めました。 今 Money Forward を見てみたら資産の8割ぐらいが株式でした。

インデックス投資でも良かったんですが、ちょっと勉強してみたいなと思い、自分で色々調べて個別に株を買う運用にしました。 最初はビギナーズラックだったのか大きく跳ねて、無事 MacBook Pro M1Max が買えました。

ここらへんの知見は溜まってきたらどこかで話すかブログにしたいですね。

外部発信

Blog

3本でした。去年より減ってるやないかい。

Podcast

大学時代の友人たちと3人で「Terendipity」という Podcast を始めました。

友人の1人が法律の勉強をしているので、法律と技術というトピックであれこれ話しています。

Terendipity • A podcast on Anchor

元々 Clubhouse で話してたんですが、定期的にやってもいいかもねということで作ってみました。 ゆるくやっていこうと思います。

おわりに

今年はモチベの低下に引っ張られてあらゆる行動から積極性が消えたので、来年はまずこれの解決からしていきたいです。

休んでダメなら Let's 新天地って感じなんですが、スカウトのメッセージを返すのにもまたやる気を出す必要がある...。 (全部読んではいます。返せなくて申し訳ないです。)

Bitcoin の Block Header を理解してマイニング結果を検証する

こんにちは、taxio です。

暗号通貨が世間を賑わせるようになってから数年が経ちました。 色々怪しかったり怪しくなかったりする話をたくさん聞きますが、そもそもシステムとして動いているという事実は確かです。 ということで、最近はその技術的バックグラウンドを知るべく、Satoshi Nakamoto の論文 や解説ブログ、BitcoinWiki を読んで、実際に自分で実装してみながら勉強しています。

今回はその中でもとっつきやすい Block Header に注目し、実際に自分で検証のコード (Go) を書きながら理解を深めてみます。

この記事では暗号通貨の用語が出てきますが、全て Bitcoin を前提にしています1

Transaction と Block

ブロックチェーン上での資産の取引は Transaction というデータで表現されます。 Block は複数の Transaction を持ち、平均して10分に1個が承認を経てブロックチェーンに追加されます。

Transaction が生まれたらすぐにブロックチェーンに追加される訳じゃないんですね。

Block に紐づく Transaction たちは Merkle Tree というハッシュ木2で情報が集約され、最終的にその Root Hash が Block に直接保存されます。

ざっくりとしたデータ構造を知りたい場合は、BigQuery にあるデータセット実際の Block の情報を直接見ると雰囲気が掴めると思います。

Block Header

Block は色々な情報を持っているのですが、特に以下の情報が 80 Byte にシリアライズされその Block のハッシュ化に使用されます。 この 80 Byte のことを Block Header と言います。

  • version (4 byte)
    • Block のバージョン
  • previous block header hash (32 byte)
  • merkle root hash (32 byte)
    • Block が持つ全 Transaction から Merkle Tree を作成した時の Root Hash
  • time (4 byte)
    • マイニングが始まった時間3
  • nBits (4 byte)
    • Target を算出するための情報
    • Block のハッシュ値は Target 以下になる必要がある
  • nonce (4 byte)
    • Block のハッシュ値を Target 以下にするために付ける任意の数字
    • マイニングのほとんどの時間はこの数字を求めるのに費やす

構造体にしてみるとこんな感じです。

type BlockHeader struct {
    Version        int32
    PrevHash       [32]byte
    MerkleRootHash [32]byte
    Time           uint32
    Bits           uint32
    Nonce          uint32
}

ハッシュ値を求める

さて、実際に Block Header からハッシュ値を計算してみましょう。 ハッシュ関数に渡す情報の作成は単純で、先程定義した構造体を上からバイト列にして繋げていくだけです。

func ReverseHashBytes(hash [32]byte) [32]byte {
    for i := 0; i < len(hash)/2; i++ {
        hash[i], hash[len(hash)-i-1] = hash[len(hash)-i-1], hash[i]
    }
    return hash
}

func (b *BlockHeader) Serialize() [80]byte {
    serialized := [80]byte{}

    bVersion := make([]byte, 4)
    binary.LittleEndian.PutUint32(bVersion, uint32(b.Version))
    copy(serialized[0:4], bVersion[:])

    bigEndianPrevHash := ReverseHashBytes(b.PrevHash)
    copy(serialized[4:36], bigEndianPrevHash[:])
    bigEndianMerkleRootHash := ReverseHashBytes(b.MerkleRootHash)
    copy(serialized[36:68], bigEndianMerkleRootHash[:])

    bTime := make([]byte, 4)
    binary.LittleEndian.PutUint32(bTime, b.Time)
    copy(serialized[68:72], bTime[:])

    bBits := make([]byte, 4)
    binary.LittleEndian.PutUint32(bBits, b.Bits)
    copy(serialized[72:76], bBits[:])

    bNonce := make([]byte, 4)
    binary.LittleEndian.PutUint32(bNonce, b.Nonce)
    copy(serialized[76:80], bNonce[:])

    return serialized
}

Block 内部では Byte 列は little-endian で扱われることに注意してください。

例として、以下のような Block Header を対象とします。

{
  "version": 1,
  "prev_hash": "00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81",
  "merkle_root_hash": "2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3",
  "time": 1305998791,
  "bits": 440711666,
  "nonce": 0,
}

構造体に当てはめて、実際にシリアライズしてみると以下のようになります。

header := BlockHeader{
  Version: 1,
  PrevHash: [32]byte{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xa3,
    0xa4, 0x1b, 0x85, 0xb8, 0xb2, 0x9a, 0xd4, 0x44,
    0xde, 0xf2, 0x99, 0xfe, 0xe2, 0x17, 0x93, 0xcd,
    0x8b, 0x9e, 0x56, 0x7e, 0xab, 0x02, 0xcd, 0x81,
  },
  MerkleRootHash: [32]byte{
    0x2b, 0x12, 0xfc, 0xf1, 0xb0, 0x92, 0x88, 0xfc,
    0xaf, 0xf7, 0x97, 0xd7, 0x1e, 0x95, 0x0e, 0x71,
    0xae, 0x42, 0xb9, 0x1e, 0x8b, 0xdb, 0x23, 0x04,
    0x75, 0x8d, 0xfc, 0xff, 0xc2, 0xb6, 0x20, 0xe3,
  },
  Time:  1305998791,
  Bits:  440711666,
  Nonce: 0,
}
fmt.Printf("Serialized:\n%x\n", header.Serialize())

// Serialized:
// 0100000081cd02ab7e569e8bcd9317e2fe99f2de44d49ab2b8851ba4a308000000000000e320b6c2fffc8d750423db8b1eb942ae710e951ed797f7affc8892b0f1fc122bc7f5d74df2b9441a00000000

これを SHA-256 で2回ハッシュ化します。 2回ハッシュ化するのは Length Extension Attack4 というのを避けるためだそうです。

bitcoin.stackexchange.com

本来はこれで終了なのですが、後続の説明の分かりやすさのため、このハッシュ値を big-endian に直しておきます。

func (b *BlockHeader) Hash() [32]byte {
    serialized := b.Serialize()
    h1 := sha256.Sum256(serialized[:])
    h2 := sha256.Sum256(h1[:])
    return ReverseHashBytes(h2)
}

先程のデータを入れると以下のような情報が出てきます。

fmt.Printf("Hash:\n%x\n", header.Hash())

// Hash:
// da830f8941824ae65eb8cdacf37df88057c533f7e2168aceeae314b8a3d783d8

さて、これで Block のハッシュ値を求めることができましたが、実はこれで終わりではありません。

Target と Nonce

先程の例では da830f8941824ae65eb8cdacf37df88057c533f7e2168aceeae314b8a3d783d8 というハッシュ値を算出しました。 しかし先述したとおり、このハッシュ値は Target 以下にする必要があります。

そのために Nonce に色々な値を入れていくのですが、まずは目指すべき Target を Bits から算出しましょう。

Bits は先頭2byteが指数部で、残りの3byteが仮数部となっています。

例えば Bits が 0x1A44B9F2 だった場合、以下のようにして Target を算出します。

Target = 0x44B9F2 * 2 ^ (8 * (0x1A - 3))
       = 0x44b9f20000000000000000000000000000000000000000000000

これを実際にコードに落とし込んでいきましょう。 (もっとうまいこと Byte を使う方法はあると思いますが、適当に実装してしまっています...。)

func (b *BlockHeader) Target() [32]byte {
    exponent := b.Bits >> 24
    mantissa := b.Bits & 0x00FFFFFF

    targetStr := fmt.Sprintf("%x", mantissa) + strings.Repeat("0", (int(exponent)-3)*2)
    targetStr = fmt.Sprintf("%064s", targetStr)
    target, _ := hex.DecodeString(targetStr)

    ret := [32]byte{}
    copy(ret[:], target[:])

    return ret
}

しっかりと目的の Target が算出できていることがわかります。

fmt.Printf("Target:\n%x\n", header.Target())

// Target:
// 00000000000044b9f20000000000000000000000000000000000000000000000

さて、Target が求まったので、ハッシュ値と Target を比較するメソッドを定義して Nonce を探していきましょう。 Go には 256bit の unsigned integer は無いので、先頭から byte を比較していきます。

func (b *BlockHeader) IsValidHash() bool {
    hash := b.Hash()
    target := b.Target()
    for i := 0; i < len(hash); i++ {
        h := hash[i]
        t := target[i]
        if h != t {
            return h < t
        }
    }
    return true
}

ここは気合なのですが、なんやかんやで Nonce: 2504433986 を発見したとします。

header.Nonce = 2504433986
fmt.Printf("Hash:\n%x\n", header.Hash())
fmt.Printf("Target:\n%x\n", header.Target())
fmt.Printf("Valid: %v\n", header.IsValidHash())

// Hash:
// 00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d
// Target:
// 00000000000044b9f20000000000000000000000000000000000000000000000
// Valid: true

ちゃんと Target 以下のハッシュ値が算出されましたね。

実際の Block のデータを用いて検証してみる

ここまでで Block Header の情報を用いて、

ができるようになったので、実際に Bitcoin の Block を持ってきて検証してみましょう。

bitcoin explore」とかggって出てくる適当なサイトから Block 情報を引っ張ってきます。 今回は715405番目のハッシュ値 0000000000000000000b511bf20ecb6d0ea049862fa2741ed8098e508d98aa3c という Block を対象にします。

www.blockchain.com

計算するならこっちの方が扱いやすいかも。

https://blockchain.info/rawblock/0000000000000000000b511bf20ecb6d0ea049862fa2741ed8098e508d98aa3c

それぞれの情報を構造体に当てはめると以下のようになります。

header = BlockHeader{
  Version:        539656192,
  PrevHash:       [32]byte{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x04, 0x0b, 0xd2, 0xd1, 0x6b, 0xba, 0x61,
    0x3c, 0xbc, 0xb0, 0x88, 0x40, 0x57, 0x06, 0x4c,
    0xc4, 0x48, 0x5f, 0x6d, 0x5c, 0x81, 0xab, 0xc0,
  },
  MerkleRootHash: [32]byte{
    0xf3, 0x5c, 0x86, 0x7f, 0x07, 0x03, 0xef, 0x74,
    0x1c, 0x99, 0x02, 0xdb, 0x60, 0x08, 0xd4, 0x17,
    0x93, 0x48, 0x0c, 0x7e, 0x8e, 0xa2, 0xf3, 0xa5,
    0x8e, 0x52, 0x12, 0x8c, 0xe1, 0xac, 0x88, 0xda,
  },
  Time:           1640265851,
  Bits:           386638367,
  Nonce:          933616665,
}

これを実際に計算してみると

fmt.Printf("Hash:\n%x\n", header.Hash())
fmt.Printf("Target:\n%x\n", header.Target())
fmt.Printf("Valid: %v\n", header.IsValidHash())

// Hash:
// 0000000000000000000b511bf20ecb6d0ea049862fa2741ed8098e508d98aa3c
// Target:
// 0000000000000000000ba21f0000000000000000000000000000000000000000
// Valid: true

しっかり 0000000000000000000b511bf20ecb6d0ea049862fa2741ed8098e508d98aa3c という元のハッシュ値が算出され、Target 以下になっていることも確認できました 🎉

余談ですが、Version が 539656192 というかなり凄い数字になっていることにお気づきでしょうか? 実は Version の 13-28 bit 目は任意の数値を入れることができるようになっており、これもマイニング時の探索パラメータとして使用されています。

github.com

実際の Version が知りたい場合は 0xE0001FFF で AND をとる必要があります。

おわりに

Block Header からハッシュ値を算出し、それを検証するコードを実装していきました。

実際には今のマイニングマシンでは 32bit の Nonce の全探索は一瞬で終わってしまうので、色々な工夫・アルゴリズムが生み出されています。 これらも実装していきたいですね。

今回のコードの全容はコチラ: https://go.dev/play/p/a_-6rrM3bex

ということで大遅刻の14日目の記事でした。

adventar.org

参考文献


  1. 例えば Ethereum とかだと、微妙に事情が違ったりするので

  2. ハッシュ木 - Wikipedia

  3. と言いつつある程度自由度があるので nonce のようにマイニングのパラメータとして利用されている

  4. Length extension attack - Wikipedia

2021年度自宅開発環境

こんにちは taxio です。

日本の感染者数も落ち着いてきて、街に活気が戻り始めてきましたね。

とはいえ働き方に関しては以前の様に戻る方向に力は働かず、リモートワークが引き続き大きな選択肢として現れるようになりました。

ということで僕もリモートワークを快適にするためにリソースを集中投下し、一旦こんなものかなと思える基準まできたので、その自慢報告をしたいと思います。

自宅開発環境の様子
自宅開発環境の様子

椅子

www.hermanmiller.com

腰は絶対痛めたくないので、一番良いのをって感じでアーロンチェアを買いました。 今の所1日何時間座っていても腰が辛いと感じたことが全く無いので、この椅子がいいのか僕の腰が強いんだと思います。

デスク

flexispot.jp

お馴染みの昇降デスクです。

記録しておいた高さにワンタッチで調整してくれる機能があり、僕は座っているときと立っているときの2パターンを記録しています。

リモートワークで運動不足気味なので、1日に数回は立って作業するようにしています。 ミーティングの時間とか立ってることが多いかも。

デスクにはケーブルトレイとヘッドホンスタンドを吊り下げています。

www.amazon.co.jp

www.amazon.co.jp

ディスプレイ

www.dell.com

Dell の U4021QW という曲面ワイドディスプレイです。

僕は複数のディスプレイを使っていると、ディスプレイ同士の繋ぎ目がどうしても気になってしまうようなので、なるべく1枚にしたいと思っていました。

ところが次は 40 inch 超えの 4:3 のようなめちゃくちゃ大きなディスプレイを使っていると、特に端を見る時にかなり疲れるということも分かってきました。

どうしたものかと思っていたところ、たまたまみっきーさんのブログを読んで、そうか曲面ワイドという選択肢があるじゃないかと思い至り買うことにしました。

www.mzyy94.com

U4021QW は自身が USB ハブとして機能するので、基本的に全てのデバイスをディスプレイに繋ぎ、PC とは Thunderbolt 1本のみの接続で完結するようにしています。 USB PD 給電も行ってくれます。

ディスプレイアームには ergotron の LX デスクマウントアームを使っています。

www.ergotron.com

カメラ

大きなディスプレイを手に入れたので MBP はクラムシェルで使うことが多くなり、別でカメラが必要になってきました。

僕しか映らないので広角なものはいらず、そこそこ画質が良くカッコいい (とても大事) Web カメラを探していたところ、コスパが丁度良さそうな C980GR というカメラを見つけたのでこれにすることにしました。

www.logicool.co.jp

これ以上のものを求めようとすると一眼が選択肢に入ってくるようです。

マイク

普段 WH-1000XM3 というヘッドホンを使っているのですが、こいつのマイク性能があまり良くなく、音質トラブルが頻発していました。

オンラインミーティングにおいて音質はかなり重要なので、Yeti というマイクを買うことにしました。

www.bluemic.com

某2チャンネルを作った人が配信で使っているのと同じマイクですね。

ここにさらに Krisp というソフトウェアでノイズキャンセリングをしています。 こちら側の声以外の雑音やハウリングがキャンセルされるので、自分起因の音声トラブルはゼロになりました。

krisp.ai

MacBook Pro M1 Max

私物の PC が2017年の MBP だったので新調しようと思い切って買ってみました。 M1 Max にしたのはメモリを64GBにしたかったという理由だけです。

開発環境の構築には難ありですがそれ以外は非常に快適で、前の Intel Mac には戻れないなという感じです。

個人的には tensorflow-metal が M1 をサポートしているため、Colab を使わなくても GPU を使った機械学習環境を構築することができるようになっており、さらにユニファイドメモリなため巨大なモデルや入力も扱いやすいという、結構 ML をする人には嬉しいノート PC になっているのではないかなと思っています。 (何なら Colab での学習より早いような気もする。ちゃんと比べてないですが。)

ここから更に改善するなら

開発環境に直接関係するものはもう要らないかなと思い始めてきたので、もう1歩外側の環境を整えていこうと思っています。

具体的には以下のような感じでしょうか。

  • 足置き
  • コーヒーメーカー
    • でもメンテナンスはしたくない...
  • テレビとソファ の Upgrade
    • QOL は生産性に直結するので
  • Apple TV, HomePod mini

あーでもそろそろ分割キーボードとかも欲しいかも。

ということで、5日目の記事でした。

adventar.org

2020年振り返り

2020年も終わりなので雑に振り返ろうと思います。 気が向いたら加筆修正するかも。

振り返りブログを書くのは初めてで、特に今年の目標とか置いていなかったのでまずは時系列で振り返っていきます。

1-3月: 大学院生活

修論を書いてました。

手法が Deep 系だったので重みのお気持ちを感じながら心が折れつつも書き上げました。 扱っているデータ構造が木になっていて、そのままネストした構造を Tensor にはできないので、SparseTensor なんかに変換しながら頑張ってました。 TensorFlow 上のグラフが途切れないようにモデルを構築するのが大変でした。 (出たばかりの tf2 を使っていたというのもありますが)

修論が終わった後も研究所ではデータをまとめたりプログラムを改善したりしていました。

3月の中旬に東京に引っ越し、Wantedly で内定者インターンとして早めに働き始めました。 Wanteldy People 開発チームで機能開発や品質改善をしていましたが、この時 Rails はおろか、Ruby も1㍉も書けませんでした (なんで雇ってもらえたんだ)。 メンターに Python みたいなものだよと言われたので初めて出した PR で if 文に and を使ったらレビューで怒られました。 PythonRuby も許せん。

その後、Rubyhttps://www.ruby-lang.org/ja/documentation/ を、Railshttps://railsguides.jp/ を読んで大体雰囲気を掴み、メタプログラミングRubyを読んで Ruby の構造について勉強したりしました。

4-12月: 社会人生活

無事社会人になれました。

People と Profile 系の開発をしていて、いくつかプロダクトのリニューアルに携わりました。

www.wantedly.com

www.wantedly.com

いくつかの機能は丸投げ設計から任せてもらえて結構楽しかったです。

あとは、社内イベントの運営とか Go Conference 仙台で出した TechBook の編集とか、インターン生のメンターとか色々やった気がする。

この1年で新しく学んだこと/挑戦したこと

施策設計 / データ解析 ( BigQuery )

グロース系のタスクでは施策設計のために BigQuery で分析作業なんかもしていました。 入社当時は生の SQL 書くと蕁麻疹出ますって感じだったんですが、かなり慣れました。 最近は毎日ガリガリ書いてます。やっぱり場数ですね。

施策自体の提案なんかももちろんしますが、プロダクトの深い理解が必要なのでまだまだです。 情報設計能力・問題の構造化能力はもっと伸ばしていきたい。

考古学

多分このスキルが一番伸びました。 先輩に「よく見つけてきたね」って30回くらい言われた気がする。

なぜそのコードが入ったのか、当時の背景も含めて Git, GitHub, Slack から議論を探し当てる方法についてはどこかでまとめたほうがいいのかも知れない。 ですが大事なのはちゃんと commit message や PR のコメントを書くことです。 未来のチームのためにちゃんと議論は残しておきましょう。

他分野の読書

自分の専門分野の本しか読んだことが無かったので、知見を広めるために10月くらいから本を読み始めました。 内容の咀嚼に時間がかかったりするのでまだペースは遅いです。

特に面白かったのは以下の2冊。

発信系

普段からあまり Blog は書かないのですが、今年は特に書いてないですね。 良くない。

おわりに

「これやりたいです!」って言ったら100%通るどころか、もう勝手にやっちゃって良いような会社にいるので、のびのびと楽しんで開発しています。 来年も色々なものに手を出していきたいです。

具体的な OKR みたいなのは年が明けたら考えます。

ではー。

普段使ってるツールとか

たまにインターン生に「なんですかそれ?」とか言われるので、普段使っていて便利なツールやTipsについて軽くまとめてみる。 これは16日目。

adventar.org

使っているPCはMac Book ProなのでmacOSが前提。

アプリ系

Alfred

www.alfredapp.com

めちゃくちゃ強いSpotlightみたいなやつ。

アプリの起動やファイル検索はもちろん、任意のWeb検索やClipboard Historyなど様々な機能が使える。 Hotkeyを登録しておけばいつでも起動できるので、Google検索とかは基本Alfred経由で行っている。

特に自分が特に便利だなと思っているのは以下の2つ。

  • Snippets
  • Workflow

Snippets

Snippets and Text Expansion - Alfred Help and Support

登録しておいたスニペットを貼り付けてくれる機能。
しかもただ登録した文字列を貼るだけでなく、Clipboardの内容を反映させたり貼り付けた後のカーソルの位置を指定できたりと、ちょっとした拡張性がある。

いつも同じようなもの書いてるなとか、このコマンド覚えるの面倒くさいなと思ったものは基本これに登録している。
ちなみに毎回書きたくないランキング堂々の1位はHTMLの折りたたみ表現。Markdown書いているときによく使う。

Workflow

Alfred Workflows - Extend Alfred and Boost Your Productivity

課金すると使えるようになる機能で、ユーザが定義した処理をAlfred上でコマンドを入力したり、割り当てたHotkeyを入力したりして起動することができる。
Workflowの中ではbashスクリプトが実行できるので本当になんでもできる。便利。

作ったWorkflowはimport/exportできるようになっているので、世の中には便利なWorkflowがたくさん公開されている。

自分がよく使うのは以下の3つ。

Display Menu

Display Menu

Display Menu

  • Milch im Gemüsefach
  • ユーティリティ
  • 無料
apps.apple.com

macOSの標準設定よりも色々な解像度が選べる。 限界を超えたいときにおすすめ。

見えねぇよって時はアクセシビリティのズーム機能を合わせて使うと幸せになれる。

Macでアクセシビリティの「ズーム機能」環境設定を変更する - Apple サポート

Moom などのウィンドウマネージャを入れておくともっと幸せになれるかもしれない。

Moom

Moom

  • Many Tricks
  • ユーティリティ
  • ¥1,220
apps.apple.com

Todoist

todoist.com

タスク管理ツール。

最初はTrelloを使っていたがApple Watchに対応していなかったのでこっちを使うようになった。

Getting Things Doneのお気持ちが大事。

Inkdrop

www.inkdrop.app

Markdown形式のノートアプリ。 プラグインVimキーバインドを使えるのがポイント高い。

自分は日々のタスクを雑にメモったりしているので、Active/Completedのステータスが標準機能で付けられるというのがとても良かった。

標準のメモ帳やBear, Evernote, Scrapboxなど、色々なものを試してみたがこれが一番しっくりきたので今の所ずっと使っている。

エディタ系

Vim

汎用エディタ。

気軽に使いたいのでプラグインとかはあまり入れていない。 カラースキーマ系とlsp系とfzf連携くらい。

Jetbrains IDE

専用エディタ。

色々試行錯誤しながらコードを書く時は基本的にこっちを使っている。 (なのでVimはなるべくシンプルにしている)

ここでもVimキーバインドを使いたいのでideavimプラグインを入れている。 たまーに使い勝手が悪いことがあるのでその時は.ideavimrcでちょこちょこいじっている。

GitHub - JetBrains/ideavim: Vim emulation plugin for IDEs based on the IntelliJ Platform

CLIツール系

ag

github.com

ファイル内の文字列をいい感じにgrepするツール。 便利。

ghq

github.com

リポジトリの取得・管理を便利にするツール。 便利。

使い方はこちら。

GitHub - Songmu/ghq-handbook

fzf

github.com

曖昧検索ツール。

など、何かしらの検索によく使う。便利。

他にも色々曖昧検索ツールは存在するが、僕はfzfのスコア計算が好きなのでこれを使っている。

tldr

github.com

「あのコマンドってどうやって使うんだっけ?」って思ったときに使い方のサマリを出してくれるツール。 ネットで調べたりmanを見たりしたくないときに便利。

❯ tldr curl

curl

Transfers data from or to a server.
Supports most protocols, including HTTP, FTP, and POP3.
More information: <https://curl.haxx.se>.

- Download the contents of an URL to a file:
    curl http://example.com -o filename

- Download a file, saving the output under the filename indicated by the URL:
    curl -O http://example.com/filename

- Download a file, following [L]ocation redirects, and automatically [C]ontinuing (resuming) a previous file transfer:
    curl -O -L -C - http://example.com/filename

- Send form-encoded data (POST request of type `application/x-www-form-urlencoded`). Use `-d @file_name` or `-d @'-'` to read from STDIN:
    curl -d 'name=bob' http://example.com/form

- Send a request with an extra header, using a custom HTTP method:
    curl -H 'X-My-Header: 123' -X PUT http://example.com

- Send data in JSON format, specifying the appropriate content-type header:
    curl -d '{"name":"bob"}' -H 'Content-Type: application/json' http://example.com/users/1234

- Pass a user name and password for server authentication:
    curl -u myusername:mypassword http://example.com

- Pass client certificate and key for a resource, skipping certificate validation:
    curl --cert client.pem --key key.pem --insecure https://example.com

gh

github.com

GitHubの公式CLIツール。 gh apiでv3, v4の任意のAPIが叩けるので何でもできる。 超便利。

その他

GitHubのファイル曖昧検索

docs.github.com

リポジトリのTopページでtを押すと使える。 便利。

GitHubでmentionするときにアイコン出してくれるやつ

chrome.google.com

基本的にアイコンで人を覚えているのでこれが無いと厳しい。

git log -S

差分の検索ができる。 便利。

終わりに

ペアプロ・ペアオペとかすると相手が使ってる便利ツールが知れて良いですよね。
自分がみんな知ってるでしょって思ってたツールも意外と知られてないことがあったりして驚くので、よく使うやつを書き出してみました。

でも流石にここらへんは知ってそう。

unexportedなmethodを持つGoのinterfaceとsum type

何か書けと後輩に詰められたので、寝る前に雑に調べごとをしてメモを残しておくことにする。 これは8日目。

adventar.org

Goのinterfaceに書けるメソッド名にはidentifierが指定されているだけで特に制限が無い。 つまり、unexportedなメソッドを定義することができる。

InterfaceType      = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" .
MethodSpec         = MethodName Signature .
MethodName         = identifier .
InterfaceTypeName  = TypeName .

( https://golang.org/ref/spec#Interface_types より )

周りのコードを見渡してみると、例えば reflect.Type なんかはこれに該当する。

type Type interface {
  ...
  common() *rtype
  uncommon() *uncommonType
}

( https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/reflect/type.go#L27-L214 より )

ちなみにgo docは通常exportedな情報しか表示してくれないが、-uをつけるとちゃんとunexportedの情報も見える。

こういったinterfaceはunexportedなmethodを持つため、(もちろんunexportedな型を返り値に持っているというのもあるが)パッケージの外でこれを満たす型を定義することはできない。

package foo

type A interface {
    Hoge() int
}

type B interface {
    Hoge() int
    piyo() int
}
package main

import (
    "fmt"

    "github.com/taxio/playground/foo"
)

type X struct{}

func (x *X) Hoge() int { return 0 }

func NewA() foo.A { return &X{} }

type Y struct{}

func (y *Y) Hoge() int { return 0 }
func (y *Y) piyo() int { return 0 }

func NewB() foo.B { return &Y{} }

func main() {
    fmt.Println(NewA())
    fmt.Println(NewB())
}
❯ go build
# github.com/taxio/playground
./main.go:20:28: cannot use &Y literal (type *Y) as type foo.B in return argument:
        *Y does not implement foo.B (missing foo.piyo method)
                have piyo() int
                want foo.piyo() int

調べてみるとこういうのをSealed Interfaceというらしい。 続けて「sum typeをエミュレートするのにも使える」と記述されている。

-- https://blog.chewxy.com/2018/03/18/golang-interfaces/
Sealed interfaces can only be discussed in the context of having multiple packages. A sealed interface is an interface with unexported methods. This means users outside the package is unable to create types that fulfil the interface. This is useful for emulating a sum type as an exhaustive search for the types that fulfil the interface can be done.

sum typeというのはどうやら代数的データ型というトピックの中で出てくるものらしく1、Tagged Unionなど様々な呼ばれ方があるらしい2 (wikipedia調べ)。

Tagged Unionで適当にggってると、TypeScriptの記事がよく目につく。

これはTypeScript 2.0で入った機能らしく、これを応用することでそれぞれのメンバが条件的に網羅されているかを検証できるらしい3。 確かに言われてみれば、switch文のdefault節の中にneverで網羅性をチェックするコードを書いた記憶がある。

凄い、知らない間に代数的データ型の恩恵を受けていたのか。 数学的なバックグラウンドは全く分からないので、今度会社の人に優しそうな型の入門書を聞いてみよう4

さて、こんな便利な機能がGoでも使える可能性があると分かったのであれば試すほかない。 先の記事で紹介されていたgo-sumtypeのREADMEを眺めながら手元で動かしてみる。

github.com

❯ go get -u github.com/BurntSushi/go-sumtype

サンプルのコードを少しいじって以下のようにしてみた。 構造体A,BはインターフェースXを満たす。

package main

import "fmt"

//go-sumtype:decl X
type X interface {
    sealed()
}

type A struct{}

func (a *A) sealed() {}

type B struct{}

func (b *B) sealed() {}

func NewXA() X {
    return &A{}
}

func main() {
    x := NewXA()
    switch x.(type) {
    case *A:
        fmt.Println("A!")
    case *B:
        fmt.Println("A!")
    default:
        panic("unreachable")
    }
}

この状態ではgo-sumtypeは怒らない。

❯ go-sumtype main.go

しかし*Aのcaseを除くと、

...
    switch x.(type) {
    // case *A:
    //     fmt.Println("A!")
    case *B:
        fmt.Println("A!")
    default:
        panic("unreachable")
    }
...
❯ go-sumtype main.go
~/go/src/github.com/taxio/playground/main.go:24:2: exhaustiveness check failed for sum type 'X': missing cases for A

しっかり条件が網羅されていないことを報告してくれた。

これを応用すればもしかして網羅的なerrorチェックみたいな面白いことができる...??? ( 眠い頭で適当なことを言っています。)

おわりに

パッと調べただけだったが、思ったより面白い話が見つかった。 もうちょっとちゃんと調べて年末の技術書典で記事を出そうかな。知らんけど。

ではおやすみなさい。