2022/07/31

[vscode] vscode と editorconfig

私はテキストエディタでハードタブ、キャラクタコードで 0x09 の表現をスペース4つで見えるようにしていることが多い。

UNIX とかは 8 文字だけど、横幅が広くなると困るので 4 つにしている。
もともと C言語で書いていて、X Window とかで書いているときはそのまま従って 8 つだったのだけど、当時の文化として「横幅はだいたい 80桁で!」だったはずだ。メールも 76 だったと思う。
そうなるとインデントで幅を取られることが馬鹿らしくなり、自然と短くしていったのだった。

 

HTML を触ることがあった。
そのときに「タブは 2文字」というのがよく使われていたのだ(私の作業では)。
そもそもその時代になると、ハードタブで個人差が生じるくらいだったらソフトタブでスペースを直接埋め込めば間違いがない、という風潮だったように思う。
「インデントを削るのに文字数分削除するのが面倒!」「間違えて 3 とか 5 になってしまうかもしれないじゃないか!」と心の中で反発しつつも使っていくと、うん、慣れてしまった。
今やスペースでタブを表現せずにどうするんだ、くらいまでになっている。
人間、そんなもんだよね。

 

さて、ここまでは時代の風潮くらいのものなのだが、言語仕様でタブを意識するモノが出てきた。
Python と Golang だ。

Python は、インデントが正しくなっていないと実行時エラーになっていたと思う。
面倒なので例として PEP8 のインデントルールを見てみよう。

PEP 8 – Style Guide for Python Code | peps.python.org
https://peps.python.org/pep-0008/#indentation

> Use 4 spaces per indentation level.

なるほど、4スペースか。
・・・ではなく、Python の仕様確認が先だろう。

2.1.8 Indentation
https://docs.python.org/release/2.5.1/ref/indentation.html#l2h-9

> First, tabs are replaced (from left to right) by one to eight spaces such that the total number of characters up to and including the replacement is a multiple of eight (this is intended to be the same rule as used by Unix). The total number of spaces preceding the first non-blank character then determines the line's indentation. Indentation cannot be split over multiple physical lines using backslashes; the whitespace up to the first backslash determines the indentation.

Google翻訳
> まず、タブは (左から右へ) 1 から 8 個のスペースで置き換えられ、置換を含む最大文字数が 8 の倍数になります (これは、Unix で使用されるのと同じ規則になるように意図されています)。次に、空白以外の最初の文字の前にあるスペースの総数によって、行のインデントが決まります。バックスラッシュを使用してインデントを複数の物理行に分割することはできません。最初のバックスラッシュまでの空白がインデントを決定します。

うーん、これを正しく解釈できる自信がない。
tabs を one to eight spaces に置換するということは、純粋に 8文字のスペース文字に変換するのではなく、 8文字インデントになっている文章と見なして最大 8 文字のスペース文字に変換して頭を揃えようとする、ということだろうか。
インデントエラーの例も、けっこう緩いと思った。

  • 関数の先頭がインデントされているのはエラー
  • 関数の中身がインデントされていないのはエラー
  • インデントされないはずの行でインデントが変化しているのはエラー
  • インデントが戻るはずがない行でインデントが変化しているのはエラー

いくつインデントすべき、みたいなのは要件に入らないということだろう。


さて、いつものように本題から逸れていますが、いつものことです。

お勉強中の Go言語だけど、あっちは「インデントはハードタブ」という仕様だ!・・・と思っていたのだけど、Launguage Specification に "indent" は出てこなかった。

The Go Programming Language Specification - The Go Programming Language
https://go.dev/ref/spec

あーれー、私の勘違い??
まあいいや。golang のフォーマッタを使うと自動的にタブ文字になっていたと思うし。

 

そこらへんはよかったのだが、他から clone した GitHub のプロジェクトのファイルを開いたときに何か違和感があった。
おかしい・・・初めて見るコードなのに違和感がある・・・。

ああ、タブ文字が 8 スペースで表示されているんだ。
でも vscode の設定では 4 スペースにしているし。。。
4 スペースに変更しても別のファイルでは 8 スペースで表示されるし。。。
なんなの??

 

ようやく本題ですが、このプロジェクトには `.editorconfig` というファイルがあり、それを vscode が読み込んで反映させていたようだった。

EditorConfig
https://editorconfig.org/

私の設定よりもこっちが優先されるのね。
まあ、そうでないと意味が無いわな。


EditorConfig のページはなかなか気合いが入っている。
サポートしているサイトの紹介があるのだが、そのリンク先画像がたぶん自作なのだ。 GitHub なんかこれだよ。

image

普通に引用した方がかんたんだろうに、あえてこのページの世界観でアイコンを描くというところに気合いを感じる。だって、アイコンをそのまま借りた方がはるかに楽なのですぞ。

 

EditorConfig はすべてのエディタがサポートしているわけではないと思うが、いくつかの主要なエディタが採用しているならファイルとして作って置いても損にはならないかもしれない。テキストエディタで外部からの設定を求めるというのはどうなんだろうと考えなくもないが、嫌なら使わなければよいだけの話である。

vscode だとプラグインがいりそうなことを書いてあるのだが、使っていないつもりなのに反映されてしまった。
こういうのは個人の好みもあるから、けっこう難しいね。

2022/07/30

[win10] キーアサインの変更(2022年7月)

Windows でキーアサインを変更したいという要望はそこそこあると思う。
そうでなければ PowerToys にそういう機能を付けないだろう。

PowerToysWindows 用の Keyboard Manager ユーティリティ | Microsoft Docs
https://docs.microsoft.com/ja-jp/windows/powertoys/keyboard-manager

私は AutoHotKey と Change Key で割り当てている。

AutoHotkey
https://www.autohotkey.com/

「Change Key」非常駐型でフリーのキー配置変更ソフト - 窓の杜
https://forest.watch.impress.co.jp/library/software/changekey/

Change Key は CapsLock を 左Ctrl にするのと、半角/全角 を Esc と入れ替えるのに使っている。
AutoHotKey だけでいいやん、と思うかもしれないが、少なくとも私が AutoHotKey を使い始めた頃はまだうまくいかなかったのだ。

それに、AutoHotKey はアプリが起動しないと使えないが、Change Key はその前から使えるのでログインする前でも確か使えていたと思う。

そんな Change Key 万能!という雰囲気をかもし出しつつも、これは単独のキーしか変更できない。
Ctrl+F で右矢印キーの代わりにする、なんてことはできない。


そんなわけで、ショートカットキーのキーアサインを変更するアプリについてだ。

 

AutoHotKey も PowerToys もだが、Ctrl+[何か] のアサインを割り当てた場合、たまに Ctrl が押されない動作をしてしまうことがある。

AutoHotKey は長押しすると発生しやすいし、CPU負荷が高まるとさらに発生しやすいと思う。私は Ctrl+F を右矢印キーに割り当てているのだが、 Android Studio のエディタでそれをやるとときどき「f」が打ち込まれてしまってあせる。そして Android Studio は保存キーを使わずに勝手に保存したりするので、コンパイルエラーになって気付くことがたまにある。

PowerToys の場合はキーの押し始めに抜けることが多いと思った。処理が重たくなると押しっぱなしの状態でもすり抜けることはあるが、AutoHotKey よりは少ないかも?

 

AutoHotKey についてはもう 9年も前の記事だが、これがやっぱり出てくる。

AutoHotkey:キー押しっぱなし病・ホットキーすり抜け病対策の研究
https://did2memo.net/2013/10/03/autohotkey-ctrl-key-is-stuck/

まあ、すり抜けるときはすり抜けるのだけど、PowerToys で発生するということは OS として発生するのではないかと思う。それを対策したら AutoHotKey などでもうまくいくようになってくれるとよいのだがね。

AutoHotKey はスクリプトで書けるから楽なのが良い。自由度も高いし。そのせいで PowerToys よりもすり抜けやすくなっているのではないかという気がしなくもない。

難しいもんだね。

2022/07/10

[golang] 埋め込む順番で type は異なる

構造体の勉強中。

interface は「こういうメンバたちを持ってるよね? 持ってないと我が一族の名を名乗れないからね?」という脅しだと思う。いや、コンピュータ言語で脅しても仕方ないのだが、コンパイルエラーになるという意味では脅しと受け取った方がよいんじゃなかろうか。

ここ数年 C++ から離れているが、 class のメソッドで =0 にしていると abstract なメソッドという扱いになって、必ず派生した class でオーバーライドしないといけないようになってた気がする。
=0 が 1つでも入っているとインスタンスにできなかったと思うので、それがなくなるごとに抽象度が減るという表現をするのかもしれない。

 

その interface と似てるような似てないようなのが「埋め込み」とか「annonymous field」というやつだと思う。今のところお仕事で使う機会が無いのだが、C言語で class 的なものを使いたい場合のやり方だと認識している。
C言語だと構造体のメンバを定義した順にメモリ配置するので、

struct A {
  int a;
  char b;
}

struct B {
  int a;
  char b;
  double c;
}

という構造体を用意すると、a と b については型とメモリ上のアドレス相対位置が一致するので struct A と struct B の変数は a と b については気にせずアクセスできる。
c については、 struct A の変数を struct B としてキャストすればアクセスはできるだろうが、読み込めるのはそのアドレスにあるデータなのでどうなるかは不明だ。まあ alignment はpack(0) とかしてなければ問題ないので死にはしないだろう。

結局のところ、メモリ上にあるものはすべて「データ」に過ぎない。
それがプログラムなのか文字列なのかExcelのデータなのかというのは後付けの理由だ。画像データなんてフォーマット以外の部分はバイナリデータに過ぎないので、そこにプログラムとして動作する値があったとしても不思議ではない。ウイルスだったりセキュリティホールだったりはそういうところからやってくるんだろう。

 

さて話を戻して。

struct の説明で、メンバの並びが違うと違うものとして扱われるというのをどこかで読んだ気がする。まあ、私は C言語のことを思い浮かべながら Go言語を見ているので、書いてなくても「きっとそうなんだろう」と思い込んでいるだけかもしれない。

埋め込みだとどうなのか、ちょっと確認してみよう。

package main

import "fmt"

func main() {
    // annonymous field
    type Name struct {
        name string
    }
    type Age struct {
        age int
    }
    type User1 struct {
        Name
        Age
    }
    type User2 struct {
        Age
        Name
    }

    var u1 User1
    var u2 User2
    u1.name = "kuma"
    u1.age = 98
    u2.age = u1.age
    u2.name = u1.name
    fmt.Printf("u1=%v\n", u1)
    fmt.Printf("u2=%v\n", u2)
    fmt.Printf("compare: %v\n", u1 == u2)
}

最後の u1 == u2 がコンパイルエラーになる。

invalid operation: u1 == u2 (mismatched types User1 and User2)

ただ・・・これはAge と Name が違うからではなく、純粋に struct で定義したものが違うからエラーにしているだけだと思う。並びを同じにしてもエラーになるからだ。

 

多少異なるのがキャストについてだ。
埋め込む順番を同じにした場合、 User2(u1) も User1(u2) もエラーにならない。

しかし Name(u1) も Name(u2) もエラーになる。
そこは通してやってもいいんじゃないかと思ったが、そうすると Age(u1) なんかも通さないといけなくなってしまうのかもしれない。メンバの先頭だけ OK というのは説明しづらいからな。

2022/07/09

[javascript] プリミティブ型と toString()

困ったことに、JavaScript の try-catch で catch するものは Error 型とは限らないそうだ。
取りあえずログに出したかったのだが、JSON.stringify(e) で {} になったり、 e で [Object Object] になったりして、もう考えるのが面倒になってきた。
Error 型だったら、e.message と e.name はあるのだけど、ライブラリを使っていると派生したオブジェクトを返したりして、もうどうしてよいやら・・・。

Error - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Error

よく見ると、インスタンスメソッドで toString() があった。

Error - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#instance_methods

Error を派生したんだったら toString() もオーバーライドしてほどほどの文字列を作るのが礼儀なんじゃなかろうか、きっとそうだ、と思い込むことにした。

 

しかし、throw が Error 型じゃなくてもよいということはプリミティブ型でもよいはずだ。
プリミティブ型はオブジェクトじゃないはずだ。
オブジェクトじゃないなら Object を継承しないので toString() を持たないかもしれない。
だったら e の型チェックしてからじゃないと toString() が使えないのか。
めんどくさい・・・。

 

と思って文字列や数字を throw したのだが、toString() で文字列として出力されていた。
なんで??

本を読んだところ、プリミティブ型は使われる際にラッパーオブジェクトが一時的に生成されるようになっているらしい。
つまり、文字列なら String型、数字なら Number型が生成される。
これらはオブジェクトなので toString() を使える、という理屈のようだ。

もしかしたら toString() などのメソッドを使う直前に生成されるのかもしれん。そっちの方が理屈に合うか。

 

それはそうと、元の問題である catch したデータをログに出した意見だが、これは昔からどうしたもんだかって話のようだ。 Error の派生についてStackoverflow のリンクが載っているくらいだから、 throw となるとさらにめんどくさそうだ。

throw - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/throw

オンラインでコードを動かせるので throw を書き換えながら試したが、 new Error(文字列) は console.log で出力できるが、単に Object として {message: 文字列} なんかを throw すると [object Object] になる。
じゃあ console.log で JSON.stringify() したものを出せば良いのだが、 new Error() だと {} だけになる。

 

そういえば、console.log を使いたいわけでは無く、ログファイルに出力させたいので「文字列」として取得したいのを忘れていた。 console.dir が使えるという記事があったのだけど、そういうわけで今回は対象外だ。

となると、無難に toString() しておけば文字列として取得できるのか。
オブジェクトの中を全て見れば詳細な情報が確認できる場合もありそうだが、そこはもうピンポイントで調べたい場合だけとあきらめても許してくれるんじゃないだろうか。

2022/07/03

[golang] よく出てくる context.Context

通信するライブラリなどを使おうとするとしばしば登場するのが context.Context だ。
gRPC だとこんな感じ。

Basics tutorial | Go | gRPC
https://grpc.io/docs/languages/go/basics/#simple-rpc-1

context.Background() でコンテキストを作って与えておけば取りあえず使えるので深く考えずに使っていたのだが、真面目に実装していくと理解していないとまずいと感じた。というより、あまり理解していなくてコードレビューで指摘を受けてしまった。

よく出てくるのでほどほどに理解したい。
ネットで「golang context」で検索するとたくさん出てくるので、きれいな解釈はそちらを見た方がよいだろう。なら書くなよと思われそうだが、私は書かないと理解ができないタイプなので仕方ないのだ。


まずは godoc

context package - context - Go Packages
https://pkg.go.dev/context#Context

出てくるのは context.Context だが、パッケージの存在目的が Overview に書いてあるので目を通す。

  • プロセス間で伝播
    • デッドライン
    • キャンセル
    • シグナル
    • その他 request-scoped な値

「between proceses」とあるが、Linux でいうところのプロセスではなく goroutine を指してるのだろうか。
「request-scoped values」は、処理の要求を行ったコンテキストが持っている値だろうか。 goroutine だったらそういう値はがんばらなくても参照できそうなものだが、うーん?

「プログラミング言語Go」には context が載っていない。本は v1.5 のときに書かれているのだが context が生まれたのは v1.7 のようだ。ちなみに本では goroutine のキャンセルについて書かれており、それは channel を使って行うようになっている。

 

こちらが、よく紹介されている go.dev のブログ。

Go Concurrency Patterns: Context - The Go Programming Language
https://go.dev/blog/context

まあ、もう 8年も経っているので日本語で紹介しているサイトの文面を読むだけでいいや。。。


いろいろ使い道はあるのだが、主な使い方はキャンセル処理の通知だと思う。
チャネルを使えば処理の終了を待ち合わせることもできるし、キャンセル処理をさせることもできる。

hiro99ma blog: [golang] chan で待つ
https://blog.hirokuma.work/2022/06/golang-chan.html

ただ、それをおのおのの実装でやっていくと違いが出てくるだろうし、似たような処理があちこちに出てきて格好が悪くなるから context にそういうのをまとめておいてみんな使いましょう、ということなのだろう。
A が Bライブラリを使って、 B が Cライブラリを使って、とやっていって、Aがキャンセルする処理を Bに伝えて、それを B が Cに伝えて、ということになるのをルール化したというところか。

なので、goroutine を走らせておいて「あとは好きにやっとけ」というタイプの処理だと使うことが無いかもしれない。多少なりとも goroutine を走らせた方が結果を待ったりするからこそ自分がキャンセルすることを下々に通知する必要が出てくると思われるからだ。

ただ、goroutine を呼び出した方が先に終了してしまうと goroutine の処理が終わる前に中断させられてしまうことになるから、何かしら終了を待つようなことにはなるのではなかろうか。
そう考えると、あまり難しく考えず、goroutine の中で context.Context を引数にとる API を使うなら その外側で context.Context を渡すようにした方がよい、くらいでいいのかな。

 

そういう見方をすると context のメンバーの用途がわかりやすい。

Done() は呼び出し元がキャンセルしたのを子が知るための channel 。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    fmt.Printf("start\n")
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go func(ctx context.Context) {
        fmt.Printf("goroutine start\n")
        time.Sleep(10 * time.Second)
        fmt.Printf("goroutine done\n")
        <-ctx.Done()
        fmt.Printf("detect Done!")
    }(ctx)

    time.Sleep(5 * time.Second)
    cancel()
    fmt.Printf("main done.\n")
}

$ go run .
start
goroutine start
main done.

まあ、待ち始めるのが 10秒後なので、5秒後に cancel() するとそうなるよね。。。
だからといって goroutine の方を先に終わらせて Done() を待つというのは意味が無い。それなら goroutine を終われば良いだけだ。
となると、select で Done() を待つのと同じように、別の channel も待つようなシーンでないと意味が無いのか。

やるなら、cancel() を通知した後、自分が実行した goroutine が終わるのは待つようにするというところか。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    fmt.Printf("start\n")
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    done := make(chan struct{})
    go func(ctx context.Context) {
        fmt.Printf("goroutine start\n")
        time.Sleep(10 * time.Second)
        fmt.Printf("goroutine done\n")
        <-ctx.Done()
        fmt.Printf("detect Done!")
        done <- struct{}{}
    }(ctx)

    time.Sleep(5 * time.Second)
    cancel()
    <-done
    fmt.Printf("main done.\n")
}

5秒経過したら cancel() を呼び、待ち合わせている goroutine からの channel done を待ってから終了させる。
done を通知するのは自作の goroutine だから、done しなかったら自分のバグということでよいだろう。

では、もしその goroutine の中で別の goroutine を呼び出しているのであればどうするか?
同じだ。 Done() の channel を受け取ったら配下の goroutine をキャンセルさせるように通信して終わるまで待つのだと思う。

いやー、難しいね。