LoginSignup
92
47

More than 5 years have passed since last update.

TypeScriptの型システムなら《コンパイル時に》ポーカーの役を判定できるかも?

Last updated at Posted at 2019-03-16

与えられた手札(5枚のトランプカード)からポーカーの役を判定するアルゴリズムをどう組むかというクイズあります。

第一回 オフラインリアルタイムどう書くの参考問題 - Qiita

こちらを、TypeScriptの型システムだけで達成できるのかやってみました。

TypeScriptの型システムにはConditional Typeという仕組みがあり、平たく言うと型宣言に条件分岐が使えるというものです。

次のように、A extends B ? C : Dのように三項演算子のような書き方で型の分岐が行えます。

type Exclude<T, U> = T extends U ? never : T

この例では、T型がU型と同じだったらnever型、つまりコンパイルエラーにし、そうでなければコンパイルを通すという仕掛けになります。

type StringOrStrings = string | string[]
type StringOnly = Exclude<StringOrStrings, string[]> // string

上で定義したStringOnly型はstring型もしくはstring[]型を受けつけるユニオン型から、string[]でない型、つまり、string型だけを受け付ける型という意味になります。

const value1: StringOrStrings = 'aaa'
const value2: StringOrStrings = ['aaa']
const value3: StringOnly = 'aaa'
const value4: StringOnly = ['aaa'] // コンパイルエラー

このconditional typeの仕組みをフル活用して、コンパイル時にポーカーの役を判定するコードを書いてみました。

次がポーカーの役を判定する型定義部分です。

type A = "A"
type J = "J"
type Q = "Q"
type K = "K"
type Card = A | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | J | Q | K
type True = true
type False = false
type IsTrue<A> = A extends True ? True : False
type Not<A> = A extends True ? False : True
type And<A, B> = A extends True ? IsTrue<B> : False
type Eq<A, B> = A extends B ? True : False;
type FourCard_<A, B, C, D, E> = [A, B, C, D, E] extends [Card, Card, Card, Card, Card]
  ? And<Eq<A, B>, And<Eq<B, C>, And<Eq<C, D>, Not<Eq<A, E>>>>>
  : False;
type FullHouse<A, B, C, D, E> = [A, B, C, D, E] extends [Card, Card, Card, Card, Card]
  ? And<Eq<A, B>, And<Eq<B, C>, And<Eq<D, E>, Not<Eq<A, E>>>>>
  : False;
type ThreeCard<A, B, C, D, E> = [A, B, C, D, E] extends [Card, Card, Card, Card, Card]
  ? And<Eq<A, B>, And<Eq<B, C>, And<Not<Eq<D, E>>, Not<Eq<A, E>>>>>
  : False;
type TwoPair__<A, B, C, D, E> = [A, B, C, D, E] extends [Card, Card, Card, Card, Card]
  ? And<Eq<A, B>, And<Eq<C, D>, And<Not<Eq<A, C>>, And<Not<Eq<A, E>>, Not<Eq<C, E>>>>>>
  : False;
type OnePair__<A, B, C, D, E> = A extends Card
  ? And<Eq<A, B>, And<Not<Eq<B, C>>, And<Not<Eq<B, D>>, And<Not<Eq<B, E>>,
    And<Not<Eq<C, D>>, And<Not<Eq<C, E>>, Not<Eq<D, E>>>>>>>>
  : False;

どの役にあたるかの判定はコンパイラにやってもらいます。具体的には上で定義した役の型(FullHouse<A, B, C, D, E>など)の型パラメータに、手札のランクを当てはめ、コンパイルが通るかで判定します。

const test1: FourCard_<2, 2, 3, J, Q> = true
const test2: FullHouse<2, 2, 3, J, Q> = true
const test3: ThreeCard<2, 2, 3, J, Q> = true
const test4: TwoPair__<2, 2, 3, J, Q> = true
const test5: OnePair__<2, 2, 3, J, Q> = true

この状態で、tscでコンパイルを実行します。

tsc poker.ts
logical.ts:29:7 - error TS2322: Type 'true' is not assignable to type 'false'.

29 const test1: FourCard_<2, 2, 3, J, Q> = true
         ~~~~~

logical.ts:30:7 - error TS2322: Type 'true' is not assignable to type 'false'.

30 const test2: FullHouse<2, 2, 3, J, Q> = true
         ~~~~~

logical.ts:31:7 - error TS2322: Type 'true' is not assignable to type 'false'.

31 const test3: ThreeCard<2, 2, 3, J, Q> = true
         ~~~~~

logical.ts:32:7 - error TS2322: Type 'true' is not assignable to type 'false'.

32 const test4: TwoPair__<2, 2, 3, J, Q> = true
         ~~~~~


Found 4 errors.

コンパイル結果を見るとOnePair__以外がコンパイルエラーになったので、手札はワンペアであることがわかります。

毎回コンパイルしてどの行が通っているか考えるのは面倒なので、WebStormを使うことをおすすめします。WebStormではコンパイルエラーの箇所が赤の下線で表示され、視覚的に判断しやすいからです。

typescript-playground___Volumes_dev_typescript-playground__-_____logical_ts__typescript-playground_.png

今回の試みでTypeScriptの型システムを用いると、ポーカーの役が判定できることがわかりました。ただ、実用性はないと思います。

92
47
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
47