[Haskell] 数値リテラルの活用

本当は　　*[Haskell] リテラルのあれこれ（数値・リスト・文字列）*　　というタイトルで、リストや文字列のリテラルについても触れるつもりでしたが、思ったより長くなってしまったのでとりあえず数値リテラルについて。

お手柔らかに　(*´σｰ｀)ｴﾍﾍ

# はじめに
HaskellではC言語などのように浮動小数点型のリテラルを、`3.0`のように明示的に小数点をつけて書く必要はありません。
以下のコードはコンパイルを通ります。

```hs:literal
fooInt :: Int
fooInt = 3

fooInteger :: Int
fooInteger = 3

fooFloat :: Float
fooFloat = 3

fooDouble :: Double
fooDouble = 3
```

型推論がそれぞれの「`3`」の型を `Int`,`Integer`,`Float`,`Double` に決定してくれるからですね。

では以下のコードはどうでしょう？

```hs:literalNewtype
newtype MyInteger = MyInteger Integer

fooMyInteger :: MyInteger
fooMyInteger = 3
```

このコードはこのままではコンパイルを通りません。

ですが、少しコードを書き足すことでこのコードもコンパイルを通るようになります。
つまり、自分で定義した型についても組み込みの型と同じようなリテラルが使えるということです。

この記事では、

- 数値リテラルを自作の型で使う方法
- 数値リテラルを書きやすくする言語拡張

について説明します。


# 数値リテラル
A Gentle Introduction To Haskell（[原文](https://www.haskell.org/tutorial/numbers.html)・[日本語版](http://www.sampou.org/haskell/tutorial-j/numbers.html)）によると、整数リテラルと小数リテラルは、それぞれ以下のように解釈されます

```hs:literal
foo = 3
-- ↓
foo = fromInteger 3

bar = -3
-- ↓
bar = negate (fromInteger 3)

baz = 3.0
-- ↓
baz = fromRational 3.0
```

これらの関数 `fromInteger`, `negate` および `fromRational` はそれぞれ、`Num`クラスと`Fractional`クラスで多相的に定義されているものです。

つまり、自作の型はこれらのクラスのインスタンスにすれば組み込み型と同じようなリテラルが使えます。

```hs:literalNewtype
newtype MyInteger = MyInteger Integer

instance Num MyInteger where
  fromInteger = MyInteger

fooMyInteger :: MyInteger
fooMyInteger = 3
```

上記のコードはコンパイルを通ります。
（面倒くさくて`(+)`などの定義を省略しました。簡単に定義するには、後述の`GeneralizedNewtypeDeriving`拡張を使ってください）

## ためしに乱用してみる
`Bool`を`Num`クラスのインスタンスにすることで数値リテラルで`Bool`を表現してみます。

**バグの元だから *絶対* マネしないでね。**

```hs:bool.hs
instance Num Bool where
  (+) = (||)
  (*) = (&&)
  abs = const True
  signum = id
  negate = not
  fromInteger = (> 0)
```
正の数を`True`、`(+)`を論理和、`(*)`を論理積で表現してみました。
ちょっと試してみます。

```hs:bool.hs
main = do
  print (-1 +   1  :: Bool) -- False || True
  print (-1 + (-1) :: Bool) -- False || False
  print ( 1 *   1  :: Bool) -- True && True
  print (-1 * (-1) :: Bool) -- False && False
```

```c:output
True
False
True
True
```

なんだか、最後の出力が思っていたのと違いますね。
このようなことが起こる理由は`NegativeLiterals`拡張の紹介と一緒に説明します。

## 負数リテラル（`NegativeLiterals`拡張）
A Gentle Introduction To Haskell（[原文](https://www.haskell.org/tutorial/numbers.html)・[日本語版](http://www.sampou.org/haskell/tutorial-j/numbers.html)）によると、単項演算子"`-`"は二項演算子の`(-)`と同じ結合優先度を持ちます。

また、栄光のグラスゴーHaskellコンパイラ利用の手引［[link](http://www.kotha.net/ghcguide_ja/7.8.2/syntax-extns.html#negative-literals)］によるとデフォルトでは負数リテラル -3 は`negate (fromInteger 3)`のように展開されます。

つまり、`-1 * (-1)`は`-(1 * (-1))`と解釈され、
`negate (fromInteger 1 * negate (fromInteger 1))`
と展開されます。

これで先程の最後の出力が`True`になった理由が理解できました。

`NegativeLiterals`拡張を使うとこの展開の仕方を変更できます。
リテラルの -3 はこの拡張が有効になっている間は`fromInteger (-3)`と展開されます。

よって、`-1 * (-1)`は
`(fromInteger (-1) * fromInteger (-1))`
と展開されます。

これにより、先程の例の最後の出力は`Fasle`になります。

## 小数風整数リテラル（`NumDecimals`拡張）
`NumDecimals`拡張を使うと、整数リテラルを小数リテラルのように指数表示を使って書けるようになります。

```hs:numDecimals
main = print (1.25e5 :: Int)
```

```foo:output
125000
```

大きな整数を扱う必要があるときにはなかなか便利。

## 2進数リテラル（`BinaryLiterals`拡張）
Haskellでは10進法、8進法、16進法の整数リテラルは以下のように書くことができます。

```hs:literals.hs
main = do
  print 10
  print 0o10
  print 0x10
```

```foo:output
10
8
16
```

`BinaryLiterals`拡張を使うことで、2進数の整数リテラルも書くことができるようになります。

```hs:literals.hs
{-# LANGUAGE BinaryLiterals #-}
main = do
  print 0b10
```

```foo:output
2
```

Qiitaの色付けが対応してませんが、ちゃんと動きます。

## Numのインスタンスにするのが面倒くさい（`GeneralizedNewtypeDeriving`拡張）
`GeneralizedNewtypeDeriving`拡張を使うことで自作の型を簡単に`Num`クラスのインスタンスにできます。

この言語拡張は、`newtype`宣言で型をラッピングするときに、もとの型がインスタンスであったようなクラスを`deriving`節に書くことで、もとの型のインスタンス宣言を使えるようになるものです。[[参考](http://www.kotha.net/ghcguide_ja/7.8.2/deriving.html#newtype-deriving)]

百聞は一見にしかず、ということで実際に使ってみましょう。

```hs:generalized.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype MyInteger = MyInteger Integer deriving Num

fooMyInteger :: MyInteger
fooMyInteger = 3
```

このように、`deriving`節に`Num`を書くことで、`Integer`型の`Num`の実装がそのまま自作の型`MyInteger`でも使えるようになり、数値リテラルも使えるようになります。

この言語拡張は既存のモナドをラッピングする目的でもよく使われるものです。

「型安全のために既存の型をラッピングしたいけど、必要なクラスのインスタンスにするのが面倒くさい」という場面は多いと思われますが、この言語拡張を使えばそれも解決します。

# まとめ

- 数値リテラルには`fromInteger`,`negate`,`fromRational`関数が関係している。
- `NegativeLiterals`拡張で負数リテラルの展開の仕方を変えられる。
- `NumDecimals`拡張で整数リテラルにも指数表記が使える。
- `BinaryLiterals`拡張で2進数整数リテラルが使える。
- `newtype`宣言で型をラッピングするなら`GeneralizedNewtypeDeriving`拡張がオススメ。

数値リテラルを使いこなして、簡潔で型安全なコードを書きましょう！

# あとがき
`Bool`を`Num`クラスにするやつは、`NegativeLiterals`拡張の説明のために書いただけなので、**絶対に** 実用コードで真似しないでください（バグの温床になることうけあいです）

自作の型を`Num`クラスにすることで数値リテラルを使えるようにする例としては [algebraic-graphs](https://hackage.haskell.org/package/algebraic-graphs-0.0.5/docs/Algebra-Graph.html) というグラフ代数のライブラリが面白いです。頂点を1つしか持たないグラフを整数リテラルで書けるようにしています。

### どうしてもやりたかったオマケ
```hs:Int1984.hs
newtype Int1984 = Int1984 Int

instance Num Int1984 where
  (+) (Int1984 x) (Int1984 y) = Int1984 (x + y + 1)
  fromInteger = Int1984 . fromInteger

instance Show Int1984 where
  show (Int1984 x) = show x

```

```hs:ghci
ghci> 2 + 2 :: Int1984
5
```

P.S.
お手柔らかにつっこんでね　(*´σｰ｀)ｴﾍﾍ