銀の弾丸

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

祝!SHARP MZ-700シリーズ発売36周年!な思い出話とエミュレータのお話です

f:id:takamints:20181115104532p:plain

自分にとっての最初のコンピュータ SHARP の 8ビットマイコン MZ-700 シリーズが 1982年11月15日に発売されて、今日でちょうど36年経ちました。 おめでたいので、当時からの思い出話などつらつら書いてお祝いしようと思います。

当時は、まさか36年後に、こんなお祝いしてるだなんて思ってもいませんでした。 当たり前ですけど当時はインターネットも携帯電話もハードディスクも普及していません。 ボクらにとってのメジャーな媒体はカセットテープでしたから!10分かけてゲームをロードしてたんだよー(笑) こんなに便利な世の中になったのは、みんなのおかげ。いろんなものに感謝・感激しておきたいです!おめでたい!

本日も、はてなブログで毎年MZ-700の発売記念をお祝いしていらっしゃるすぎもとさんのブログの記事を思わずTwitterでシェアしたら、たくさんの「いいね」をもらって、リツイートしていただきました。

なんだかすぎもとさんのふんどしで相撲を取ってるみたいで申し訳ない気分でもありますので、ご本人のツイートをRT。かのブログはすぎもとさんのブログですのでー。

なにより平成最後のこの秋に、MZ-700を覚えている人がたくさんいるってことが、うれしい・楽しい・素晴らしいですね!

レジェンドパソコンゲーム80年代記
佐々木潤 レトロPCゲーム愛好会
総合科学出版
売り上げランキング: 239,995

初めてのマイコンMZ-700との出会い

今から36年前。ワタシが中学二年の12月。父がMZ-731を買ってきました。プロッタプリンタがついている奴ですね。

父は仕事で使うために買ったのだとおもいますが、結局兄と私がほぼ占有することになっていました。

そんなこんなで自分にとって一番印象深い過去のマシンは未だにコレがトップです。

MZ-700のおかげ様

中学生の時にほぼ毎日プログラムリストとにらめっこしたおかげで、BASICやZ80アセンブリ言語を覚えました。

一時期マイコンからは離れていましたが、数年後、成人してからソフトウェア企業へもぐりこむときに役立ちました。

その時まで、まさかコンピュータのプログラムを書くことが仕事になるとか思っていなかったんですよね。自分にとっては遊びでしたから。

その後ソフトウェアのエンジニアとして大成功こそしていませんが、紆余曲折とかありつつも、それなりに幸せに、それなりに楽しく暮らせているのも、このマシンのおかげといって過言ではないと言い切りたい。

フルJavaScriptエミュレータ

ここ数年は、あくまでも趣味の範疇でブラウザで動くJavaScriptによるエミュレータを作って公開しております。 業務上JavaScriptを多用しており、最近のブラウザならZ80のエミュレーションとか平気でできるのではないだろか?と思ったのがきっかけでした。

とはいえ、そんなに簡単にはいかず。約1年半で「MZ-700のエミュレーションやってます」と胸を張って言える状態まで持っていって、つい3か月ほど前にv1.0をリリースした次第。

そのうち手を入れますのでゴメンナサイ

しかしこのあとまた放置。本業のほうがなかなかややこしい状況というのもあって、なかなか手が付けられません。

10月にリリースされた ChromeのVer.70で、「Audio API の使用はユーザー操作によって許可しないといけません」てな制限がかかるようになったのですが、これも手付かず。 ページを開いていきなり音が出るのを防ぐためなんですね。 この制限、4月か5月に一度リリースされてましたが上手く周知されていなかったようで世界中から猛反対に遭いGoogleさんが引っ込めちゃった。 Googleさんが引っ込める前に慌てて対策していたのですが、引っ込められて余計にややこしくなったので、こちらもロールバックしてしまい、そのコミットがどっかいっちゃいましたねえ。まいりました。

takamin.github.io

キャラグラの世界

先日、前述のすぎもとさんのブログで、キャラグラのMZTファイルが公開されていました(MZTはMZシリーズで読み込み可能なファイル形式です)。

30th700.hatenablog.com

せっかくなので、これをダウンロードして拙エミュレータで実行してみましたが、動きません。

メモリアクセスの関連でエラーになっていましたので、こちら側の不具合ですね。

この良き日にバグを見つけて残念ですけど、今から泣きながらデバッグしようと思います。

てな感じで普段の記事とは雰囲気が違っていますが、自分にとって、とっても大切な日でしたよってことで、それでは!

リンク

30th700.hatenablog.com

30th700.hatenablog.com

30th700.hatenablog.com

takamin.github.io

非同期処理の直列化:今やArray.reduceを使わなくてもできますよね

f:id:takamints:20181114220424j:plain
photo credit: hans-johnson 700-7000 Series_1 via photopin (license)

非同期処理の直列化とは「複数の非同期処理を、順番に実行する処理」のことです。非同期処理の順次実行や逐次実行とも呼ばれます。 処理速度は、並列処理よりも遅いのですが、処理順が重要であったり、先に実行した非同期処理の結果を次の非同期処理に使用する場合に必要になります。

JavaScriptでの非同期処理の直列化のやり方を検索すると「Array.reduce を使えばできる」とよく出てきます。

しかしアレ、そんなに便利ですかね?

コードが直感的じゃないし、ぱっと見ナニやってるのか分かりにくい。処理効率もそれほど良くなさそうです。

なんかもう「他のやり方ではできないよ」って誤解しそうな勢いですが、そうじゃない。 ES2017(ES8)以降のJavaScriptならもっとシンプルに書けますよね。

確かに「目的外使用」的で、軽く目からうろこが落ちて、「なるほどイイね!」と思う気持ちは理解できるのですが、それよか「誰にとっても直感的でわかりやすいコードを書く」ほうが重要ではないかと思ってます。

実際MDNでも、 reduceを使った直列化のコード が紹介されています。でも「こうすればできる」は「こうするべき」ではないですよね。

Vue.js入門 基礎から実践アプリケーション開発まで
川口 和也 喜多 啓介 野田 陽平 手島 拓也 片山 真也
技術評論社
売り上げランキング: 1,741

reduce使わずどうするか

じゃあreduce使わずにどうするの?っていうと、単純にfor ループで async / await を使えば普通に書けますfor~of 文ならもっとラク。reduce よりも明らかに簡潔です。

async/awaitが使えないならreduceを使うしかなさそうですが、そんなレガシーな環境で動かしたいなら Babel で変換すればよろしいのです。

※ ちなみに、Array.forEachでループを回すと並列処理になってしまいますので使えません。

for~of で非同期処理を直列化するコード

以下、ちょっと長くなっていますが、MDNのreduceのページで紹介されているreduceを使った直列化のコードfor~of を使った同等の処理を追加しました。

/**
 * for~of を使ってPromise配列をチェインしながら実行する。
 * (下のreduceバージョンと比べて如何に簡潔か確認)
 *
 * @async
 * @param {array} arr - Promise配列
 * @return {Object} Promiseオブジェクトを返す
 */
async function runPromiseInSequenseByForOf(arr) {
  let res;
  for(const currentPromise of arr) {
      res = await currentPromise(res);
  }
  return res;
}

/**
 * reduce を使ってPromise配列をチェインしながら実行する。
 *
 * @param {array} arr - Promise配列
 * @return {Object} Promiseオブジェクトを返す
 */
function runPromiseInSequense(arr) {
  return arr.reduce((promiseChain, currentPromise) => {
    return promiseChain.then((chainedResult) => {
      return currentPromise(chainedResult)
        .then((res) => res)
    })
  }, Promise.resolve());
}

// promise function 1
function p1() {
  return new Promise((resolve, reject) => {
    resolve(5);
  });
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// promise function 3
function p3(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 3);
  });
}

const promiseArr = [p1, p2, p3];

// Reduceバージョンで直列化
runPromiseInSequense(promiseArr)
  .then((res) => {
    console.log(res);   // 30
  });

// For~of バージョンで直列化
runPromiseInSequenseByForOf(promiseArr)
  .then((res) => {
    console.log(res);   // 30(結果は同じ)
  });

これ見て、どっちが良いかって考えると、どう考えても for~of のほうが良いと思うんですよね。

ただ、関数型プログラミングに傾倒している人にとってはletで宣言した変数resを逐次更新するところが気持ち悪いのかもしれません。

C#のActionクラスとFuncクラスを理解する

f:id:takamints:20181112124832j:plain
photo credit: ARMLE Action ! via photopin (license)

C#でメソッドや関数を表す 2つのクラス Action と Func について説明します (全くの余談ですが、C言語的には関数ポインタ、JavaScript的にはFunctionオブジェクトに相当するものです)。

2つの違いは戻り値の有無。戻り値なしが Action で、戻り値ありが Func です。

どちらもジェネリックパラメータとして関数のパラメータリストを指定できるジェネリッククラスです。 ジェネリックパラメータを指定しない場合は引数無しの関数を表すことになります。

以降、それぞれの詳細な使い方について説明します。

※ この記事は、もともと「C#のラムダ式はAction・Funcと一緒に理解を深めるとヨロシイようで」で書いていた内容です。長ったらしいのでこちらに独立させました。

目次

Action クラス

ACTION
ACTION
posted with amazlet at 16.09.22
B’z
VERMILLION RECORDS(J)(M) (2007-12-05)
売り上げランキング: 22,294

Actionクラスは「戻り値のない処理を記述するためのクラス」です。

Actionだけなら引数もなし。

// 引数無しのActionをラムダ式で生成
Action action = () => {
    foo.bar();
};

引数が必要ならAction<引数の型リスト>を使用します。

例えば、

//引数を取るActionをラムダ式で生成
Action<string, int> action = (name, age) => {
    Console.WriteLine(string.Format(
        "{0} is {1} years old.", name, age));
};

てな感じです。

このように、インスタンスを作ること自体は難しいことではありませんが、誰かが作った既存のメソッドがActionクラスの引数を要求している場合、この逆の考え方、つまり、そのメソッドの定義を見て「何を与えればよいのか」をきちんと理解する必要がありますね。

覚えること ☞「Actionクラスは戻り値のない関数。ジェネリックパラメータは引数リストの型を表しています。」

Func<TResult>クラス

グレイト・ヒッツ
グレイト・ヒッツ
posted with amazlet at 16.09.22
T.レックス
インペリアルレコード (2005-05-25)
売り上げランキング: 3,577

Funcは戻り値があるメソッドを表します。戻り値がboolで、引数のないFuncは、以下のように記述します。

//戻り値がboolのFuncをラムダで生成
Func<bool> func = () => {
    return true;
};

引数が必要な場合は、Actionと同じくジェネリックパラメータで型を指定します。

//戻り値がboolで引数付きのFuncをラムダで生成
Func<string, int, bool> isAround50 = (name, age) =>
    Console.WriteLine(string.Format(
        "{0} is {1} years old.", name, age));
    if(45 <= age && age < 55) {
        return true;
    }
    return false;
};

これがたとえば、メソッドのパラメータだとしても、考え方は同じで、以下のように書くわけです。

Task<bool> task = new Task<bool>(() =>
{
    return execute.Invoke();
});
chainedTask._task.Start();

Lambda式は、ActionかFuncクラスのインスタンスを生成していると考えられます。 このため、Lambda式だけを見て、どちらの型なのか推測するクセをつけておくと、混乱が少なくなる気がします。 ようするにしっかり理解しておきましょうということですが。

上の例では、Task<bool>のコンストラクタの第一引数の型は Func<bool> だと推測できますね。

そのほか無駄話など

Func<void>ではダメなんですか?

個人的な好みとしてですが、戻り値の有無でクラスを分けずに、Func<void>を認めて、Actionクラスはなくてよいでしょと思っています。 しかし、そもそもジェネリックの型パラメータにvoidは無理なのかもしれませんね。 VBでもFunctionとSubに分かれているし、マイクロソフトさんは昔から分けたい派なのかと思っていたけど、言語的制約なのかもしれません。

mocha を使った npm のユニットテストをブラウザで動かす設定

f:id:takamints:20181110123153j:plain
photo credit: wuestenigel White Cup with Coffee Grains via photopin (license)

mochaとchaiを使ったnpmのユニットテストをブラウザで動かすための設定をご紹介。

mochaにはブラウザで動作させる機能が備わっていますが、テストスクリプト以外にHTMLも用意しなくてはならないので少し億劫なんですよね。

実際には、ほぼ定型のHTMLなのですが、結構情報が少なくて「これで決まり!」みたいなのがない感じ。 コンソールとブラウザでスクリプトを共有したい場合に、問題が出て途方に暮れたこともありました。

ところが最近、ゆる~く試行錯誤を繰り返した結果、自分なりのテンプレートみたいなのが出来上がりつつあるので、ここに書いておきますよっと。

これとは逆に、WEBブラウザ向けESM(ESモジュール)のmochaのスクリプトをNode.jsでテストしたい場合の設定は、以下のページに書いています。

takamints.hatenablog.jp

テスト駆動開発
テスト駆動開発
posted with amazlet at 18.11.10
オーム社 (2017-11-13)
売り上げランキング: 7,425
目次

ブラウザでmochaのテストを行う為のHTML

以下はユニットテストブラウザーで動かすために必要なHTMLファイルです。

test/web-test.html

<!DOCTYPE html>
<html>
    <head>
        <title>npm run web-test</title>
        <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
    </head>
<body>
<h1>npm run web-test</h1>

<div id="mocha"></div>

<!-- mocha の読み込みとセットアップ -->
<script src="../node_modules/mocha/mocha.js"></script>
<script>
    mocha.setup('bdd');
    mocha.setup('tdd');
</script>

<!-- テストスクリプトを読み込む -->
<script src="./web-test.js"></script>

<!-- テストの実行 -->
<script>
    localStorage.debug = "*"; //npm debug向けのデバッグログの設定。
    mocha.run();
</script>

</body>
</html>

mochaの公式ページに掲載されているHTMLファイル(→RUNNING MOCHA IN THE BROWSER - mocha)をもとにしており、npm の開発環境にインストール(npm install --save-dev mocha chai )されているモジュールを利用するように変更しています。

元ファイルでは mocha と chai の外部スクリプトCDNから読み込んでいますが、mocha は node_modules 以下のファイルを読み込むようにして、chai はJS側で取り込みます(理由は次項で)。

debug モジュールのすゝめ

必須ではありませんが、上の例では見やすいログを出力する debug モジュールの設定をしています。 ブラウザではF12ツールのコンソールに色がついて見やすくなります。 WebではlocalStorage.debugに、出力したいロガーの名前をカンマ区切りで指定しておきますが、上の例では "*" としていますから、すべてのログが表示されます(mochaもdebugを利用しています)。

ブラウザで動作するmochaのテストスクリプト

chai は テストスクリプトで require する。

前述のように、元ファイルでは chai をSCRIPTタグで読み込んでいますが、自分的にはコンソールから実行するものと同じように、テストスクリプトで取り込むようにしています。 こうしておくことで、コンソールで実行するテストスクリプトのうち Node.js特有の機能を使っていない物をブラウザでも実行できるようになるからです。

しかし、これによってテストスクリプトをバンドラーでコンパイルしなくてはなりません。 これについては後述しますが、設定なしの Parcel を使えば簡単です。

ユーザー操作を待つテストの書き方

テストの実行時に、ボタンのクリックなどの操作を待つ必要がある場合は、その操作で解決する非同期関数を用意し、テストの中から呼び出しますようにします。 この場合、mochaのテストのタイムアウトを無効にしておく必要があります。

以下の例は、ボタンがクリックされてからテストが実行されます。

例)ユーザー操作を待つテストスクリプト

"use strict";
const assert = require("chai").assert;

/**
 * ボタンを作って押されたら解決するPromiseを返す。
 * Promiseは30秒後にリジェクトされる。
 * @returns {Promise} Promiseを返す。
 */
const buttonClicked = (() => (new Promise( (resolve, reject) => {
    const button = document.createElement("BUTTON");
    button.innerHTML = "Click to go";
    button.addEventListener("click", () => resolve() );
    document.body.appendChild(button);
    setTimeout(() => {
        document.body.removeChild(button);
        reject(new Error("The user operation timeout"));
    }, 30000);
})));

//テストターゲット
const foo = () => "bar";

//ユニットテスト
describe("foo", () => {
    it("should be bar", async () => {//非同期関数とします
        await buttonClicked();         //ボタンが押されるまで待つ関数

        assert.equal( foo(), "bar" );

    }).timeout(0);  //タイムアウト無効
});
  • 待ち時間が30秒を越えるとエラーを投入するのでテストは失敗します。
  • クリックするためのボタンを動的に追加していますが、あらかじめHTMLに書いておいても構いません。

npmのscriptsを設定する

テストに限らず、何らかのコマンドの実行は npm の scripts を使うと便利です。 node_modules 以下にあるモジュールのコマンドはパスの指定をせずに使えますから。

ここでは一例として、npm run web-test でブラウザでのテストを実行するよう設定し、 npm test または npm run test で通常のコンソールでのテストを実行するようにしています。 この辺はお好みに応じて変更してください。

package.json(一部):

"scripts": {
    "test": "mocha",
    "web-test": "parcel test/web-test.html --open",
    ・・・
    },

Parcel のすゝめ

ブラウザでのテストは、Parcel だけで 「バンドル」 → 「Webサーバー起動」 →「 ページを開く」 までを一気にやっています。 Parcelめっちゃ便利です。ブラウザーでのテストのためだけに使っても良いかも?って思っています。 BrowserifyやWeb-Packでは、いろいろ細かな設定が必要になるはずです。

ページを開いたときの実行時エラー 「regeneratorRuntime is not defined」を解消する

Parcelを使うとBabelも使うことになりますが、そのままだとブラウザでの実行時に以下のようなエラーが出ることがあります。

Uncaught ReferenceError: regeneratorRuntime is not defined
    at Suite.<anonymous> (test-script.js:38)
    at Object.create (mocha.js:720)
    at context.describe.context.context (mocha.js:532)
    at Suite.<anonymous> (test-script.js:7)
    at Object.create (mocha.js:720)
    at context.describe.context.context (mocha.js:532)
    at Suite.<anonymous> (test-script.js:6)
    at Object.create (mocha.js:720)
    at context.describe.context.context (mocha.js:532)
    at Object.parcelRequire.transworker.js.chai (test-script.js:5)

このエラーはBabelで変換対象となるような比較的新しいコードがあるときに発生するように思います。

解消するには、以下のような babel-polyfill だけをインポートするスクリプトを用意して、HTMLのSCRIPTで、テストスクリプトよりも前に読み込みます。ポリフィルはWebページから一度だけ参照すべきなので、このような対処になります。

test/babel-polyfill.js

require("babel-polyfill");

test/web-test.html

<!-- ポリフィルを読み込む -->
<script src="./babel-polyfill.js"></script>

<!-- テストスクリプトを読み込む -->
<script src="./web-test.js"></script>

コンソールでブラウザ向けテストを除外する

細かなことですが、コンソールでもテストを行っている場合は、 ブラウザ特有の機能を使っているテストスクリプトを除外してやる必要があります。

これは、mocha.opt というファイルで行います。 以下の例では、testディレクトリ内のファイル名が'web-'で始まるファイルを除外しています。

test/mocha.opt

test/!(web-)*.js

リンク

あなたが正しくウェブページのカーソル形状を変更するには

f:id:takamints:20181101110252j:plain
photo credit: Maria Eklind Frankie & Benjys bookstore and theatre Reggiano via photopin (license)

なにやら暑苦しいタイトルで失礼します。

ここにはウェブページのマウスカーソルの形状を変更するにはどうすればってことを書いてます。

「何を今さら」って感じですけど、先日、目からウロコが3つほど落ちましたので書いておきます。

小ネタといえば小ネタです。

目次

ページ全体のカーソル形状を変更する

ページ全体でマウスカーソルの形状を「待ち状態」にしたいことがありますね。 勝手に手が動くぐらいの勢いで以下のようにしていましたし、ググってみてもこのように説明されてるページが多いです。

でも、これ厳密には不完全なんですよ。

document.body.style.cursor = "wait";

何がダメかって言うと、WEBページのコンテンツが短い場合です。

コンテンツが少ないと、画面の下のほうにカーソル形状が変化しない領域が残ってしまうのです。

BODY要素(=document.body)はブラウザ画面の全体に広がっているとは限らないということです。 スタイルシートがどうなってるかにもよりますけどね。

で、これを回避するのは実に簡単。BODYじゃなくてHTMLのスタイルを変更します。

document.body.parentElement.style.cursor = "wait";

document.bodyの親要素はHTMLです。document.documentElement もHTML要素を指すそうですが、ブラウザ間の実装に違いがあるかもしれないので注意が必要。

以下のボタンを押すとHTMLのカーソル形状を変化させます。

コントロールは親要素のカーソル形状を継承しない

下のフォームのボタンを押すとBODYのカーソル形状をWaitカーソルに変えます(5秒後に元に戻ります)が、 各コントロールの上へカーソルを持って行っても形状は変化していません。 ChromeFirefoxではLABELのカーソル形状も変化しません(Edgeでは変化しました)。





既定の形状に戻すのはdefaultではない

一時的に変化させたカーソル形状を元に戻す場合、default に設定しなおしていましたが、これでは全てが矢印になっていました。

ほとんどの要素の既定の形状は矢印ですが、そうでないのもありまして。 例えばテキストボックス(<input type="text"/>)は縦線みたいなのが既定の形状。

const foo = getElementById("foo");
foo.style.cursor = "wait";
(長めの処理)
foo.style.cursor = "default"; //← 全部矢印になっちゃいます

といっても、すべての要素の既定のカーソル形状を覚えておいて、それぞれ個別に元に戻すなんてナンセンス。

じゃ、どうするか。defaultじゃなくautoにすればよいのです。

const foo = getElementById("foo");
foo.style.cursor = "wait";
(長めの処理)
foo.style.cursor = "auto"; // ← 要素ごとの既定のカーソルに戻ります。

ちなみに、最近のブラウザでは initial でも元に戻っているようです。 これはHTML5で規定されているのかもしれませんが、IE11では効きませんから、やっぱり auto でよいみたいです。

default / initial / auto でそれぞれ意味が違うのでしょうから、ややこしいですね。