TypeScriptやJavaでif式やtry式が欲しい時

概要

TypeScriptやJavaなど、 iftry が式ではなく文になっている言語でそれらを式として使う方法の一つとして即時実行関数式を紹介します。 また、専用のユーティリティ関数によるアプローチと比較します。

対象とする問題

TypeScriptやJavaでは iftry が文であるため、宣言的な記述が難しい場面があります。

// TypeScript (手続き的な記述)
let x;
if (a > 0) {
  x = a;
} else {
  x = -2 * a;
}
// Rust (宣言的な記述)
let x = if a > 0 { a } else { -2 * a };

この例は簡単すぎて条件演算子a > 0 ? a : -2*a )で実現できますが、条件が増えたりすると読みづらいと言われていますし、tryの場合はそのような文法はありません。

即時実行関数によるアプローチ

即時実行関数式を使えば、式になっていない制御構文を式として使用することができます。

// TypeScript
const x = (() => {
 if (a > 0) return a;
 return -2 * a;
})();

このままでは関数の宣言と読み間違えるリスクがあるので、次のようなユーティリティ関数を宣言することで意図を明確にできます。

// TypeScript
// Immediately Invoked Function Expression: 即時実行関数式
const iife = <T>(f: () => T): T => f();
const x = iife(() => {
  if (a > 0) return a;
  return -2 * a;
});

tryも式のように使えます。

// TypeScript
const y = iife(() => {
  try {
    return parse(s);
  } catch(error) {
    return '';
  }
});

専用のユーティリティ関数との比較

ユーティリティ関数を作るアプローチ

if 式や try 式が欲しいとき、次のようにそれぞれの専用ユーティリティ関数を作るというアプローチもあります。

// TypeScript
const x = if_(a > 0)
  .then(a)
  .else_(-2 * a);

const y =  try_(() => parse(s)).unwrap_or(-2 * a);

私自身もこのような実装は好きですし、下記のような利点があると思います。

  • 意図の理解しやすさと文字数の少なさの両立
  • かっこいい

これに対し即時実行関数式は平凡、野暮、冗長に見えますが、即時実行関数式にも明確で強力な次のメリットがあります。

機能が多い

iife 関数は単純ながら意外にも多機能です。

  • iftryswitch などに対応している
  • 遅延評価に対応している
  • スコープを作り、変数宣言ができる

などなど。単純に関数に備わっている機能が使用できます。

実装が単純明快である

iife の実装は関数を実行するだけであり、非常に理解しやすいです。

このことは地味に見えますが非常に強力で、バグったときに何が起きるのかを追うのが非常に簡単です。

ユーティリティ関数は一度作ればずっと使いまわせるのでそのような場面は少ないように思われますが、 iftryswitch それぞれに実装が必要になったり、それらに遅延評価を追加したくなって機能変更をする場面など、実装・デバッグが必要な場面は案外何度か発生してしまいます。

静的コード解析が適用されやすい

iife 関数は宣言された関数を実行するだけなので、人間だけでなく静的コード解析にとっても理解しやすくなっています。

例えば、JavaでOptionalな値をget()するコードパスでisPresent()をチェックしているかをコード解析ツールが追うことができます。

さらに、TypeScriptの場合は if の条件によるType Guardも有効になります。

まとめ

式志向で書きたい時、ついif式を実現するためのユーティリティを書きたくなってしまいますが、即時実行関数式によるアプローチも案外強力であると思います。

参考

mypyの型チェックは通るのに実行時に TypeError: 'method' object is not subscriptable が発生する場合

概要

ライブラリのクラスなどの型パラメータを指定するとmypyによる型チェックは通るものの実行時にエラーになってしまう場合があります。 その対処法と、このエラーが発生する理由について書きます。

from multiprocessing import Queue

q: Queue[int] = Queue()
$ mypy main.py
Success: no issues found in 1 source file

$ python main.py 
Traceback (most recent call last):
  File "xxx/main.py", line 3, in <module>
    q: Queue[int] = Queue()
TypeError: 'method' object is not subscriptable

対処法

Using classes that are generic in stubs but not at runtimeに書かれている通り、3種類の対応方法があります。

Python 3.7以上を使っている場合

from __future__ import annotations により実行時エラーが発生しなくなります。

from __future__ import annotations
from multiprocessing import Queue

q: Queue[int] = Queue()

文字列リテラルを使用

from multiprocessing import Queue

q: 'Queue[int]' = Queue()

型チェックとランタイムで切り替え

from multiprocessing import Queue
from typing import TYPE_CHECKING

if TYPE_CHECKING:
  IntQueue = Queue[int]
else:
  IntQueue = Queue

q: IntQueue = Queue()

実行時エラーが発生する理由

PEP 484 には、型ヒントについて次のように書かれています。

Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined

アノテーションは例外を発生させない式でなければなりません。

Queue[int] を式として評価したとき、 Queue (のメタクラス)が __getitem__ を実装していないためにエラーが発生します。 そのため、 TypeError: 'method' object is not subscriptable というエラーが発生します。

参考

ドラッグで要素を移動させる - React

導入

ドラッグで要素を移動させるような実装について、方法と注意点等を書きます。

デモをCodesandboxに置いています。

注意が必要な部分について先に箇条書きしておきます。

  • setPointCapture / releasePointCapture を使わないとスムーズな挙動にならない
  • ReactのSynteticEvent はイベントをプールするため、コールバックにプロパティを渡してはいけない
  • onPointerMove が高頻度に実行されるので、race conditionに注意する
  • スマホタブレットの場合
    • onMouseUp 等ではなく onPointerUp 等を使用する
    • スクロールのジェスチャーにイベントを取られないよう、 touch-action: none を使用する

実装

カスタムHookで実装しており、メインの実装はuseMoveOnDrag.tsです。

基本

ドラッグに対する挙動を実装したいので、 onPointerDown, onPointerMove, onPointerUp をうまく定義することを考えます。

const startDrag = (event) => { ... };
const dragging = (event) => { ... };
const endDrag = (event) => { ... };
...
return {
  onPointerDown: startDrag,
  onPointerMove: dragging,
  onPointerUp: endDrag,
};

ちなみに、PCでの動作のみを想定する場合は onMouseDown などでも構いません。 onMouseDown 等はマウスによる入力を前提としたもので、 onPointerDown 等はペンやタッチを考慮したものです。

参考: Pointer events - Web API | MDN

一方、スマホ等での使用を想定する場合はスクロール等のジェスチャーにイベントを取られないよう、稼働範囲の要素のスタイルにtouch-action: noneを設定します。

状態や副作用の設計

移動を実現するコールバックの実装方法はいろいろ考えられると思いますが、非同期に onPointerMove が高頻度で実行されるので、race conditionに注意が必要です。 今回の実装では、ドラッグ開始時の要素の位置とカーソルの位置を覚えておき、 onPointerMove が発生するたびにカーソルが初期位置から移動した距離を計算して要素を初期位置から移動させています。

現在の位置 argPosition, 新しい位置に移動する関数 onMove を引数として受け取ることにし、onPointerMoveonMove を呼ぶことでドラッグ中の位置に移動します。

interface Position {
  x: number;
  y: number;
}

interface DragState {
  originalPosition: Position;
  startCursor: Position;
}

const useMoveOnDrag = (argPosition, onMove) => {
  const [state, setState] = useState<DragState | null>(null);
  const startDrag = (event: React.PointerEvent) => {
    setState({
      originalPosition: {
        x: argPosition.x,
        y: argPosition.y
      },
      startCursor: {
        x: event.pageX,
        y: event.pageY
      }
    });
  };
  const dragging = (event: React.PointerEvent<T>) => {
    if (state === null) return;
    onMove({
      x: state.originalPosition.x + event.pageX - state.startCursor.x,
      y: state.originalPosition.y + event.pageY - state.startCursor.y
    });
  };
  const endDrag = (event: React.PointerEvent<T>) => {
    setState(null);
    if (state === null) return;
    onMove({
      x: state.originalPosition.x + event.pageX - state.startCursor.x,
      y: state.originalPosition.y + event.pageY - state.startCursor.y
    });
  };
  ...
}

前回からの移動分を足し続けるような実装の仕方もあるかと思いますが、その場合は高頻度に変化する状態によるrace conditionを考慮する必要があります。State Hookで実装する場合は、 setStateコールバック形式setState((prevState) => newState) )での実装を検討するなど、工夫が必要です。

また、別の実装をする場合はReactでイベントハンドラに渡されるイベントはSynthetic Eventであることに注意が必要です。 イベントハンドラのスコープを抜けるとイベントは null で初期化されてしまうため、ハンドラ内でコールバックを宣言する際はコールバック内でイベントを参照すると null になってしまいます。 イベントハンドラ内でプロパティを別の変数として格納することで回避します。 今回の実装の場合はコールバックの宣言はしていないので対策はしていません。

Pointer Captureをする

上記のような実装のままでは、カーソルの移動に追いつかずカーソルが要素の外に出てしまったりした際にイベントハンドラが実行されずぎこちない動きになってしまいます。 onPointerDown から onPointerUp までの間、カーソルの移動のイベントのターゲットを移動させたい要素に設定するよう Pointer capture を使用します。

const startDrag = (event: React.PointerEvent) => {
  event.currentTarget.setPointerCapture(event.pointerId);
  ...
};
...
const endDrag = (event: React.PointerEvent<T>) => {
  event.currentTarget.releasePointerCapture(event.pointerId);
  ...
};

setPointerCapture をしない場合の挙動は↓で確認できます。

CodeSandbox

「Move me」を素早くドラッグしてカーソルに追いつかない場合や、「Slide me」をドラッグしている時に上下にカーソルがはみ出た時の挙動がおかしいことがわかると思います。

主な実装の説明は以上です。 このHookを使用する際は、コンポーネントの位置をStateとして持ち、現在位置と位置を設定するコールバックをHookの引数に渡します。(例: Code Sandbox

冒頭で掲載したデモの実装では、記事で触れた内容に加えて useCallback によるメモ化をしたり、型をしっかり定義したりしています。

まとめ

  • onPointerDownonPointerMoveonPointerUpのハンドラを書くことで実現した
  • 実装の際には、Pointer Captureやrace conditionなどに注意する

参考

Reactでコンポーネントの外側がクリックされた際にドロップダウンやモーダルを閉じる等の動作をする

ToC

導入

こういうやつを関数コンポーネント+フックで書きます。

f:id:seiyab:20200926225940g:plain

Material-UI等にも存在しますが、それだけのために依存を追加したくない場合や勉強のために実装しました。 少し検索した感じだと他の日本語記事はクラスコンポーネントのものだったり、フックを使っていても汎用コンポーネントではなかったり副作用の管理の仕方が異なっていたので記事にすることにしました。

実装

onClickに関数を渡すとコンポーネント外をクリックした時に実行してくれるコンポーネントを実装しました。

import { useRef, useEffect } from 'react';

type Props = {
  onClick: (event: MouseEvent) => void;
}

const OutsideClickListener: React.FC<Props> = ({ children, onClick }) => {
  const wrapperRef = useRef<HTMLDivElement | null>(null);
  useEffect(
    () => {
      const listener = (event: MouseEvent) => {
        if (wrapperRef.current === null) return;
        if (event.composedPath().includes(wrapperRef.current)) return;
        onClick(event);
      } 
      document.addEventListener('click', listener);
      return () => document.removeEventListener('click', listener);
    },
    [onClick],
  );

  return (
    <div ref={wrapperRef}>
      {children}
    </div>
  );
};

export default OutsideClickListener;

使い方の例

type Props = {
  content: JSX.Element;
};

const Dropdown: React.FC<Props> = ({ content, children }) => {
  const [isOpened, setIsOpened] = useState(false);
  const toggle = useCallback(
    () => setIsOpened((prev) => !prev),
    [setIsOpened],
  );
  const close = useCallback(
    () => setIsOpened(false),
    [setIsOpened],
  );
  return (<div className={style.dropdown}>
    <OutsideClickListener onClick={close}>
      <button
        className={style.button}
        onClick={toggle}
        type="button"
      >
        {children}
      </button>
      {isOpened && <div className={style.content}>
        {content}
      </div>}
    </OutsideClickListener>
  </div>)
};

export default Dropdown;

解説

コンポーネント外クリックの取得

コンポーネントの外側のイベントを取得したいので、document.addEventListenerを使い全体に対するイベントリスナーを使用します。 クリックされた箇所がOutsideClickListener内の要素でない場合にonClickを実行したいので、document.addEventListenerに渡す関数は次のように書いています。

const listener = (event: MouseEvent) => {
  if (wrapperRef.current === null) return;
  if (event.composedPath().includes(wrapperRef.current)) return;
  onClick(event);
} 

wrapperRefOutsideClickListener自身であるdivに対するrefです。

最初のif文は、wrapperRefの初期値がnullになってしまうのでrefがセットされる前は何もしないようにするearly returnです。

2つ目のif文は、クリックされた要素がwrapperRef内の要素の場合は何もしないようにするearly returnです。 event.composedPath()はイベントに対するリスナーを実行する要素の配列を取得しています。 OutsideClickListener内の要素の場合はバブリングによりOutsideClickListenerに到達するので、includes(wrapperRef.current)trueになります。 この条件文は下記のように実装してもいいかもしれません。

if (event.target instanceof Node && wrapperRef.current.contains(event.target)) return;

リスナーの登録・解除

上記のイベントリスナーはコンポーネントのマウント時に登録し、アンマウント時に解除したいです。 このようにライフサイクルに応じて副作用を起こすにはuseEffectを使用します。

useEffect(
  () => {
    const listener = (event: MouseEvent) => {
      if (wrapperRef.current === null) return;
      if (event.composedPath().includes(wrapperRef.current)) return;
      onClick(event);
    } 
    document.addEventListener('click', listener);
    return () => document.removeEventListener('click', listener);
  },
  [onClick],
);

副作用としてdocument.addEventListener('click', listener)を実行します。 また、アンマウント時や再登録時のクリーンアップ関数として戻り値に() => document.removeEventListener('click', listener) を設定しています。

参考・関連

Division or Subtractionを解く(AtCoder Beginner Contest 161 F)

導入

AtCoder Beginner Contest 161 F - Division or Subtractionを解きます。

解法については公式解説が存在します。 この記事では私が解法に至るまでの思考を書きます。 思考の過程を解説するので、やや遠回りもします。 どうやって解法を思いつくかの参考になれば幸いです。

前提

問題文はAtCoderで確認してください。

問題における、「最終的にNが1になるようなK」を本文では便宜上「いいK」と呼びます。

本文

Nが大きい

問題文を読んだ時にまず注目したのは、 Nの大きさです。 2 <= N <= 10 ^ 12 から 2 <= K <= N 全てについてKがいいKかどうかO(1)で調べられたとしても実行時間制限に間に合わないということです。

# このような解法は check がどれだけ早くても間に合わない
ans = 0
for k in range(2, n+1):
  if check(n, k):
    ans += 1
print(ans)

なので、TLEにならないためにはたとえば下記のようなことができないかを考える必要があるということを頭に入れておきます。

  • いいKになりうる候補をO(√n)個以下程度に絞り込む(O(log n)などだとより嬉しい)。
    • たとえば√n個に絞り込めた場合checkO(log n)程度で実装できれば候補に対してcheckしていく方法が間に合う。
    • あるいは、log n個に絞り込めた場合checkO(√n)程度かかっても間に合う。
  • Kに対していいKかを調べる方法ではなく、NからいきなりいいKの個数をO(√n)以下で求める。
  • Nに対しいいKM個以上あるか」をO(√n)以下で求める。
    • これができるなら二分探索で答えを求めることができる

操作について

操作の性質について考えていきます。

一旦、手で操作を実施してみます。人によるかもしれませんが、具体的な値を触った方が理解が深まるということはよくあります。 入力例1にあるN=6を使ってみます。

  • K=2のとき(出力例の説明にあるとおり、これは「いいK」のはず)
    1. 6は2で割り切れるので割ります。6→6/2=3
    2. 3は2で割り切れないので引きます。3→3-2=1
    3. 1はK=2未満なので終了です。いいKです。
  • K=3のとき(出力例の3つの「いいK」いずれでもなく、「いいK」ではないはず)
    1. 6は3で割り切れるので割ります。6→6/3=2
    2. 2はK=3未満なので終了です。いいKではありません。

操作をプログラムで行なった場合どれくらいの時間がかかるかを見積もってみます。 条件によって2種類の操作を使い分けて操作を繰り返した場合の計算時間の見積もりは少し難しいので、一旦片方の操作しかない場合を考えてみます。

  • 割り算だけを考える場合
    • 簡単のためNK未満になるまで必ず割り切れる場合を考えます。一回の操作でN1/Kの大きさになります。これは指数関数的な減少であり、NK未満になるまでlog_k n回程度の操作で済みそうです。
  • 引き算だけを考える場合
    • 一回の操作でNKだけ小さくなるので、繰り返しにより実現するとn/k回の操作が必要です。しかし、もし引き算のみを繰り返す場合は割り算の余りと同じですのでn%kとすることでO(1)で最終的なNを求めることができます。

単一の操作の繰り返しはどうやらある程度高速に実行できそうです。 あるいは、もし実はほとんどどちらかの操作の連続でごくまれに操作が切り替わるのであれば高速に操作を終わらせることができるかもしれません。

操作が連続しそうか、どんなときに切り替わるかについて考えてみます。

  • 割り算を実施した後
    • 割り算をした後再び割り切れるか、あるいは何連続で割り切れるかを高速で判断する方法は残念ながら思いつきませんでした。
    • 余談ですが、Kが固定であれば[1, K, K^2, K^3,...]というリストをあらかじめ作っておくことで二分探索で何連続割り切れるか高速に判断できそうです。今回はいろんなKについて調べたいので不適です。
  • 引き算を実施した後
    • 引き算を実施したということは引き算実施前のNKで割り切れません。この場合、N-KKで割り切れないので、引き算の後の操作は必ず引き算ということがいえます。

上記考察により引き算のあとに割り算をすることはないことがわかりました。 このことから、KがいいKかどうか調べるのは次のような手順で実現できます。

def check(original_n, k):
  n = original_n
  while n % k == 0:
    n = n // k
  n = n % k
  return n == 1

この操作は、繰り返し文の箇所が高々log_k n回程度しか行われないので、もしいいKになりうる候補をO(√n)個以下に絞り込めればそれらに対してチェックをしても間に合いそうです。

候補の絞り込み

候補の絞り込みができるか考えてみます。 この手順で解けると決まったわけではありませんが、他に確実な解法も思いつかないので一旦考えてみるという感じです。

これまでの考察から、操作は0回以上の割り算→0回以上の引き算の順しかないので、Kを2種類に分類して考えてみます。

  • 1回以上の割り算を行うもの = Nの約数
  • 1度も割り算を行わないもの = Nの約数ではない

やや方針が突飛に見えるかもしれませんが、下記の点からこの分類は考察する価値がありそうです。

  • Nの約数の数は高々2√n個なので、前者は絞り込みの要件に合う
  • 後者は操作が1つしかないので考察を進めやすそう

まず、Nの約数であるKについては個数が2√N個以下であり、その列挙もO(√n)であることがいえます。

# 約数を列挙する方法の例
def divisors(n):
  result = []
  for i in range(1, n+1):
    if i*i > n:
      break
    if n%i == 0:
      result.append(i)
      d = n // i
      if d != i:
        result.append(d)
  return result

これら全てに対していいKかどうか調べることは実行時間制限内に収まりそうです。

Nの約数ではないKについて考察します。 引き算のみを行うので、N % K == 1となるようなKの数を調べるということになります。 この変形ができてしまえば簡単で、N-1の約数がいいKということになります。 N-1の約数もNの約数と同様O(√n)で列挙できるので、どうやら間に合いそうです。

仕上げ

Nの約数であるようないいKの数とNの約数でないようないいKの数の合算が答えです。

次のことに気をつけます。

  • Kは2以上なのでNN-1の約数のうち「1」はカウントしない
  • 同じKを重複してカウントしてはいけないが、NN-1の最大公約数は1なので考慮は不要

以上より、下記のような手順で解くことができます。

  1. Nの約数であるようないいKをカウントする
    1. 1以外のNの約数を全て列挙する
    2. 各約数について、いいKかチェックする
  2. Nの約数でないようないいKをカウントする
    1. 1以外のN-1の約数の数がNの約数でないようないいKの数
  3. Nの約数であるようないいKの数とNの約数でないようないいKの数の和が答え

私の提出

docker-composeのvolumeを作り直す

TL;DR

docker volume rm {volume-name}

ToC

導入

docker-composeで使用しているvolumeを作り直す必要があったが検索が下手で見つからなかった。 docker-composeではなくdockerの機能で削除できるので、docker-composeのオプション等を調べるとハマる。

方法

特定のvolumeだけリセットしたい場合

docker volume rm {volume-name}

参考: docker volume rm

削除するべきvolume-nameについて:命名の設定をしていない場合はデフォルトで {ディレクトリ名}_{docker-compose.yamlに書いたvolume名} などになっている。

次のコマンドで存在するvolume一覧を確認できる。

docker volume ls

参考: docker volume ls

コンテナが使用していて削除できないというエラーが出た場合はコンテナを終了させてから実行し直す。

# コンテナ一覧
docker ps -a

# コンテナ停止
docker stop {container-id}

# コンテナ終了
docker rm {container-id}

参考:

docker-compose.yamlで使用している全てのvolumeをリセットしたい場合

docker-compose down -v

参考:

eslint-plugin-reactのno-exhaustive-depsは何故propsのプロパティの関数を使う際にpropsも依存に要求するのか

TL;DR

  • オブジェクトのpropetyの関数をコールする際にthisが渡されるため。
  • propsは常に分割代入しておくのがよいとされる(react/destructuring-assingmentでESLintに設定可能)。

背景

eslint-plugin-reactのno-exhaustive-depsはpropsのpropertyの関数を使う際に下記のようにprops自身も要求します (正確にはpropsに限らずオブジェクトのpropertyを関数として使用するとオブジェクト自身も要求します)。

useEffect(
  () => props.updateNumber(5),
  [props.updateNumber],  // React Hook useEffect has a missing dependency: 'props'.
);

props内のプロパティとしてはprops.updateNumberしか使用していないのに何故 propsを要求するのでしょうか?

理由

no-exhaustive-depsが追加された時にフィードバックを募るIssueで同様の質問がありました。

Not sure if this is intentional or not: When you call a function from props, the linter suggests adding the entire props object as a dependency.

和訳

意図的なのかわからないのですが、propsから関数を読んだ時、リンターが依存としてpropオブジェクト全体を追加することを提案してきます。

それに対する回答は次のとおりです。

This is because technically props.foo() passes props itself as this to foo call. So foo might implicitly depend on props. We'll need a better message for this case though. The best practice is always destructuring.

和訳

それは厳密に言うとprops.foo()thisとしてprops自身をfoo呼び出しに渡すためです。fooは暗黙的にpropsに依存している可能性があります。このケースについてより良いメッセージが必要ですね。ベストプラクティスは常に分割代入することです。

つまり、オブジェクトのプロパティを関数として呼び出すとthisとしてオブジェクト自身を使用するため、オブジェクトにも依存してしまうのでexhaustive-depsはオブジェクトを依存のリストに追加することを提案します。

挙動の確認

FirefoxのConsoleでthisへの依存について動作確認してみます。

>> function func() { return this.a; }
<< undefined
>> func(); // 関数をそのまま呼ぶとthisはグローバルオブジェクトであるウィンドウオブジェクトで、this.aはwindow.aでありundefinedです
<< undefined
>> const x = { a: 'aaa', f: func };
<< undefined
>> x.f(); // xのプロパティとして呼ぶとthisはxになるので、this.aはx.aであり'aaa'です。
<< "aaa"
>> const { f } = x;
<< undefined
>> f(); // 分割代入をして呼び出すと、thisはウィンドウオブジェクトになりthis.aはundefinedになります。
<< undefined

次のことがわかります。

  • オブジェクトのプロパティを関数として呼び出すとオブジェクトの状態に依存して結果が変わる場合がある
  • 分割代入をしてプロパティとしての呼び出しでないようにするとthisによるオブジェクトへのアクセスはできなくなる

対処法

props内の関数を使用したいけどprops自身をdepsに加えたくない場合はどのようにすればよいかというと、理由として引用したコメントに書かれているとおり、常にpropsを分割代入するようにすればよいです。

常にpropsを分割代入することを推奨するためルールも存在するので、設定しておくとスタイルを統一できます。