LEFログ:学習記録ノート (original) (raw)
たまにしか実行せずちょこちょこ忘れるのでメモを兼ねて書きます。自分はいつもターミナル上でこのコマンドを実行しています。
find ~/dotfiles -maxdepth 1 -name '.*' -exec ln -s {} ~/ ;
これでdotfiles/内に移動したファイルたちのシンボリックリンクをホームディレクトリ配下に一発で作成できます。一つずつ手作業する必要がなくなります。
for文を回す次のようなスクリプトでも良いのですが、ワンライナーのほうが格好良いのと、ファイル化するのがちょっと面倒なので上のコードで実行しています。
for file in ~/dotfiles/.*; do ln -s "$file" "$HOME/$(basename "$file")" done
結論
- Go の interface は型安全にダックタイピングを使いたいときに使おう
- Go の interface は(
implements
のような)キーワードを使うことなく、同じメソッドを実装するだけで暗黙的に決定される
はじめに
皆さんは Go の interface を知っていますか?
自分はこの interface を理解するまでに少し時間が掛かりました。今では「完全に理解した」*1状態になっています。
そのため、この記事ではできる限り分かりやすく interface を解説して、世界一の分かりやすさを目指します!
interface を使いたくなるとき
interface を使いたくなるとき、それはダックタイピングをしたくなったときです。
まず、ダックタイピングとは何なのかを説明しましょう。
ダックタイピングとは、Dave Thomasの言葉を借りるならこのようにまとめられます。
これだけではよく分からないかもしれません。もうちょっと具体的に示しましょう。
ダックタイピングの例
まずは例として Ruby のコードを紹介します。
このコードは、異なる出力方法(コンソール、ファイル、バッファ)にデータを書き込むためのクラスを作り、それらを共通の関数で扱えるようにしている例です。
class ConsoleWriter def write(data) puts data end end
class FileWriter def initialize(filename) @filename = filename end
def write(data) File.open(@filename, 'w') { |file| file.write(data) } end end
class BufferWriter def initialize @buffer = "" end
def write(data) @buffer << data end
def contents @buffer end end
def write_to_somewhere(writer, data) writer.write(data) end
message = "Hello, Interface!"
console_writer = ConsoleWriter.new write_to_somewhere(console_writer, message)
file_writer = FileWriter.new("output.txt") write_to_somewhere(file_writer, message)
buffer_writer = BufferWriter.new write_to_somewhere(buffer_writer, message) puts "Buffer contents: #{buffer_writer.contents}"
このコードには write_to_somewhere
関数があります。この関数はwriter
オブジェクトの具体的な型を指定していませんが、write
メソッドを持っていることを前提としています。
そして、ConsoleWriter
、FileWriter
、BufferWriter
の各クラスは、すべて write
メソッドを実装しているため、write_to_somewhere
関数で使用できます。
つまり、期待されるメソッドを持っていれば、そのオブジェクトを使用できます。
この方法は柔軟性が高く、新しいWriter
クラスを追加する際に、既存のコードを変更することなく、write_to_somewhere
関数と互換性を持たせることができます。
ダックタイピングのデメリット
しかし、ダックタイピングにはデメリットがあります。
ダックタイピングでは、オブジェクトの型ではなく振る舞い(メソッド)に基づいて操作を行います。
そのため、実行されるまでエラーに気づかない可能性もあります。
class InvalidWriter
def write_data(data)
puts "Writing: #{data}"
end
end
InvalidWriter
クラスは write
メソッドではなく write_data
メソッドを持っています。
そのため、このクラスから生成されたオブジェクトをwrite_to_somewhere
関数に渡すと、実行時に NoMethodError
が発生します。
これはとても簡単な例です。型チェックがない場合、こうしたミスをしてしまう可能性があります。
シンプルに書けるというメリットと引き換えに、上記のデメリットを引き受ける必要があります。
Go で同じコードを実装すると……
さて、次に全く同じ振る舞いのコードを Go で実装してみましょう。
このコードは、異なる出力方法(コンソール、ファイル、バッファ)を Go のインターフェースを使って抽象化し、共通の関数で扱えるようにしている例です。
type Writer interface { Write([]byte) (int, error) }
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(data []byte) (int, error) { n, err := fmt.Println(string(data)) return n, err }
type FileWriter struct { filename string }
func (fw FileWriter) Write(data []byte) (int, error) { return os.WriteFile(fw.filename, data, 0644) }
type BufferWriter struct { buffer bytes.Buffer }
func (bw *BufferWriter) Write(data []byte) (int, error) { return bw.buffer.Write(data) }
func WriteToSomewhere(w Writer, data string) { w.Write([]byte(data)) }
func main() { message := "Hello, Interface!"
cw := ConsoleWriter{}
WriteToSomewhere(cw, message)
fw := FileWriter{filename: "output.txt"}
WriteToSomewhere(fw, message)
bw := &BufferWriter{}
WriteToSomewhere(bw, message)
fmt.Println("Buffer contents:", bw.buffer.String())
}
先ほどの Ruby のコードと似ていますが、一番大きく違う部分があります。そう、interface の存在です。
type Writer interface { Write([]byte) (int, error) }
Writer
インターフェースが明示的に定義されています。これにより、Write
メソッドの期待される振る舞いが明確になります。
例えば、コンパイル時に型チェックが行われるため、WriteToSomewhere
関数に渡されるオブジェクトが確実に Writer
インターフェースを実装していることを保証します。*2
また、Write
メソッドの引数と戻り値の型が Writer
インターフェースで明確に定義されています([]byte
を受け取り、(int, error)
を返す)。 これにより、全ての実装が同じシグネチャに従うことが保証されます。
interface を使うと、ダックタイピングをしつつ、型を明示することができます。
他の言語の interface との比較
ここまでの説明でまとめに入って良いかもしれませんが、理解を深めるためもう少しだけ解説します。
interface という概念は他の静的型付け言語にもあります。それらと Go の interface はどう異なるのでしょうか?
今度は別の言語と比較してみましょう。
次のコードは、Java を使って同じ振る舞いを実装したものです。
import java.io.IOException;
interface Writer { void write(String data) throws IOException; }
class ConsoleWriter implements Writer { @Override public void write(String data) { System.out.println(data); } }
public class Main {
public static void writeToSomewhere(Writer writer, String data) throws IOException {
writer.write(data);
}
このコードにも interface が出てきました。ここだけ見ると、Go の実装と似ています。
interface Writer { void write(String data) throws IOException; }
ただし、ここからが重要なポイントですが、先ほどの Go のコードと大きな違いがあります。
それは、**implements
キーワードを使っている**ということです。
class ConsoleWriter implements Writer {
つまり、Writer
という interface を使うためには、必ず implements
キーワードをセットで使う必要があります。*3
Go の場合はこのimplements
キーワードを書かなくても、暗黙的に型チェック時に interface に適合しているかをチェックしてくれます。
Go のインターフェースの実装は暗黙的です。つまり、実装を明示的に宣言する必要はありません。インターフェースの実装は、インターフェースのメソッドを実装する型によって決定されます。その型がインターフェースで定義された全てのメソッドを実装している限り、追加の宣言は必要なく、そのインターフェースを暗黙のうちに実装することになります。
> Dog 型が Animal インターフェースを実装しているかどうかは、Dog 型が Animal インターフェースで定義されたすべてのメソッドを実装しているかどうかで判断できます。
改めて先ほどの Go のコードを見てみましょう。
type Writer interface { Write([]byte) (int, error) }
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(data []byte) (int, error) { n, err := fmt.Println(string(data)) return n, err }
ここで、ConsoleWriter
にはWrite
というメソッドが実装されていて、引数がdata []byte
で戻り値が(int, error)
になっています。
これはWriter
の interface に合致すると Go が暗黙的に決定してくれるため、implements
が必要ないというわけです。
Go では、「コードが interface に従うかどうかは、メソッドが合致するかどうかで決まる」という哲学を持っています。*4
そのため、後からコードを書き換えるのが楽なつくりになっています。
まとめ
改めて結論をまとめると、次のようになります。
- Go の interface の使いどころは、ダックタイピングを使いたくなったとき
- Go の interface によるダックタイピングは、型安全に実装できる
- Go の interface は、他の言語の interface とは違って、特別なキーワードを用いなくても暗黙的に決定される。
Go 言語の interface について理解の一助になれば幸いです。
参考
この記事を書くにあたって、オライリー・ジャパンさんから出版されている『初めての Go 言語』がとても参考になりました。
特に日本語版オリジナルの内容である、付録 A と B が素晴らしいので、ぜひご一読をオススメします!
今日の1on1で島田さんと話した内容が自分にとってとても有用だったので忘れないうちに文章に残したいと思います。
結論から言うと、オフのときにもスケジュールを作ることが重要だと分かりました。これがどういうことが説明していきます。
自分はオンとオフの切り替えがすごく苦手で、そこそこ負荷の高い作業があったときや、解決していない問題がIn progress(進行中)に残り続けているとそのことをずっと考えてしまって、なかなか気持ちを切り替えられずに困っていました。
時間で区切って切り替えようと思っても、なかなかその問題が頭から離れません。ぐるぐると考えてしまって他のことに意識を向けづらくなります。
一つの解決策としては運動をすること――自分の場合だとエアロバイクを漕ぐことで肉体を動かして、問題に意識が向かないようにして強制的にオフにすることです。これはそこそこ効果が高く、健康にもつながって良いです。しかし、疲れているときはエアロバイクを漕ぐ気力すら起こらないこともあります。
そこで出てくるのが先程の結論で、つまりオフのときにも「そのタイムボックスで取り組むことを事前に決める」、つまり「オフでもスケジュールを作ること」が有用そうだと分かりました。
自分はオフの時間は特にスケジュールを決めておらず、その日の気分で決めています。その日の気分で決める……ということは、そうです、気分に影響されてしまいます。スケジュールが無いからこそ、その空白を業務の気分が占めてしまい、結果として気持ちを切り替えられないという問題が発生していることが分かりました。
ここで大事なのは、事前に決めるということです。そのタイムボックスに入る前に、そのタイムボックスでやるべきことを一つ決めておきます。例えば仕事が終わったときに気持ちを切り替えたいのであれば、仕事が終わる前からやるべきことを決める必要があります。そうしないと、「いざそのときになってから」だと、気分によって決断力が鈍ってしまい、ずるずると問題を考え続けることになります。
自分のようにオンとオフの切り替えが苦手な方の参考になれば幸いです。
概要
最近、Zennで次のような記事を書きました。
useEffectの中でsetStateを使うときはアンチパターンを疑おう
お盆休みのおかげか思った以上にアクセスが伸び、Zennのトップページ&はてなブックマークのトレンドに掲載されました。読んでくださった皆様、ありがとうございます!
Zennのトップページ
はてなブックマーク - 人気エントリー - テクノロジー - 2024年8月17日
このブログ記事では、上記のZennの記事に書けなかった細かな補足情報について書いてみようと思います。
setStateを呼ぶ関数をuseEffect内で登録する
具体的にはuseEffectでsetStateを書くことが問題ない場合についてです。
useEffect内部で直にsetStateを呼んでるのはほぼ間違いなく他に良い書き方があるけど、useEffectの中で「setStateを呼ぶ関数を登録する」は問題無いんだな
ちょっと躓きやすいポイントかもしれない— 夜綱 (@sub_827) 2024年8月16日
https://x.com/sub_827/status/1824342149481005377
useEffect内部で直にsetStateを呼んでるのはほぼ間違いなく他に良い書き方があるけど、useEffectの中で「setStateを呼ぶ関数を登録する」は問題無いんだな ちょっと躓きやすいポイントかもしれない
sub_827さんが言及してくださったように、「setStateを呼ぶ関数をuseEffect内で登録する」場合には問題は生じません。
例えば、次のようなコードを考えてみました。これはウィンドウのリサイズ時にコンポーネントの幅を状態(State)として保持しています。
import { useState, useEffect } from 'react';
export default function WindowWidthTracker() { const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return ( div pWindow width: {windowWidth}pxp div ); }
handleResize
は内部でsetStateを使っています。そしてそのhandleResize
は、addEventLisner
メソッドへと登録しています。
第一引数として'resize'
イベントを渡しているため、画面サイズの変更というイベントがあったときに、handleResize
が呼び出されるようになっています。
実際の挙動をCodePenでも確認しましょう。以下のリンク先に進んで、画面が表示されたあとにウィンドウサイズを調整すると、画面の数値が変化します。
この書き方はコンポーネント設計として何も問題ありません。
Reactのドキュメントでは、次の部分が該当しそうです。
外部ストアへのサブスクライブ:そのエフェクトは不要かも – React
コンポーネントが React の状態の外にあるデータをサブスクライブ(subscribe, 購読)する必要があることがあります。データは、サードパーティ製のライブラリから来るかもしれませんし組み込みのブラウザ API から来るかもしれません。このデータは React の知らないところで変わる可能性があるため、コンポーネントが手動でサブスクライブする必要があります。これは例えば以下のように、よくエフェクトを使って行われます。
useSyncExternalStore の活用
上のコードでもぜんぜん問題ないのですが、更にコードを向上させる方法があります。
それは、Reactのドキュメントに書かれているように、useSyncExternalStoreを使うことです。
先程のコード使って、改善後のコードを紹介します。
import { useSyncExternalStore } from 'react';
export default function WindowWidthTracker() { const windowWidth = useSyncExternalStore(subscribe, getSnapshot);
return ( div pWindow width: {windowWidth}pxp div ); }
function subscribe(callback) { window.addEventListener('resize', callback); return () => window.removeEventListener('resize', callback); }
function getSnapshot() { return window.innerWidth; }
Reactの公式ドキュメントでは次の辺りのコードが参考になると思います。
ブラウザ API へのサブスクライブ:useSyncExternalStore – React
useSyncExternalStore を追加するもう 1 つの理由は、時間とともに変化する、ブラウザが公開する値にサブスクライブしたい場合です。
振る舞いとしては改善前のコードと同じです。こちらについてもCodePenを用意したのでぜひお試しください!
大きなメリットは useSyncExternalStore を使うと、useEffectの中でsetStateを使わずに同様の処理を実現できるため、**アンチパターンかどうかを疑わずに済む**ことです。
また、useSyncExternalStore を使うことでReactの状態の外にあるデータを参照しているんだな〜ということが一目で分かるようになります。これも嬉しいです。
まとめ
setStateを呼ぶ関数をuseEffect内で登録する場合は、積極的にuseSyncExternalStore
を活用しよう!
概要
ブラウザ上で簡単に動くAudio Visualizerをリリースしました!
ソフトウェアをインストールする必要がなく、すぐに使えるのが特徴です。
こちらのリンク先からすぐに使えます。
https://lef237.github.io/audio-visualizer/
OGPを設定していませんが、そのうち設定したいと思います。
見た目
見た目はこんな感じです。
Chillな雰囲気を出すために、青と黒を基調にしました。
Audio Visualizerで音楽を再生しているところ
使い方
使い方はシンプルです。
Select Audio File
ボタンをクリックして、ビジュアライズしたい音声ファイルを選択します。mp3にもwavにも対応しています。
あとはStartボタンを押すだけでOKです。青い波が画面に広がります。
ボリュームも調整できますし、スマホにも対応しています。
リポジトリ
GitHubで公開しているので、コードが気になった方はぜひチェックしてみてください💻
https://github.com/lef237/audio-visualizer
GitHub Actionsを使ってGitHub Pagesへとデプロイしています。GitHubで完結するので楽でした。
動画
動画も載せておきます。
コード例
このコード部分は、オーディオデータの波形をキャンバスに描画しています。
const bufferLength = analyserRef.current.frequencyBinCount; const sliceWidth = (canvas.width * 1.0) / bufferLength; let x = 0;
for (let i = 0; i < bufferLength; i++) { const v = dataArrayRef.current[i] / 128.0; const y = (v * canvas.height) / 2;
if (i === 0) { canvasCtx.moveTo(x, y); } else { canvasCtx.lineTo(x, y); } x += sliceWidth; }
周波数ビンの数を取得して、ビンの幅を計算して、キャンバス上のy座標を出して、線を引くだけでOKです。
https://www.analog.com/jp/resources/glossary/frequency_bin.html
これらを簡単に実現できたのは、AnalyserNode
インターフェイスのおかげでした。MDN Web Docsに詳しい説明が載っているのでオススメです!
https://developer.mozilla.org/ja/docs/Web/API/AnalyserNode
AnalyserNode.frequencyBinCount 読取専用
符号なし long 型の値で、 FFT のサイズの半分の値。一般的に音声再生時の可視化に用いられます。
作った動機
3つあります。
- 音楽に関連するアプリを作りたかった
- ブラウザ上で気軽に使えるAudio Visualizerが見つからなかった
- 制限時間のあるなかでコードを書く訓練
音楽に関連するアプリを作りたかった
「LEF(筆者)は暇なときいつも何をしているっけ……?」と自分自身を観察したとき、ずっと音楽を聞いていることが分かりました。
また、(恥ずかしいので)公開はしていませんが、自分で作曲をすることもあります。
自分に身近なアプリを作りたいというモチベーションから開発を始めました。
ブラウザ上で気軽に使えるAudio Visualizerが見つからなかった
GoogleやBingで「Audio Visualizer」と検索しても、ブラウザ上で使えるものだと自分に合ったものが見つかりませんでした。
「無ければ作る!」というDIYの精神で作りました。
制限時間のあるなかでコードを書く訓練
土日という制限時間があるなかで開発することで、自分を鍛えようと思いました。
これは一人ハッカソンというもので、前にも開催したことがありますが、久々に開催することにしました。*1
一人ハッカソンのmyルールとしては「時間内に作り切る」というものがあります。作りかけで放置するよりも、最後まで作り切ることを目標にしました。
そのため、例えば色を変えるとか、波形の描画ロジックを変えるだとかは実装していないです。あえてシンプルにして、無駄を削ぎ落としました。もし必要になったらそのときに作れるようにしています。
まとめ
日曜大工をする感覚で、アプリやツールを作ると楽しいです。
暇なときに一人ハッカソン、オススメです!🧑💻
概要
Windowsの一部のキーはPowerToysやAutoHotKeyではうまくリマップできないときがあります。そのため、Ctrlが押しっぱなしになってしまう事象が発生します。
この問題を回避するためには、レジストリ内の値を変更し、OSレベルでキーを変更する必要があります。
その変更を容易にしたツールがChange Key。これを使うのが一番良さそう。
感想
自分への備忘録として書き残しました。
この原因に辿り着くのにかなり時間が掛かりました(初めは固定キーの問題かと思っていました)。
PowerToys内のツールでもうまくいかないので注意が必要です。
macOSだとKarabiner-Elementsを使うのが王道ですが、WindowsだとChange Keyを使うのが一番良さそうです。
Change Keyの使う上でのちょっとした注意
Change Keyの使い方ガイド | キーボードのキー割当を変更できるソフト - PCデスク周り・ガジェットのレビュー、リモートワーク情報 | リモライフ
LZHファイル形式でダウンロードされるので、Lhaplusあたりの定番ソフトで解凍しましょう。
Change Keyを使わず自力でレジストリを書き換える場合
この記事が参考になると思います。
Windows でも CapsLock を Ctrl として使う
参考記事
AutoHotKey で正常に扱えないキーとその対策 #AutoHotkey - Qiita
AutoHotKey で正常に扱えないキーは (おそらく) 以下の 3 つです。
- 半角/全角
- CapsLock・英数
- カタカナ・ひらがな
共通点は以下の通りです。
- IME の機能が割り当てられている
- トグル系の機能を持つ
「Change Key」非常駐型でフリーのキー配置変更ソフト - 窓の杜
JavaScript の console.log()
は便利ですが、注意も必要です。
今回は、自分が最近遭遇したコードを元に、具体的な事例を紹介したいと思います。
事例: エスケープ文字 \t に気づかず === で比較
これは実際のコードを単純化したものです。このコードでは、二つの文字列が同じかどうかを比較しています。
if (str1 === str2) { console.log("同じ文字列です"); } else { console.log("異なる文字列です"); }
str1
と str2
は外部から渡された string
型の変数です(TypeScriptで開発していたため、型自体は把握していました)。
ログを見ると次のようになっていました。
異なる文字列です
ここで、 str1
と str2
の中身を確認します。
console.log(str1); console.log(str2);
出力結果は次のようになります。
Hello World Hello World
わずかに間隔の違いはありますが、すぐに気が付かないかもしれません。
実はそれぞれの変数には、次の文字列が代入されていました。
const str1 = "Hello\tWorld"; const str2 = "Hello World";
この場合、str1
にはエスケープ文字 \t
が含まれていますが、str2
では半角スペースが使われています。そのため、===
を使った比較では「異なる文字列です」となっていたのです。
このように、出力結果を見比べるだけでは違いがわかりにくい場合があります。
console.log()
ではエスケープ文字が表示されないため、どこが異なるのかは一目ではわかりません。
(もし半角スペースが2つだったら、更に分かりづらかったかもしれません……)
事例2:全く見分けがつかないケース
次のように改行文字 \n
が末尾にある場合は更に大変です。
const str1 = "Hello World\n"; const str2 = "Hello World";
出力結果は次のようになります。
Hello World Hello World
そう、全く見分けがつかなくなるのです。
試しにブラウザ上のコンソールで確認しましょう。次の画像のように、出力結果だけでは区別がつかないです。
対処法:console.log()とJSON.stringify()を組み合わせて使う
そこで、console.log()
と JSON.stringify()
を組み合わせて使うことで、エスケープ文字を含む文字列を視覚的に確認できます。
console.log(JSON.stringify(str1)); console.log(JSON.stringify(str2));
出力結果は次のようになります。
"Hello\tWorld" "Hello World"
JSON.stringify()
を使うことで、エスケープ文字も含めて文字列全体が出力されるため、違いを明確に確認することができます。
まとめ
JavaScriptで文字列を出力する際には、エスケープ文字に注意を払うことが重要です。
console.log()
と JSON.stringify()
を組み合わせて使うことで、エスケープ文字を含む文字列を明確に表示できます。
特に文字列を取り扱うときは気をつけることをオススメします。
(もしかしたらもっと良い方法があるかもしれません。気軽にコメントして頂けると嬉しいです!)
補足(ブログを書いた動機)
例えばRubyだと、puts
メソッドを使ったときはエスケープ文字が表示されませんが、p
メソッドを使うとエスケープ文字が表示されます。
JavaScriptにもp
メソッドに相当するようなメソッドがないか調べていますが、今のところ見つけられていません……。