---
title: 制約を表現する型を書く
tags: 
author: [yuta-ike](https://www.docswell.com/user/4136989)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/8EDKRY2Y7G.jpg?width=480
description: TSKaigi2026本編での発表: https://www.docswell.com/s/4136989/Z6NJ78-tskaigi2026/1
published: June 09, 26
canonical: https://www.docswell.com/s/4136989/KN79JV-tskaigi-side-event-20260609
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/8EDKRY2Y7G.jpg)

制約を表現する型を書く
2026.06.09 TSKaigi 2026 本編で話せなかったこと、
話し足りなかったこと
@yuta-ike
エムスリー / エンジニアリンググループ

# Page. 2

![Page Image](https://bcdn.docswell.com/page/V7PKW612J8.jpg)

型 = プログラムの制約を
表現する手段

# Page. 3

![Page Image](https://bcdn.docswell.com/page/2JVV83YXJQ.jpg)

型がない場合
どんな値が渡されるか分からない
→ 必然的に任意の値を想定した防御的なプログラミング
命名規則・コメントなどのナイーブな手段しかない
function formatDate(x) {
let date
if(x instanceof Date) { date = x }
if(typeof x === &quot;string&quot;) { date = new Date(x) }
if(typeof x === &quot;number&quot;) { date = new Date(x * 1_000) }
// ...
}

# Page. 4

![Page Image](https://bcdn.docswell.com/page/5EGL539RJL.jpg)

型は事前条件を表明する手段
• この関数はDate型を受け取ることで機能する
• 引数の想定範囲を狭める
• 関数本来の目的にフォーカスできる
// この関数はDate型を期待する
function formatDate(date: Date) {
// ...
}

# Page. 5

![Page Image](https://bcdn.docswell.com/page/4JQYZ5WY7P.jpg)

型は事前条件を表明する手段
• buy関数はプロダクトの配列を期待する。
• 型があることで情報量が増える → 処理内容を読まずとも、
関数の中身が想像できる（意図の明白なインターフェース）
function buy(products: any[]) : Promise&lt;OrderDetail&gt; {
/*... */
}
function buy(products: Product[]): Promise&lt;OrderDetail&gt; {
/*... */
}

# Page. 6

![Page Image](https://bcdn.docswell.com/page/K74W39KZE1.jpg)

型は事前条件を表明する手段
• buy関数は、購入物が1個以上存在しないとエラー（0個を購入
することはできない）
• 処理内部でバリデーション。0個なら Error throw
function buy(products: Product[]): Promise&lt;OrderDetail&gt; {
if(products.length === 0) {
throw new Error(&quot;0件です&quot;)
}
/* ... */
}

# Page. 7

![Page Image](https://bcdn.docswell.com/page/LJ1Y1KXDEG.jpg)

型は事前条件を表明する手段
• buy関数は、購入物が1個以上存在しないとエラー（0個を購入
することはできない）
• 処理内部でバリデーション。0個なら Error throw
function buy(products: Product[]): Promise&lt;OrderDetail&gt; {
if(products.length === 0) {
throw new Error(&quot;0件です&quot;)
}
/* ... */
}

# Page. 8

![Page Image](https://bcdn.docswell.com/page/GJWG8DN872.jpg)

型は事前条件を表明する手段
• buy関数の事前条件:
• ぱっと見 「Productの配列が与えられる」
• 本当は... 「Productの長さ1以上の配列が与えられる」
function buy(products: Product[]): Promise&lt;OrderDetail&gt; {
if(products.length === 0) {
throw new Error(&quot;0件です&quot;)
}
/* ... */
}

# Page. 9

![Page Image](https://bcdn.docswell.com/page/4EZL8G3973.jpg)

型は事前条件を表明する手段
• buy関数の事前条件:
• ぱっと見 「Productの配列が与えられる」
• 本当は... 「Productの長さ1以上の配列が与えられる」
function buy(products: Product[]): Promise&lt;OrderDetail&gt; {
if(products.length === 0) {
throw new Error(&quot;0件です&quot;)
}
/* ... */
インターフェースに現れていない情報

# Page. 10

![Page Image](https://bcdn.docswell.com/page/Y76WPYXD7V.jpg)

NonEmptyArray
type NonEmptyArray&lt;T&gt; = [T, ...T[]]

# Page. 11

![Page Image](https://bcdn.docswell.com/page/G75MKGX874.jpg)

NonEmptyArray
type NonEmptyArray&lt;T&gt; = [T, ...T[]]
最初のTは必ず存在する

# Page. 12

![Page Image](https://bcdn.docswell.com/page/9J29WYXVER.jpg)

NonEmptyArray
type NonEmptyArray&lt;T&gt; = [T, ...T[]]
0以上の要素が続く
最初のTは必ず存在する

# Page. 13

![Page Image](https://bcdn.docswell.com/page/DEY4LG8QJM.jpg)

NonEmptyArray
type NonEmptyArray&lt;T&gt; = [T, ...T[]]
0以上の要素が続く
最初のTは必ず存在する
• 1つ以上の要素が必ず存在する配列型を定義できる

# Page. 14

![Page Image](https://bcdn.docswell.com/page/VJNY4GQ278.jpg)

NonEmptyArray
type NonEmptyArray&lt;T&gt; = [T, ...T[]]
function NonEmptyArray&lt;T&gt;(arr: T[]) {
if(arr.length === 0) {
throw new Error(&quot;0件です&quot;) }
return arr as NonEmptyArray&lt;T&gt;
}
• NonEmptyArrayを得るためのファクトリ関数を定義
（スマートコンストラクタ）

# Page. 15

![Page Image](https://bcdn.docswell.com/page/YE9PQWDDJ3.jpg)

型は事前条件を表明する手段
• buy関数の事前条件:
• ぱっと見 「Productの配列が与えられる」
• 本当は... 「Productの長さ1以上の配列が与えられる」
function buy(products: Product[]): Promise&lt;OrderDetail&gt; {
if(products.length === 0) {
throw new Error(&quot;0件です&quot;)
}
/* ... */
}

# Page. 16

![Page Image](https://bcdn.docswell.com/page/GE8DGN35ED.jpg)

型は事前条件を表明する手段
• buy関数の事前条件:
• ぱっと見 「Productの配列が与えられる」
• 本当は... 「Productの長さ1以上の配列が与えられる」
function buy(products: NonEmptyArray&lt;Product&gt;): Promise&lt;OrderDetail&gt; {
// バリデーションを丸ごと消せる
/* ... */
}
NonEmptyArrayを使って
空配列を想定しないことを型レベルで表明

# Page. 17

![Page Image](https://bcdn.docswell.com/page/LELMGPV37R.jpg)

型は事前条件を表明する手段
• buy関数の事前条件:
• ぱっと見 「Productの配列が与えられる」
• 本当は... 「Productの長さ1以上の配列が与えられる」
function buy(products: NonEmptyArray&lt;Product&gt;): Promise&lt;OrderDetail&gt; {
// バリデーションを丸ごと消せる
/* ... */
}
バリデーションが不要に

# Page. 18

![Page Image](https://bcdn.docswell.com/page/4JMYQ4GMJW.jpg)

全域関数と部分関数
全域関数: 任意の入力について答えが定まる関数
（必ずreturnする）
部分関数: 答えが定まらない入力が存在する関数
（Errorが出る入力パターンがある）
ex. 足し算はオーバーフローを考慮しなければ全域関数
割り算は N ÷ 0 が定義されないので部分関数

# Page. 19

![Page Image](https://bcdn.docswell.com/page/PJR98WZL79.jpg)

全域関数と部分関数
Product[] 型
[Product, Product]
[Product, Product, ...]
[Product]
空配列型 []
答えが定まる領域
答えが定まらない領域
（Errorが出る入力パターン）

# Page. 20

![Page Image](https://bcdn.docswell.com/page/PEXQ8P16JX.jpg)

全域関数と部分関数
Product[] 型
[Product, Product]
[Product, Product, ...]
[Product]
空配列型 []
元の実装は、Prodcut[] 全体を
取りうる値として定義していた
function buy(products: Product[])

# Page. 21

![Page Image](https://bcdn.docswell.com/page/3EK9KD2GED.jpg)

全域関数と部分関数
Product[] 型
[Product, Product]
[Product, Product, ...]
[Product]
空配列型 []
あとの実装は、答えが存在する範囲に
絞り込んでいた
function buy(products: NonEmptyArray&lt;Product&gt;)

# Page. 22

![Page Image](https://bcdn.docswell.com/page/L73WZR4575.jpg)

なぜ全域関数が嬉しいのか？
全ての情報が型に表出するから（裏を返すと、throw されたエラーは
型に表出しない）
• 型に表出すれば、TypeScriptの型システムの恩恵を受けてより堅
牢な静的解析が可能になる。
function buy(products: Product[])
function buy(products: NonEmptyArray&lt;Product&gt;)

# Page. 23

![Page Image](https://bcdn.docswell.com/page/87DKRYQYJG.jpg)

なぜ全域関数が嬉しいのか？
先ほどの例は定義域（引数の取りうる値の範囲=型）を狭めることで全
域関数を作る。
逆に、値域（戻り値の範囲=型）を広げることで対応するのが、所謂
Result型と呼ばれるテクニック。
Product[] 型
[Product, Product]
[Product, Product, ...]
[Product]
空配列型 []

# Page. 24

![Page Image](https://bcdn.docswell.com/page/VJPKW6L2E8.jpg)

Result型
本来であればErrorをthrowするところを、値を返すアプローチ。
async function getUserByUserId(userId: string):
Result&lt;User, UserNotFoundError&gt; {
const user = await db.user.findUnique({ id: userId })
if(user == null) {
return err(new UserNotFoundError(&quot;User not found&quot;))
}
return ok(user)
}

# Page. 25

![Page Image](https://bcdn.docswell.com/page/2EVV83QXEQ.jpg)

Result型
本来であればErrorをthrowするところを、値を返すアプローチ。
• 入力の範囲を狭めるだけではエラーを防げない（=副作用あり）に使える
• インターフェースにエラーパターンが表出するのは先ほど同様
async function getUserByUserId(userId: string):
Result&lt;User, UserNotFoundError&gt; {
const user = await db.user.findUnique({ id: userId })
if(user == null) {
return err(new UserNotFoundError(&quot;User not found&quot;))
}
return ok(user)
}

# Page. 26

![Page Image](https://bcdn.docswell.com/page/57GL53WREL.jpg)

素直なTSの型では表現できない型がたくさんある
むしろNonEmptyArrayのように素直に定義できる方が稀かも。
例えば...
• 空文字列を許容したくないケース
• → 純粋なTypeScriptでは「1文字以上の文字列」を表現できない
• 特定のドメインモデルを指定したいケース
• → 純粋なTypeScriptでは、パース前のデータとパース後のドメインモ
デルを区別できない（構造的部分型）

# Page. 27

![Page Image](https://bcdn.docswell.com/page/4EQYZ53YJP.jpg)

そんなときには....
Branded Types で解決しよう
TSKaigi 2026
Leveragesトラック 30分セッション DAY2 15:10 ~ 15:40
AI時代に考える
Branded Typesで実現する堅牢な型付け
池奥裕太/@yuta-ike

# Page. 28

![Page Image](https://bcdn.docswell.com/page/KJ4W391Z71.jpg)

懇親会でお話しましょう
@yuta-ike
エムスリー / エンジニアリンググループ

