PureScriptで関数型プログラミングに挑戦した

1/10 遅くなりましたが、やっと追記始めました

PureScriptで関数型プログラミングに挑戦した結果の紹介です。
リポジトリ　https://github.com/maron8676/Advent_2018_MyPoker

## PureScriptとは
1. JavaScriptにコンパイルできるAltJSの一種
2. Haskellに影響を受けている関数型プログラミング言語
4. 既存のJavaScriptコードと組み合わせて使える

公式　http://www.purescript.org/

インストール方法などは以下の方が詳しく紹介してくださっているので、本記事では省略させていただきます。
[純粋関数型スクリプト言語PureScriptのはじめかた](https://qiita.com/hiruberuto/items/2316b58162cfec150460)

## PureScriptで関数型プログラミングに挑戦した
最近、関数型プログラミングを勉強しているのですが、何かを作ってみるのが一番だと思い、トランプのポーカープログラムを作成しました。正確にはまだ手札の役判定だけです。
※勉強中のため、もっといい書き方あるよーという方がいたら教えてくださると助かります。

予定日から遅れてしまっているためいったん投稿しますが、どのような考えで書いていたかを随時追記していきます。

### 工夫したポイント
ポーカーの役判定で難しいところは以下の２つだと思っています

1. ジョーカーはどのカードにもなれて、エースは１としても使える
2. ジョーカーやエースを使ってなるべく強い役で判定する必要がある

今回は安直な実装ですが、ジョーカーやエースを含む手札からあり得る可能性を全て列挙することで1.を解決しました
2.については、強い役から順に判定してくことで解決できると思います（未実装）。

### ファイル構成
以下の通りです。未実装部分があるためRank.pursは現状意味がない状態となっています。

|ファイル名|内容|
|---------|----------|
|Main.purs|メイン処理|
|Card.purs|カードのモデル|
|Rank.purs|役のモデル|

### ジョーカーとエースの扱い

結果から出すとこんな感じになりました

```haskell:Card.purs（一部）
-- | カード１枚を表す
type Card = Tuple Suit CardNum

-- | カードのスート
data Suit = Diamonds | Clubs | Hearts | Spades | StJoker

-- | スートの表示用文字列
instance showSuit :: Show Suit where
  show Diamonds = "Diamonds"
  show Clubs = "Clubs"
  show Hearts = "Hearts"
  show Spades = "Spades"
  show StJoker = ""

-- | スートの同値関係
instance eqSuitInst :: Eq Suit where
  eq _ StJoker = true
  eq StJoker _ = true
  eq a b = show a == show b

-- | スートの順序関係（手札ソートのために定義）
derive instance ordSuit :: Ord Suit

-- | カードの番号（強さ）
-- | OneはAceの別名として使う
data CardNum = 
  One |
  Two |
  Three |
  Four |
  Five |
  Six |
  Seven |
  Eight |
  Nine |
  Ten |
  Jack |
  Queen |
  King |
  Ace |
  Joker 

-- | カードの番号（強さ）の表示用文字列
instance showCardNum :: Show CardNum where
  show One = "A"
  show Two = "2"
  show Three = "3"
  show Four = "4"
  show Five = "5"
  show Six = "6"
  show Seven = "7"
  show Eight = "8"
  show Nine = "9"
  show Ten = "10"
  show Jack = "J"
  show Queen = "Q"
  show King = "K"
  show Ace = "A"
  show Joker = "JK"
derive instance eqCardNumInst :: Eq CardNum

-- | 番号（強さ）の順序関係（手札ソートのために定義）
derive instance ordCardNum :: Ord CardNum

-- | １つ前の番号（強さ）のカード
pred :: CardNum -> CardNum
pred Ace = King
pred One = Joker
pred Two = Ace
pred Three = Two
pred Four = Three
pred Five = Four
pred Six = Five
pred Seven = Six
pred Eight = Seven
pred Nine = Eight
pred Ten = Nine
pred Jack = Ten
pred Queen = Jack
pred King = Queen
pred Joker = Joker

-- | １つ次の番号（強さ）のカード
succ :: CardNum -> CardNum
succ Ace = Joker
succ One = Two
succ Two = Three
succ Three = Four
succ Four = Five
succ Five = Six
succ Six = Seven
succ Seven = Eight
succ Eight = Nine
succ Nine = Ten
succ Ten = Jack
succ Jack = Queen
succ Queen = King
succ King = Ace
succ Joker = Joker

-- | 第１引数のカードの１つ前の番号（強さ）が第２引数のカードである場合`True`を返す
isPred :: CardNum -> CardNum -> Boolean
isPred _ Joker = true
isPred Joker _ = true
isPred a b = pred a == b

-- | 第１引数のカードの１つ次の番号（強さ）が第２引数のカードである場合`True`を返す
isSucc :: CardNum -> CardNum -> Boolean
isSucc _ Joker = true
isSucc Joker _ = true
isSucc a b = succ a == b

-- | スートの種類数
cardinalitySuit :: Int
cardinalitySuit = length suits

-- | ジョーカー以外のカードの種類数
cardinalityCardNum :: Int
cardinalityCardNum = length normalCardNums

-- | カード全体の種類数
cardinalityCard :: Int
cardinalityCard = length cards

-- | 全てのスートのリスト
suits :: List Suit
suits = (Diamonds : Clubs : Hearts : Spades : Nil)

-- | 全ての番号（強さ）のリスト
normalCardNums :: List CardNum
normalCardNums = (Two : Three : Four : Five : Six : Seven : Eight : Nine : Ten : Jack : Queen : King : Ace : Nil)

-- | ジョーカー以外のカードのリスト
normalCards :: List Card
normalCards = Tuple <$> suits <*> normalCardNums

-- | 全てのカードのリスト
cards :: List Card
cards = (Tuple StJoker Joker) : normalCards

-- | ロイヤルストレートフラッシュ判定用リスト
royalStraight :: List CardNum
royalStraight = (Ten : Jack : Queen : King : Ace : Nil)
```

```haskell:Main.purs（一部）
-- | エースと別名の１を入れ替える　他の番号はそのまま返す
flipAce :: CardNum -> CardNum
flipAce Ace = One
flipAce One = Ace
flipAce cardNum = cardNum

-- | エースを考慮して、カードの番号をあり得る可能性に展開する
expAceElm :: CardNum -> List CardNum
expAceElm cardNum = (identity : flipAce: Nil) <*> singleton cardNum

-- | エースを考慮して、カードの番号のリストをあり得る可能性に展開する
-- | 例　(A : 2 : 3 : 4 : 5 : Nil) -> ((A : 2 : 3 : 4 : 5 : Nil) : (1 : 2 : 3 : 4 : 5 : Nil) : Nil)
expAce :: List CardNum -> List (List CardNum)
expAce = map expAceElm >>> transpose >>> nub

-- | ジョーカーを考慮して、カードの番号をあり得る可能性に展開する
expJokerElm :: CardNum -> List CardNum
expJokerElm Joker = normalCardNums
expJokerElm cardNum = replicate cardinalityCardNum cardNum

-- | ジョーカーを考慮して、カードの番号のリストをあり得る可能性に展開する
-- | 例　(Joker : 2 : 3 : 4 : 5 : Nil) -> ((A : 2 : 3 : 4 : 5 : Nil) : (2 : 2 : 3 : 4 : 5 : Nil) : ... : (K : 2 : 3 : 4 : 5 : Nil) : Nil)
expJoker :: List CardNum -> List (List CardNum)
expJoker = map expJokerElm >>> transpose >>> nub

-- | エースとジョーカーを考慮して、カードの番号をあり得る可能性に展開する
expAceJokerElm :: CardNum -> List CardNum
expAceJokerElm = expJokerElm >=> expAceElm

-- | エースとジョーカーを考慮して、カードの番号のリストをあり得る可能性に展開する
expAceJoker :: List CardNum -> List (List CardNum)
expAceJoker = map expAceJokerElm >>> transpose >>> nub

-- | エースとジョーカーを考慮して、スートをあり得る可能性に展開する
expStJokerElm :: Suit -> List Suit
expStJokerElm StJoker = suits
expStJokerElm suit = replicate cardinalitySuit suit

-- | エースとジョーカーを考慮して、カードをあり得る可能性に展開する
expCardElm :: Card -> List Card
expCardElm card = (expSuit >=> expCardNum) card where
  expSuit c = zip (expStJokerElm $ fst c) (replicate cardinalitySuit $ snd c)
  expCardNum c = zip (replicate (cardinalityCardNum * length (Ace : One : Nil)) $ fst c) (expAceJokerElm $ snd c)

-- | エースとジョーカーを考慮して、カードのリストをあり得る可能性に展開する
expCard :: List Card -> List (List Card)
expCard = map expCardElm >>> transpose >>> nub
```

* Card.pursについて
内部的にはエースと１を両方持った方が処理しやすそうだったので、カードとしては、１から１３、エースとジョーカーを定義することにしました。isPredやisSuccはもっといい書き方あるような気がするので直したいですね。

* ジョーカーとエースの扱いとMain.pursについて
expCard関数で、上記の「ジョーカーやエースを含む手札からあり得る可能性を全て列挙する」が実現されています。