TypeScriptやJavaでif式やtry式が欲しい時
概要
TypeScriptやJavaなど、 if
や try
が式ではなく文になっている言語でそれらを式として使う方法の一つとして即時実行関数式を紹介します。
また、専用のユーティリティ関数によるアプローチと比較します。
対象とする問題
TypeScriptやJavaでは if
や try
が文であるため、宣言的な記述が難しい場面があります。
// 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
関数は単純ながら意外にも多機能です。
if
、try
、switch
などに対応している- 遅延評価に対応している
- スコープを作り、変数宣言ができる
などなど。単純に関数に備わっている機能が使用できます。
実装が単純明快である
iife
の実装は関数を実行するだけであり、非常に理解しやすいです。
このことは地味に見えますが非常に強力で、バグったときに何が起きるのかを追うのが非常に簡単です。
ユーティリティ関数は一度作ればずっと使いまわせるのでそのような場面は少ないように思われますが、 if
、 try
、 switch
それぞれに実装が必要になったり、それらに遅延評価を追加したくなって機能変更をする場面など、実装・デバッグが必要な場面は案外何度か発生してしまいます。
静的コード解析が適用されやすい
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
を引数として受け取ることにし、onPointerMove
で onMove
を呼ぶことでドラッグ中の位置に移動します。
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
をしない場合の挙動は↓で確認できます。
「Move me」を素早くドラッグしてカーソルに追いつかない場合や、「Slide me」をドラッグしている時に上下にカーソルがはみ出た時の挙動がおかしいことがわかると思います。
主な実装の説明は以上です。 このHookを使用する際は、コンポーネントの位置をStateとして持ち、現在位置と位置を設定するコールバックをHookの引数に渡します。(例: Code Sandbox)
冒頭で掲載したデモの実装では、記事で触れた内容に加えて useCallback
によるメモ化をしたり、型をしっかり定義したりしています。
まとめ
onPointerDown
、onPointerMove
、onPointerUp
のハンドラを書くことで実現した- 実装の際には、Pointer Captureやrace conditionなどに注意する
参考
Reactでコンポーネントの外側がクリックされた際にドロップダウンやモーダルを閉じる等の動作をする
ToC
導入
こういうやつを関数コンポーネント+フックで書きます。
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); }
wrapperRef
はOutsideClickListener
自身である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)
を設定しています。
参考・関連
- MDN
- React
- Matrial-UI
- Stack Overflow
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
個に絞り込めた場合check
をO(log n)
程度で実装できれば候補に対してcheck
していく方法が間に合う。 - あるいは、
log n
個に絞り込めた場合check
にO(√n)
程度かかっても間に合う。
- たとえば
- 各
K
に対していいK
かを調べる方法ではなく、N
からいきなりいいK
の個数をO(√n)
以下で求める。 - 「
N
に対しいいK
がM
個以上あるか」をO(√n)
以下で求める。- これができるなら二分探索で答えを求めることができる
操作について
操作の性質について考えていきます。
一旦、手で操作を実施してみます。人によるかもしれませんが、具体的な値を触った方が理解が深まるということはよくあります。
入力例1にあるN=6
を使ってみます。
K=2
のとき(出力例の説明にあるとおり、これは「いいK
」のはず)- 6は2で割り切れるので割ります。6→6/2=3
- 3は2で割り切れないので引きます。3→3-2=1
- 1は
K=2
未満なので終了です。いいK
です。
K=3
のとき(出力例の3つの「いいK
」いずれでもなく、「いいK
」ではないはず)- 6は3で割り切れるので割ります。6→6/3=2
- 2は
K=3
未満なので終了です。いいK
ではありません。
操作をプログラムで行なった場合どれくらいの時間がかかるかを見積もってみます。 条件によって2種類の操作を使い分けて操作を繰り返した場合の計算時間の見積もりは少し難しいので、一旦片方の操作しかない場合を考えてみます。
- 割り算だけを考える場合
- 簡単のため
N
がK
未満になるまで必ず割り切れる場合を考えます。一回の操作でN
は1/K
の大きさになります。これは指数関数的な減少であり、N
がK
未満になるまでlog_k n
回程度の操作で済みそうです。
- 簡単のため
- 引き算だけを考える場合
- 一回の操作で
N
がK
だけ小さくなるので、繰り返しにより実現するとn/k
回の操作が必要です。しかし、もし引き算のみを繰り返す場合は割り算の余りと同じですのでn%k
とすることでO(1)
で最終的なN
を求めることができます。
- 一回の操作で
単一の操作の繰り返しはどうやらある程度高速に実行できそうです。 あるいは、もし実はほとんどどちらかの操作の連続でごくまれに操作が切り替わるのであれば高速に操作を終わらせることができるかもしれません。
操作が連続しそうか、どんなときに切り替わるかについて考えてみます。
- 割り算を実施した後
- 割り算をした後再び割り切れるか、あるいは何連続で割り切れるかを高速で判断する方法は残念ながら思いつきませんでした。
- 余談ですが、
K
が固定であれば[1, K, K^2, K^3,...]
というリストをあらかじめ作っておくことで二分探索で何連続割り切れるか高速に判断できそうです。今回はいろんなK
について調べたいので不適です。
- 引き算を実施した後
- 引き算を実施したということは引き算実施前の
N
はK
で割り切れません。この場合、N-K
もK
で割り切れないので、引き算の後の操作は必ず引き算ということがいえます。
- 引き算を実施したということは引き算実施前の
上記考察により引き算のあとに割り算をすることはないことがわかりました。
このことから、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以上なのでN
やN-1
の約数のうち「1」はカウントしない- 同じ
K
を重複してカウントしてはいけないが、N
とN-1
の最大公約数は1なので考慮は不要
以上より、下記のような手順で解くことができます。
N
の約数であるようないいK
をカウントする- 1以外の
N
の約数を全て列挙する - 各約数について、いい
K
かチェックする
- 1以外の
N
の約数でないようないいK
をカウントする- 1以外の
N-1
の約数の数がN
の約数でないようないいK
の数
- 1以外の
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()
passesprops
itself asthis
tofoo
call. Sofoo
might implicitly depend onprops
. 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を分割代入することを推奨するためルールも存在するので、設定しておくとスタイルを統一できます。