Perlで配列の先頭何要素か以外を抜き出す ― 2022年12月05日 01時47分
この記事はPerl Advent Calendar 2022の5日目の分です。
Perlで配列の先頭n要素以外を抜き出したい——例えば配列('a', 'b', 'c', 'd', 'e')
から先頭2要素以外を抜き出して配列('c', 'd', 'e')
を得たい——とき、最近はList::Util
モジュールのtail
関数を使えます。
tail
関数は配列の末尾n要素を抜き出す関数ですが、抜き出す要素数として負数-mを指定すると、先頭m要素以外の要素を返します。
use List::Util qw(tail);
my @array1 = qw(a b c d e);
my @array2 = tail -2, @array1;
# @array2の内容は('c', 'd', 'e')
List::Util
モジュールはコアモジュール(Perl本体と一緒にインストールされるモジュール)であり、Perl 5.28以降なら追加のモジュールインストールなしにtail
関数を使えます。それより古いPerlでは、List::Util
の新しいバージョン(1.50以降)をインストールする必要があります。
以前からある方法として、配列スライスを使うこともできます。
my @array1 = qw(a b c d e);
my @array2 = @array1[2 .. $#array1];
# @array2の内容は('c', 'd', 'e')
TypeScriptでイベントをPromise化する関数の型を定義したい ― 2022年12月03日 10時41分
この記事はTypeScript Advent Calendar 2022の3日目の分です。
「addEventListenerでリッスンしているイベントをPromise化する」という記事で、イベントをPromise
で受け取る関数が紹介されています。Node.jsのevents
モジュールのevents.once
メソッドと同じ機能を実現するものですね。Webブラウザ組み込みのDOMでも同じ機能を提供しようという提案もなされています。
最初の記事では余談として今回紹介したeventPromisifyはTypeScriptで書こうとすると型の定義が難しいなと思いました
と書かれています。例えばeventPromisify(document, 'click')
と呼び出したら返り値の型がPromise<MouseEvent>
になってほしいのですが、そのような型定義を記述できるでしょうか? (以下、TypeScript 4.9を想定しています。)
イベントが発生する対象(target
)とイベント名(type
)を決め打ちできるのなら、
type EventForDocumentClick =
typeof document.addEventListener<'click'> extends
(type: 'click', listener: (event: infer E) => void) => void
? E : never;
// = MouseEvent
のように、document
と'click'
からMouseEvent
を導出できます。しかし、変数document
ではなくDocument
型だけが与えられているとき、Document['addEventListener']<'click'>
のように型引数を指定することはできません。
またDocument
型におけるaddEventListener
メソッドの定義は、イベント名がDocument
固有(keyof DocumentEventMap
型)のものとイベント名が文字列全般(string
型)のものがオーバーロードされています。型引数が絡んでいなければ、「オーバーロードされた関数型から引数の型や返り値の型を取り出す方法」に書かれているように型を取り出せます。しかし、型引数を持つメソッドがオーバーロードされているときに、意図した型引数が指定された場合の引数や返り値の型を取り出せるのかどうか、私にはわかりませんでした。
結局私にできたのは以下の状態までです(TypeScript Playgroundで確認)。
type EventTypeFor<Target extends EventTarget> = Target['addEventListener'] extends {
(type: infer T, listener: (e: Event) => void): void;
(type: string, listener: EventListenerOrEventListenerObject): void;
} ? T : never;
type EventFor<Target extends EventTarget> = Target['addEventListener'] extends {
(type: string, listener: (e: infer E) => void): void;
(type: string, listener: EventListenerOrEventListenerObject): void;
} ? E : never;
const eventPromisify = <T extends EventTarget>(
target: T,
type: EventTypeFor<T>
): Promise<EventFor<T>> => {
throw new Error('Not implemented');
}
const p = eventPromisify(document, 'click');
// p: Promise<Event | MouseEvent | UIEvent | ClipboardEvent | AnimationEvent | InputEvent | FocusEvent | ... 11 more ... | WheelEvent>
第2引数をDocument
固有のイベント名に限定することはできています。しかしながら、返り値のPromise
の値の型をMouseEvent
に限定することはできず、Document
固有のイベントすべての共用体型となってしまいます。
Perlで配列の先頭何要素かを抜き出す ― 2022年12月02日 02時16分
この記事はPerl Advent Calendar 2022の2日目の分です。
Perlで配列の先頭n要素を抜き出したいとき、最近はList::Util
モジュールのhead
関数を使えます。
use List::Util qw(head);
my @array1 = qw(a b c d e);
my @array2 = head 3, @array1;
# @array2の内容は('a', 'b', 'c')
List::Util
モジュールはコアモジュール(Perl本体と一緒にインストールされるモジュール)であり、Perl 5.28以降なら追加のモジュールインストールなしにhead
関数を使えます。それより古いPerlでは、List::Util
の新しいバージョン(1.50以降)をインストールする必要があります。
以前からある方法
配列スライスを使うこともできますが、抜き出す要素数から1引いた値を指定することになって、ちょっと紛らわしいです。
my @array1 = qw(a b c d e);
my @array2 = @array1[0 .. 2];
# @array2の内容は('a', 'b', 'c')
また、元の配列の要素数が抜き出す要素数より少ないときは、不足分がundef
で埋められてしまいます。
my @array1 = qw(a);
my @array2 = @array1[0 .. 2];
# @array2の内容は('a', undef, undef)
undef
で埋められたくなければ、min
関数を使うなどひと工夫する必要があります。
use List::Util qw(min);
my @array1 = qw(a);
my @array2 = @array1[0 .. min(2, $#array1)];
# @array2の内容は('a')
splice
関数を使うこともできますが、元の配列も変更されてしまいます。
my @array1 = qw(a b c d e);
my @array2 = splice @array1, 0, 3;
# @array1の内容は('d', 'e')
# @array2の内容は('a', 'b', 'c')
CSSでモーダルダイアログの背景をスクロールさせないようにできるかもしれない ― 2022年12月01日 01時41分
この記事はCSS Advent Calendar 2022の1日目の分です。
HTMLのdialog
要素を使うとモーダルダイアログを表現できます(使い方によってはモードレスダイアログも表現できます)。ただし、そのままだとモーダルダイアログを開いているときに、マウスホイールなどによってダイアログの背景(文書全体)までスクロールしてしまいます。
モーダルダイアログの背景をスクロールさせたくない場合、これを書いている現在のCSS仕様草案によれば、以下の記述で実現できるはずです(デモ)。
dialog {
overscroll-behavior: contain;
}
しかしながら、この方法はChrome canary 110では期待通り動作しますがマウスホイールによるスクロールは防げますが、矢印キーやPageUp/PageDownキーによるスクロールは防げず、Firefox nightly 109では動作しません。
このあたりの事情はちょっと複雑で、
- CSS仕様草案によれば、
overflow: auto
な要素においてはスクロール可能な領域がないときもoverscroll-behavior
プロパティが適用されるはず。- CSS Overscroll Behaviorモジュール草案によれば、スクロールコンテナに対して
overscroll-behavior
プロパティが適用される。 - CSS Overflowモジュール草案によれば、
overflow: auto
な要素(およびoverflow: hidden
な要素)はスクロール可能な領域がないときもスクロールコンテナである。
- CSS Overscroll Behaviorモジュール草案によれば、スクロールコンテナに対して
- 各ブラウザ(少なくともChromeの場合とFirefoxの場合)は、あえて仕様草案を無視し、スクロール可能な領域がないときに
overscroll-behavior
プロパティを適用しない。- しかし、Chromeはなぜか、
dialog
要素においてはスクロール可能な領域がないときもマウスホイールによるスクロールに関してはoverscroll-behavior
プロパティを適用する模様(そうなっている経緯は未調査)。
- しかし、Chromeはなぜか、
- CSS仕様草案に対して、スクロール可能な領域がないときは
overscroll-behavior
プロパティが適用されないようにしよう(仕様側を現状のブラウザ実装に合わせよう)という提案がなされている。
となっています。上述のCSSコードが将来的にも機能するかどうかは不透明です。
overflow: hidden
を使ってスクロールを防止する方法でも、:has
疑似クラスと組み合わせることで、CSSのみでモーダルダイアログの背景のスクロールを防止できるそうです。
HTMLのa要素にはhref属性を指定しなくてもよい ― 2022年10月20日 23時30分
HTMLのa
要素はハイパーリンクを表す要素であり、リンク先のURLをhref
属性に指定します。しかし、a
要素の役割はそれだけではありません。HTML標準によれば、a
要素は「リンクとなりうる箇所のプレースホルダー」として使うこともできます。この場合はhref
属性を指定しません。
リンクとなりうる箇所の例として、ナビゲーションやタブUI、パンくずリストなどでの「現在の項目」があります。
<nav>
<ul>
<li><a href="/">ホーム</a></li>
<li><a>最新記事</a></li>
<li><a href="/archives">アーカイブ</a></li>
<li><a href="/settings">設定</a></li>
</ul>
</nav>
ReactなどJSXでa
要素を生成する場合、href
属性を指定しないためにはhref
プロパティにundefined
を指定します。
import React from "react";
type Item = {
label: string;
url: string;
isCurrent: boolean;
};
type Props = {
items: readonly Item[];
};
const Navigation: React.FC<Props> = ({ items }) => (
<nav>
<ul>
{items.map((item) => (
<li>
<a href={item.isCurrent ? undefined : item.url}>
{item.label}
</a>
</li>
))}
</ul>
</nav>
);
リンクのプレースホルダーとしてのa
要素は、うまく使えばテンプレートやCSSの記述を簡潔にできます。覚えておいて損はないでしょう。
なお、a
要素にhref
属性を指定しないと聞いてname
属性を指定するのかと思った人もいるでしょうが、現在のHTML標準ではa
要素のname
属性は廃止済みであり指定すべきでないとされています。
最近のコメント