TypeScript で string 型の値に自動補完を効かせる2021年09月11日 12時12分

結論

type X = 'foo' | 'bar' | (string & {});

のように、文字列リテラル型の共用体型に | (string & {}) を付け足した型 X を定義します。X 型は任意の文字列を受け付けますが、IDE (Visual Studio Code など) で X 型の値を入力するときには 'foo''bar' が自動補完の候補として提示されます。

解説

単純に type X = 'foo' | 'bar' | string; と書いてしまうと、共用体型の各要素がまとめられて、X は単なる string 型になってしまいます。{} 型は nullundefined を除く任意の値を受け付けるので、string & {} 型は実質的に string 型と同一なのですが、TypeScript 4.4 の時点では同一扱いされず、共用体型の各要素がまとめられることもありません。

この手法を知った経緯

React の型定義の変更履歴を見たところ、input 要素の type 属性の型定義を追加するという変更が入っていました。そこには見慣れない表記が交じっており、コメントには「| (string & {}) ハック」と書かれています。

input 要素の type 属性 (HTMLInputElement インターフェイスの type プロパティ) には、形式的には任意の文字列を指定できますが、実際に有効なのは textcheckboxradio など特定の値のみです。type プロパティの値を指定するときに、自動補完を使ってミスなく高速に入力したいというのは自然な考えでしょう。

しかし、type プロパティの型を string 型から文字列リテラル型の共用体型に変えてしまうと、これまで指定できていた値が指定できなくなったり、将来の input 要素の拡張に即座に対応できなくなったりと、互換性が失われてしまいます。型の互換性を維持しつつ IDE の入力支援を引き出すために、| (string & {}) ハックが生み出されたのだと思います。

TypeScript でモジュール内でのみ参照可能なグローバル変数を宣言する2021年03月21日 15時34分

Web ブラウザで使われる JavaScript ライブラリの中には、グローバル変数 (window オブジェクトのプロパティ) をはやすものがあります。Google Tag Manager の window.dataLayer や、Canva ボタンの window.Canva などです。

そうしたグローバル変数をあちこちのモジュールから直接操作していると保守性が下がってしまうので、ラッパーとなるモジュールを用意したいところです。グローバル変数を直接操作するのはラッパーモジュール内のみにとどめ、他のモジュールはラッパーモジュールを介して外部ライブラリにアクセスするという仕組みです。

このとき、ラッパーモジュール内でグローバル変数にアクセスしつつ、他のモジュールではそのグローバル変数にアクセスできないようにするには、どうしたらよいでしょうか。

TypeScript でのグローバル変数の宣言は以下のように declare global を使うのが基本ですが、これだと他のモジュールでも window.dataLayer にアクセスできてしまいます。

declare global {
  interface Window {
    dataLayer: object[];
  }
}

ラッパーモジュール内で以下のように window 変数を宣言しなおすことで、グローバル変数を参照できる範囲がラッパーモジュール内に限定されます。(「グローバル変数」といいつつ、window オブジェクトのプロパティとしてアクセスすることになりますが。)

declare const window: Window['window'] & {
  dataLayer: object[];
};

// このモジュール内では `window.dataLayer.push(...)` と書ける。

Window['window'] はもともとの window オブジェクトの型を指します。それとの交差型 (intersection types) を取ることで、window オブジェクトの既存のプロパティ (window.setTimeout など) にも、新たに宣言したプロパティにもアクセスできます。

Perl の wantarray 関数で返り値の扱いを確認する2020年12月22日 22時28分

こんにちは、nanto_vi です。この記事は Perl Advent Calendar の 22 日目です。


Perl の特徴のひとつに関数が多値を返せるというのがあります。Go 言語と同様に、処理の結果とエラーの値を同時に返すことができます。

sub do_something {
    ...

    return (undef, $error) if $something_wrong;

    return ($result, undef);
}

my ($result, $error) = do_something();

上述の do_something 関数はリストを返し、その第 1 要素に処理結果を、第 2 要素にエラーの値を含んでいます。ところが、ここでうっかり、

my $result = do_something();

とリストを使わずに返り値を受け取ってしまうと、変数 $result にエラーの値が代入されてしまいます。(リストをスカラコンテキストで評価した場合、そのリストの末尾要素が返ります。)

このような事態を防ぐために、wantarray 関数を使って呼び出し側がリストコンテキストで返り値を受け取っているか確かめられます。

use Carp qw(croak);

sub do_something {
    croak 'Must handle error' unless wantarray;

    ...

    return (undef, $error) if $something_wrong;

    return ($result, undef);
}

my $result = do_something();
# => 'Must handle error' 例外が投げられる。

ここでエラーの値だけを受け取りたいときは、次のようにリストの要素として undef を書けます。

my (undef, $error) = do_something();

空リストに代入することで、返り値をすべて無視することもできます。

() = do_something();

また、defined 関数と組み合わせて呼び出し側が返り値を受け取っているか確かめられます。

sub do_another_thing {
    croak 'Must handle error' unless defined wantarray;

    return $error if $something_wrong;

    return undef;
}

do_another_thing();
# => 'Must handle error' 例外が投げられる。

my $error = do_another_thing();
# => 例外が投げられず、エラーの値が返る。

この場合、返り値を無視するには scalar 関数が使えます。

scalar do_another_thing();

Perl の Test2::V0 でオブジェクトの基底クラスを確認する2020年12月18日 00時37分

こんにちは、nanto_vi です。この記事は Perl Advent Calendar の 18 日目です。


近年、Perl でテストの記述によく使われているモジュールが Test2::V0 です。Test2::V0 について詳しくは「第51回 Test2で変わるモダンなテスト―拡張性を持ったテスティングフレームワークとTest2::V0の使い方(1):Perl Hackers Hub|gihyo.jp … 技術評論社」を参照してください。

Test2::V0 を使い、あるオブジェクトがあるクラスのインスタンスであることを確かめたいときはどうすればよいでしょうか? 例えば、オブジェクトが URI クラスのインスタンスであることを確かめようとして、次のように書くと……

# Bad
use Test2::V0;
use URI;

my $uri = URI->new('https://www.example.com/');
is $uri, object {
    prop blessed => 'URI';
};

このテストは失敗します! prop blessed の値 (Scalar::Util::blessed($uri) の返り値) は URI::https であり、URI ではないからです。(URI::https クラスは URI クラスを継承しています。)

Test2::V0 0.000138 までは、次のように書く必要がありました。

# Good
is $uri, object {
    call [ isa => 'URI' ] => T;
};

$uri->isa('URI') の返り値が真ならよいというわけです。でもパッと見ただけでは何をやっているのかわかりづらいですよね。

Test2::V0 0.000139 (日本時間で 2020 年 12 月 16 日、つまりこれを書いている 2 日前のリリースです) では、次のように書けるようになりました!

# Good
is $uri, object {
    prop isa => 'URI';
};

prop isa を使うことで、オブジェクトの基底クラスの確認をわかりやすく書けます。また、check_isa 関数も追加されました。

これらの機能は Introduce object inehritance chekcs by nanto · Pull Request #230 · Test-More/Test2-Suite で導入されました。変更をレビューし取り込んでくださった Chad Granum 氏に感謝します。

Windowsでプロキシ自動設定ファイルを適用するPowerShellスクリプト2020年03月21日 22時23分

Windowsで一時的にプロキシ自動設定ファイル(プロキシ自動構成スクリプト、pacファイル)を利用したいとき、

  • プロキシ自動設定ファイルを配信するHTTPサーバーを立てなければいけない。(以前はローカルファイルが使えたが、Windows 10 Creators Updateから使えなくなった。)
  • Windowsの「プロキシ」設定画面で「スクリプトのアドレス」を指定しなければいけない。

という手間があります。

そこで、プロキシ自動設定ファイルを配信するHTTPサーバーを立ち上げ、そのURLをWindowsのプロキシ設定に指定するPowerShellスクリプトを書きました。

上記リンク先のスクリプトをローカルに保存し、Explorerでスクリプトファイルのコンテキストメニューから「PowerShell で実行」を選択すると、コンソールウィンドウが開き、スクリプト中に記述されたプロキシ自動設定が適用された状態になります。コンソールウィンドウ内でCtrl + Cを押下すれば、プロキシ自動設定が解除されます。

なお、プロキシ自動設定でSOCKSプロキシを使う場合、そのためのSSH接続は別途立ち上げておく必要があります。

既知の不具合

EdgeやInternet Explorerではプロキシ自動設定が期待通り適用されないことがあるようです。

プロキシ設定の変更をブラウザに通知する

プロキシ設定はレジストリに保存されていますが、レジストリの値を書き換えるだけではうまく機能しませんでした。どうもプロキシ設定が変更されたことをWebブラウザなどの各アプリケーションに伝えるために、Windows APIを呼び出す必要があるようです(スクリプト中のRefresh-Proxy-Settings関数の処理)。

もっと言うと、プロキシ設定の変更自体もレジストリを直接いじるのではなく、WinInetライブラリのInternetSetOption関数経由でやったほうがよいようです。ただし、そのためには構造体を定義するなど、PowerShellスクリプト中にC#のコードを書く必要があり、面倒になってやっていません。

Windows PowerShellの感想

今回初めてWindows PowerShellを書いたのですが、.NET Frameworkをそのまま使え、C#のコードを書け、Windows APIまで呼び出せるなど、何でもできる感じで感心しました。Taskオブジェクトを直接扱うことでC#のasync/await相当の処理を実現できるのも面白かったです。

もっとも、気をつけないと「PowerShellを書いていたつもりが、いつの間にかC#を書いていた」ということになりかねませんが(笑)