すごいH本で見落としがちだが実は重要な機能：newtype

すごいH本を初めて読んだ方なら「newtypeが何の役に立つのか全く分からない」と思ったことがあると思います。実はnewtypeは堅牢なアプリケーションを構築する上で非常に重要な機能です。

##　概要
### newtypeとは
すごいH本にはnewtypeに関して以下の記述があります。

> newtypeは「1つの型を取り、それを何かに包んで別の型に見せかけたい」という場合のために作られたもの

この「別の型に見せかける」ことが安全かつ利用しやすいhaskellコードを書く上で重要になってきます。

## 図書館プログラムにnewtypeを使用する
今回は簡単な図書館アプリケーションを構築し、その上でnewtypeがいかに有用なのかを紹介したいと思います。
図書館ではユーザーが本を借りることができます。

今回はユーザー、本、貸出履歴を以下のように表現します。

```haskell
-- | ユーザー（図書館利用者）
data User = User
    { uId   :: !Integer
    , uName :: !Text
    } deriving (Eq, Show)

-- | 本
data Book = Book
    { bId     :: !Integer
    , bAuthor :: !Text
    , bTitle  :: !Text
    } deriving (Eq, Show)

-- | 貸出履歴
data Record = Record
    { rId       :: !Integer
    , rBookId   :: !Integer
    , rUserId   :: !Integer
    } deriving (Eq, Show)

type LibraryDB = [Record]
```
注目してほしいのはそれぞれの`Id`です。初期バージョンではそれぞれを単なる`Integer`として表現します。

また本を貸し出す関数`borrowBook`は以下の流れで処理を行います。
1.`LibraryDB`にアクセスし、貸出履歴があるか確認する。
2. ある場合にはその履歴を返す。
3. ない場合には新たなレコードを定義し、`LibraryDB`に追加する。

加えて`BookId`, `RecordId`から貸出履歴を参照する関数も実装してみましょう。

## 初期版

```haskell
import Data.Text (Text)

data User = User
    { uId   :: !Integer
    , uName :: !Text
    } deriving (Eq, Show)

data Book = Book
    { bId     :: !Integer
    , bAuthor :: !Text
    , bTitle  :: !Text
    } deriving (Eq, Show)

data Record = Record
    { rId       :: !Integer
    , rBookId   :: !Integer
    , rUserId   :: !Integer
    } deriving (Eq, Show)

type LibraryDB = [Record]

borrowBook :: Integer -> Integer -> LibraryDB -> Either Record LibraryDB
borrowBook bookId userId db =
    case lookupRecordByBookId bookId db of
        Just record -> Left record
        Nothing -> do
            let newRecordId = getNewRecordId db
                newRecord = Record newRecordId bookId userId
            return $ newRecord:db
  where
    getNewRecordId :: [Record] -> Integer
    getNewRecordId = fromIntegral . length

lookupRecordByBookId :: Integer -> LibraryDB -> Maybe Record
lookupRecordByBookId bookId = lookupRecord bookId rBookId

lookupRecordById :: Integer -> LibraryDB -> Maybe Record
lookupRecordById recordId = lookupRecord recordId rId

lookupRecord :: (Eq a) => a -> (Record -> a) -> LibraryDB -> Maybe Record
lookupRecord _ _ []   = Nothing
lookupRecord someId getter (r:rs)
    | someId == getter r = Just r
    | otherwise          = lookupRecord someId getter rs

```
### 問題点
`Integer`が何を表現しているのか不明です。とくに`borrowBook`関数に関しては引数として`Integer`を２つ受け取っており、どちらが`BookId`、`UserId`なのかはその実装内容を確認しない限り全くわかりません。
参照関数に関しても、現状では任意の`Integer`を引数として受け取る関数となっているので、誤って`UserId`を渡して予期しない結果を出力する可能性が高いでしょう。

## 型シノニム(type)
それでは型シノニムを利用し、`Integer`が何を表現しているのかを明示的に表記しましょう。

```haskell
import Data.Text (Text)

type UserId = Integer

data User = User
    { uId   :: !UserId
    , uName :: !Text
    } deriving (Eq, Show)

type BookId = Integer

data Book = Book
    { bId     :: !BookId
    , bAuthor :: !Text
    , bTitle  :: !Text
    } deriving (Eq, Show)

type RecordId = Integer

data Record = Record
    { rId       :: !RecordId
    , rBookId   :: !BookId
    , rUserId   :: !UserId
    } deriving (Eq, Show)

type LibraryDB = [Record]

borrowBook :: BookId -> UserId -> LibraryDB -> Either Record LibraryDB
borrowBook bookId userId db =
    case lookupRecordByBookId bookId db of
        Just record -> Left record
        Nothing -> do
            let newRecordId = getNewRecordId db
                newRecord = Record newRecordId bookId userId
            return $ newRecord:db
  where
    getNewRecordId :: [Record] -> RecordId
    getNewRecordId = fromIntegral . length

lookupRecordByBookId :: BookId -> LibraryDB -> Maybe Record
lookupRecordByBookId bookId = lookupRecord bookId rBookId

lookupRecordById :: RecordId -> LibraryDB -> Maybe Record
lookupRecordById recordId = lookupRecord recordId rId

lookupRecord :: (Eq a) => a -> (Record -> a) -> LibraryDB -> Maybe Record
lookupRecord _ _ []   = Nothing
lookupRecord someId getter (r:rs)
    | someId == getter r = Just r
    | otherwise          = lookupRecord someId getter rs

```
一見これで問題は解決されたようにみえます。

#### 問題点
`BookId`, `UserId`, `RecordId` は**単なる型シノニム**なので、暗黙的には`Integer`と同じ型です。よって`BooKId == UserId == RecordId == Integer`となるため、以下のような誤った使い方をしたとしてもエラーとなりません。

```terminal
*> let bookId = 11
*> let userId = 10
-- 渡すIdの順番が間違ってる
*> JustType.borrowBook userId bookId []
Right [Record {rId = 0, rBookId = 10, rUserId = 11}]
```

開発者からすれば「型シグネチャに`BookId`とあるやん！なんで`UserId`を渡したの？」と思うかもしれませんが、他の人からすれば「なんで渡すパラメーターを間違えたのにエラーが出ないの？」と言うでしょう。

これを解決するのが`newtype`です。

## newtype
それでは今度は`BookId`,`UserId`,`RecordId`をそれぞれ`newtype`でくるんでみましょう。(開発チームではこれを"Wrap it with newtype"と表現しています。)

```haskell
import Data.Text (Text)

newtype UserId = UserId 
    { getUserId :: Integer 
    } deriving (Eq, Show)

data User = User
    { uId   :: !UserId
    , uName :: !Text
    } deriving (Eq, Show)

newtype BookId = BookId
    { getBookId :: Integer
    } deriving (Eq, Show)

data Book = Book
    { bId     :: !BookId
    , bAuthor :: !Text
    , bTitle  :: !Text
    } deriving (Eq, Show)

newtype RecordId = RecordId
    { getRecordId :: Integer
    } deriving (Eq, Show)

data Record = Record
    { rId       :: !RecordId
    , rBookId   :: !BookId
    , rUserId   :: !UserId
    } deriving (Eq, Show)

type LibraryDB = [Record]

borrowBook :: BookId -> UserId -> LibraryDB -> Either Record LibraryDB
borrowBook bookId userId db =
    case lookupRecordByBookId bookId db of
        Just record -> Left record
        Nothing -> do
            let newRecordId = getNewRecordId db
                newRecord = Record newRecordId bookId userId
            return $ newRecord:db
  where
    getNewRecordId :: [Record] -> RecordId
    getNewRecordId = RecordId . fromIntegral . length

lookupRecordByBookId :: BookId -> LibraryDB -> Maybe Record
lookupRecordByBookId bookId = lookupRecord bookId rBookId

lookupRecordById :: RecordId -> LibraryDB -> Maybe Record
lookupRecordById recordId = lookupRecord recordId rId

lookupRecord :: (Eq a) => a -> (Record -> a) -> LibraryDB -> Maybe Record
lookupRecord _ _ []   = Nothing
lookupRecord someId getter (r:rs)
    | someId == getter r = Just r
    | otherwise          = lookupRecord someId getter rs
```
型シノニムと違い、newtypeを使用すると`BookId`型と`UserId`型は**別の型**をみなされます。
(`UserId /= BookId /= RecordId`)。
よって先ほどのように誤って最初の引数に`userId`を渡そうとすると、型が一致しないためエラーとなります。

実際に使ってみると：

```terminal
*> let bookId = BookId 11
*> let userId = UserId 10
*> Newtype.borrowBook bookId userId []
Right [Record {rId = RecordId {getRecordId = 0}, rBookId = BookId {getBookId = 11}, rUserId = UserId {getUserId = 10}}]
```
となります。

### まとめ
newtypeにくるむことによってそれぞれの`Id`に、型レベルで明確な意味をもたせることができました。
もし堅牢なアプリケーションを構築するのであれば全てのフィールドをnewtypeでくるむのが望ましいでしょう。

```haskell
newtype BookId = BookId
    { getBookId :: Integer
    } deriving (Eq, Show)

newtype Title = Title
    { getTitle :: Text
    } deriving (Eq, Show)

newtype Name = Name
    { getName :: Text
    } deriving (Eq, Show)

data Book = Book
    { bId     :: !BookId
    , bAuthor :: !Name
    , bTitle  :: !Title
    } deriving (Eq, Show)
```
一見、冗長的で面倒に見えますが、これがエラー耐性のあるアプリケーションの構築につながります。


### 本当に堅牢なのか
`newtype`を利用したとしても、間違った関数を実装することは十分可能です。

```haskell
evilFun :: BookId -> UserId -> Integer
evilFun bookId userId =
   let bookNum = getBookId bookId -- Integer
       userNum = getUserId userId -- Integer
   in bookNum + userNum -- What...?
```

なのでそのようなことはしないよう気をつけましょう。