銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

Gitkの文字化けはAnacondaのせいだった

f:id:takamints:20191203150146p:plain

Gitのコミットログをグラフィカルに見せてくれるGitkですが、いつのころからか、Arch Linux の端末で実行すると、ウィンドウが開くまで数分かかり、さらに日本語が文字化けしてるという状態になっていました。

メインマシンではなかったので、見て見ぬふりを続けてきましたが、先月メモリを16Gバイトに4倍増して、機械学習のお勉強用などとしても使う機会が増えてくると「さすがにこれ以上は放置できない看過できないナントカセネバ!」とイライラしはじめ鼻息荒く調べてみると、割とあっさり「どうやらGitよりも先にインストールしていたAnacondaのせいだったのね」と判明しました。

まあAnacondaがすべて悪いかというと、そうも言いきれないのですが・・・とにかく以下の方法でGitkの文字化けは解消しました。

目次

Gitkの文字が化け化けだ

f:id:takamints:20191203124752p:plain

Gitリポジトリのコミットログじゃなくてウィンドウ自体の日本語が化けてるんですよね。

\u… なんて表示されているのはUnicode文字コードでしょう。 フォントが見つからないのかも。

2つのWishが混在するのが原因でした

GitkはWishを使います

Gitkは wish というコマンドを使っているそうです。

wishはtkパッケージに含まれるコマンドで /usr/bin/wish に配置されるTcl/Tkのインタプリタ。 対話的に画面を作れるツールですね。

ArchLinux公式のGitの説明では、GitkやGitGuiなどのGuiツールを使うにはこの tk パッケージが必要だと書いてあります。

AnacondaもWishを使います

Anacondaもwishを使っているそうです。 しかもAnacondaは、ユーザーのホームディレクトリ以下に独自?の wish をセットアップし、かつ /usr/bin のwish よりも独自のwishが優先的に実行されるように .bashrc を自動的に書き換えてしまいます(↓)。

# added by Anaconda3 Installer
export PATH="/home/<user>/anaconda3/bin:$PATH"

/usr/bin は $PATHに含まれているので、それより先にAnacondaのbinのwishが呼ばれてしまいます(<user>はユーザー名に読み替えてください)。

ワタシはそもそもtkをインストールしていませんでしたが、それでもGitkがなんとか起動していたのは、Anacondaがセットアップしたwishが呼ばれていたからなんですね。

対策は?

tkをインストールする

tkをインストールするには、以下のようにすればOKですが、

$ sudo pacman -S tk

.bashrcで/usr/bin/wishが優先されるように設定書き換え

というとで、.bashrcを以下のように書き換えて、 /usr/bin/wish が優先されるようにします。

# added by Anaconda3 Installer
export PATH="$PATH:/<user>/anaconda3/bin"

動作確認

ターミナルを立ち上げなおして、ちゃんと設定できたか確認しましょう。

まずは、 /usr/bin/ の wish が呼ばれるかどうか。

$ which wish
/usr/bin/wish

そしてGitk。

$ gitk --all&

↓即座に起動&文字化け解消!

f:id:takamints:20191203124220p:plain

まとめると

以下の対策で Gitkが即座に起動するようになり、文字化けも解消しました。

  • tkパッケージをインストール
  • Anacondaのwishではなく /usr/bin/wish が呼ばれるように .bashrc を設定

参考サイト

ja.stackoverflow.com

JavaScriptで文字列のバイト数を得るにはどーする?

f:id:takamints:20191123221032j:plain
photo credit: wuestenigel White tape measure via photopin (license)

ブラウザのJavaScriptとNode.jsで文字列のバイト長を知る方法です。文字数ではありません。 それぞれの環境でやり方が違いますが、統一コードも紹介してます。



目次

はじめに

JavaScriptで文字列のバイトサイズを取得する方法」を検索すると「URLエンコードして文字数を数える」って方法が検索結果の上位にたくさん出てきます。 しかし「こうすればできる」って感じがちょっとアヤシイ。

たいていはNode.jsに言及していないため、

「割と古い方法なのかな?最新式のJavaScriptではもっとスッキリした方法があるのでは?」

と思いまして「ブラウザのJavaScriptとNode.jsの両方の環境で、文字列のバイト長を求める方法」を調べてみました。

結果として、納得できるスッキリした方法がありましたけど、両環境では別方法。 まあ、どちらでも同じやり方が可能なモジュールもありましたので、それも含めて以降で説明しています。

ブラウザでは Blob を使う

ブラウザのJavaScriptでは (new Blob(["文字列"])).size で文字列のバイトサイズが得られます。

少し直感的でないので、関数として書けば以下のようになりますね。

/**
 * 文字列のバイト長を得る(ブラウザ版)。
 * 文字数ではありません。
 * @param {string} s 文字列
 * @returns {number} 入力文字列のバイト長
 */
function byteLengthOf(s) {
    return (new Blob([s])).size;
}

Blobはブラウザでバイナリデータを扱うためのクラスです詳細は以下。 上で使っているのはコンストラクタとsizeプロパティだけですね。

developer.mozilla.org

Node.js では Buffer クラスを使います。

Node.jsなら、Buffer.byteLength("文字列") でOKらしい。こちらはかなり直感的です。

/**
 * 文字列のバイト長を得る(Node.js版)。
 * @param {string} s 文字列
 * @returns {number} 入力文字列のバイト長
 */
function byteLengthOf(s) {
    return Buffer.byteLength(s);
}

nodejs.org

実行時に切り替えるには?

実行時に動作環境がブラウザであるのかNode.jsであるのかを調べて切り替えるには、以下のようにすればよろしいです。

/**
 * 文字列のバイト長を得る。
 * @param {string} s 文字列
 * @returns {number} 入力文字列のバイト長
 */
const byteLengthOf =
    ("window" in (Function("return this;")()) ?
        s => (new Blob([s])).size : // ブラウザ版
        s => Buffer.byteLength(s)); // Node.js版

npm buffer で統一できる

と、ここまで書いて気になって、追加で調べてみたのですが、 Node.jsのBufferクラスをブラウザのJavaScriptで使えるようにした buffer というnpmモジュールがありました。 最初に調べるべきだったかもしれない。

www.npmjs.com

Bundlerが使える場合

WebPackやParcelなどのBundlerを使っているなら、以下のようにrequireで読み込めば(パスにちょっと注意)、 ブラウザのJavaScriptでも Node.js版のコードが使えます。そしてNode.jsでも動作しました。

const Buffer = require('buffer/').Buffer  // 注意:最後のスラッシュは重要!
/**
 * 文字列のバイト長を得る。
 * @param {string} s 文字列
 * @returns {number} 入力文字列のバイト長
 */
function byteLengthOf(s) {
    return Buffer.byteLength(s);
}

Bundlerが使えない場合

Bundlerを使えない場合は、ビルド済みスクリプト( https://bundle.run/buffer )をHTMLのSCRIPTタグで読み込めば Buffer クラスが使えるようになるらしい(こちらに関しては未検証です)。

<script src="https://bundle.run/buffer"></script>
/**
 * 文字列のバイト長を得る。
 * @param {string} s 文字列
 * @returns {number} 入力文字列のバイト長
 */
function byteLengthOf(s) {
    return Buffer.byteLength(s);
}

あとがき

検索でトップに出てくる情報が完全に正しい情報だとは限らないと思いました。 この場合は、完全な間違いとも言い切れませんが。

JavaScriptは既にかなりな歴史が積み重なっていて、徐々に仕様が整備されてきたので、どうしても古い情報が残りがちですね。 これから始めようとする人たちにとっては弊害となることがありそうな気もします。

このような点からも、最近はPythonが流行っているということもあるのかなと(知らんけど)

このような心配とはまた別に、JavaScriptではnpmとかBundlerの類はストレスなく使えるようになっておいたほうが良さそうですね。 モダンJavaScriptバンザイです。いくつになってもお勉強です。

祝!MZ-700シリーズ発売37周年っ!に合わせてMZ-700フルJavaScriptエミュレータをWebWorkerでレンダリング!

f:id:takamints:20191115205104p:plain
1986 Tiny XEVIOUS namco (拙作「MZ-700フルJavaScriptエミュレーター」で撮りました)



今年もやってきました11月15日の祝日が(えっ?) SHARP MZ-700シリーズの発売日ですよ。ちょうど37年目のお祝いです。 発売日は 1982年11月15日。 当時ワタシはおそらく中学2年生(←昔過ぎて正確な年齢が算出できない)。 そら50歳にもなるわけだ(笑)

昨年のお祝いエントリーはコチラ↓です。

takamints.hatenablog.jp

MZ-700との出会いはたまたまでした。 1982年の年末に、機械関係、メカトロ関係のエンジニアだった父が買ってきたんですね。 プロッタプリンタのついていたMZ-731というモデルでした。 仕事で使うつもりだったみたいですが。

それから数年、兄とともに濃厚濃密な年月を過ごしました。 最初にBASICを覚えてから、機械語ってのが速いらしいと小耳にはさんでZ80のハンドアセンブルにいそしみました。 市販のゲームもやるっちゃあやるんですが、プログラムを作れば作った通りに動くってこと自体が楽しくて、Oh!MZを筆頭に、I/O、マイコンベーマガ読んで、あーだこーだとやっていました。

しかしそのうちに、文字しか表示できないMZ-700に引け目を感じるようになってしまい、X-1が良かったなーなんて思いながら一念発起、MZ-700の後継機種として発売されたMZ-1500を入手しまして、徐々に気持ちがMZ-700から離れていきました。ちなみにこの時MZ-1500に同梱されてたPSG6音を鳴らせるピアノロールみたいなソフトを使って和音の面白さにめざめて、その後の音楽趣味につながります。9割9分打ち込みだけど、Jazzコード理論(初歩)は独学。

Tiny XEVIOUS for 700 の衝撃

そのころは、世間的にも「MZ-700はオワコン」的な雰囲気になっていたと思うのですが、Oh!MZに唐突に「Tiny XEVIOUS for MZ-700」 という目も耳もなんなら鼻や口すら疑うような記事が載り、ワタシのあごは落ちました。ガクー

※ Tinyという単語はTiny XEVIOUS以前にも雑誌掲載のゲームなどでよく使われていた印象がありますが、このTinyは確かにTinyなんだけどさ、まったくTinyではありませんよね(?)。

これは「MZ-700に不可能はない!」との名言が生まれるきっかけとなったであろう古旗一浩さん作(というか移植)のゲームですけど、実は自分は当時これを打ち込んでもいなくて遊んでいません。

というのも、既にXEVIOUSはゲーセンでやり倒していたし、目の前にはMZ-1500があるしという状況。 「サンダーフォースやれるからもういいよ」的な気持ちだったなあ。

しかし、このとてつもないチャレンジ精神にあふれたほぼ同世代の古旗一浩という人の存在を知ったがために、「自分は薄っぺらくてダメなやつだ情けない」てな気持ちになってしまったのも影響していたように思います。

まるくんの MZ700Win

なんだかんだで、その後、制御系のソフト会社へもぐりこみましたが、既に時代は個人ユースでもNECPC-9801の独壇場。8ビットのPCなんて既に製造されていませんし、自分の中でMZはもう過去のものとなっていました。しかし、そこから10年、世間はインターネットとWindows。2000年ごろふと知った「まるくん」こと丸山武志さん作のWindowsMZ-700エミュレータ MZ-700Win。 当時VC++で仕事していたし、すげーなるほどこういうの作れるんだへぇーーーみたいな印象で、懐かしいゲームなどを再び楽しめて感動しました。

拙作 MZ-700フルJavaScriptエミュレータ

と、ここまで長くなりましたが、さらに10年後、世の中はHTML5Ajaxでできていて、ふと「MZ-700 のエミュレーションはJavaScriptで可能ではないだろうか?」と一念発起。

MZ-700って、発売当時は当たり前だったのかもしれませんが、Owner's Manualに回路図が載っているんですよね。 なので内蔵されてるICなどのデータシートを読み取って、それぞれJavaScriptエミュレータを書いていく。 当然Z80の命令もJavaScriptでコーディング。

この当時はWebWorkerの存在もほぼ知らず、タイマー駆動でCANVASに画面を描いていました。 最初は1枚のCANVASでなく40文字×25行のPNG画像で画面を構成してました。

今ではそれなりに最新機能も取り入れて高速化。 ずいぶん前からnpmとして提供しており、本日 v1.2.9 をリリースしました(追記:最新はnpmのGlobalインストール時の不具合を解消したv1.2.10)

node.jsを使える人は、npm install --global mz700-jsでグローバルインストールできます。コンソールからmz700-js` コマンドでWebAppが起動します。

takamin.github.io

mz700-js @1.2.9

OffscreenCanvasに対応しているブラウザでは、画面の描画はサブスレッド側で行われます

従来WorkerスレッドからのVRAM書き込み要求をメインスレッドに伝えて描画していましたが、ある程度キャッシュしないと速度低下につながりましたので、100msはキャッシュをしていました。 今回サブスレッドで描画するようにしましたが、これも逐一更新するとちらつきが多くなってむしろ低速。 バッファリングを10msに短縮すればなんとか納得いく速度を実現できました。

従来Chromeが速度と安定度でおすすめでしたが、相対的に低速だったEdgeやFirefoxで、今回それなりのパフォーマンスが出るようになっていて「おいおいChromeうかうかしてられへんぞー」という気分。とはいえやっぱりChromeの挙動が一番安定していて安心できるのも事実です。

描画方式を変えると、Tiny XEVIOUSのタイトル画面がビカビカしていました。

書き換えタイミングを変えたことでかなり高速に書き換えが発生しているようで、細かい動きの再現性が高まりました。実機の動きに近づいたかも?!

DynamoDB Query APIのパラメータを生成しましょ(プレースホルダをぶっ飛ばせ)

f:id:takamints:20191104181915j:plain
photo credit: Kurayba Micro Machine via photopin (license)

AWS のNoSQLデータベース DynamoDB のテーブルを Query する場合のパラメータを生成するWebアプリをつくりました。

DynamoDBでテーブルの項目を取りだすには Query API を使いますが、このパラメータがちょっと曲者なんですよね。

属性名が予約済みのキーワードにマッチしていると条件式などに直接記述できなくて「属性名のプレースホルダ」を定義して、読み替える必要があるのです。 さらに条件式では具体的な値(文字列や数値)が直接使えず「属性値のプレースホルダ」に変換しないといけません。

実際、慣れるとたいしたことではないのですけど最初はかなり悩みました。 今でも困ってる人はいるんじゃないかと思います。

DynamoDBはよくできたサービスですけど、これ(↑)って参入障壁かもしれない。 かつて悩んで解決したので、このようなソリューションを公開する責任があるのではないだろうか?と鼻息荒く意識は高く、以下に公開する次第ですー・・・


RDB技術者のためのNoSQLガイド
秀和システム (2017-05-09)
売り上げランキング: 45,590

使い方

少しUIがしょぼいですが、徐々に改善するつもりです(ゆるして)。

左にチェックボックスがついているのは省略可能なパラメータ。最初はチェックが外れていますがチェックをつけるとサンプルで入力した内容が復活します。

各テキストボックスを編集すると即座に結果のパラメータが変化します(エラーがあると変化しないので気を付けてください)。

属性名にDynamoDBのキーワードが含まれていると属性名のプレースホルダが生成されるのを確認してみてください。

生成されたパラメータはDynamoDBのQuery APIの第一引数にそのまま指定できるJSONです。[Copy]ボタンでクリップボードにコピーできまーす。

  • TableName は必須です。テーブル名を指定します。
  • KeyConditionExpression も必須で、テーブルのキーに対する抽出条件を指定します。キー以外の属性は使えません。パーティションキーには、= しか使えません。レンジキーには、範囲指定の演算子<,<=,=,>=,>, そして、BETWEEN <s> AND <e> が使えます。両者はAND条件で組み合わせます(ORは無理)。パーティションキーの条件は省略可能です。
  • FilterExpression は、KeyConditionExpressionで抽出した結果に対してフィルターをかける機能。DynamoDBではSCANした量によって課金されますからFilterで絞っても対象外です。でもFilterはAWS側で行われるので、インターネットの転送量は絞ったほうが少なくなります。まあややこしいところですが、そのうち理解できますよ(多分)。
  • Limit は抽出する行数を指定します。
  • ProjectionExpressionSQLのSELECT句。取り出したい属性値の名称をカンマ区切りで指定します。省略すれば属性値全体を取り出します。属性名がキーワードかどうかを気にする必要はありません。

関連サイト

npm aws-node-utilSQL的な構文でDynamoDBを操作する機能を提供していますが、この度こちらにこのページで使っているWebAppを公開してるのでよろしくね。

www.npmjs.com

takamin.github.io

AWS SAMテンプレートでREST APIの複数メソッドをひとつのLambdaに統合するには?

f:id:takamints:20191027090344j:plain photo credit: Andreas Komodromos Urban Nights - Hudson Yards, New York City via photopin (license)

ホントちょっとしたことなんですが、SAMのHelloWorldのチュートリアルをやっただけではハッキリわからなくて、少しだけ試行錯誤が必要でしたので書いておきます。

API Gateway REST APIの複数メソッド(GET,PUT,POST,DELETEなど)を、ひとつのLambda関数に統合(Lambdaプロキシ統合)する場合の AWS SAMのテンプレートの書き方です。

API Gatewayのコンソールでは、各メソッドの設定で、同じLambda関数を指定するだけですが、じゃあSAMのテンプレートではどう書くのでしょうか?ってところです。

目次

みんなのobniz入門
みんなのobniz入門
posted with amazlet at 19.10.27
古籏 一浩
リックテレコム (2019-11-08)
売り上げランキング: 24,382

SAMテンプレートの書き方(例)

以下のYamlでリソースパス /hello に対する GETリクエストとPUTリクエストを、Lambda関数 HelloWorldFunction に統合しています。

sam-app/template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        #ココ↓と
        HelloWorldGet:
          Type: Api
          Properties:
            Path: /hello
            Method: get
        #ココ↓
        HelloWorldPut:
          Type: Api
          Properties:
            Path: /hello
            Method: put

一般化すると、AWS::Serverless::FunctionProperties/Events以下にメソッド単位で別々のApiとして宣言すればよいのです。 APIのリソースパスが同じでもメソッドが別なら複数のApi定義が必要ということですね。 SAMテンプレートでは独立したREST APIを宣言しているのではなくて、あくまでもサーバーレス関数の定義の一部なのでこうなるのでしょう(多分)。

今となっては上の定義が理解できていますが、当初「Events 以下のエントリがAPIのリソースパスを一意に表している」と思い込んでいて、Methodを配列にしてみたりして失敗しました。

Lambdaプロキシ統合のパラメータで分岐する

Lambda関数側では、引数のeventのhttpMethod にHTTPメソッドが文字列で格納されていますので、これによって分岐します。

exports.lambdaHandler = async (event) => {
    try {
        const response = { statusCode: 200 };
        switch(event.httpMethod) {
        case "PUT":
            await put(JSON.parse(event.body));
            break;
        case "GET":
            await get(event.queryStringParameters);
            break;
        }
        return response;
    } catch (err) {
        console.log(err);
        return err;
    }
};

リクエストパラメータは、同じくeventのqueryStringParameterbodyに格納されています。 queryStringParameterはObjectですがbodyは文字列(JSON)なので気を付けましょう。 これらパラメータの使い方はメソッドの設計に依存するので一概に「こうしましょう」とは言えません。 他にも pathParametersheaders を利用することがあるかもしれませんね。

Lambdaプロキシ統合されたLambda関数の入力形式については、以下の公式ページに説明があります。 実際に動かしてみて把握する必要はあるかもしれませんよ。

docs.aws.amazon.com

あとがき

普通はメソッドごとにLambda関数を分けることが多いかもしれません。 そのほうがLambdaでの分岐が不要で多少はパフォーマンスが良いはずですし。

しかし、各メソッド間で同じデータモデルを扱うなど共通処理が必要な場合は一つにまとめるとスッキリすると思います。

共通処理についてはLambda Layersを使うのも一つの解です。 ここでは、「とりあえず手軽にやるには?」という観点でサクッと調べた結果を書いておきました。