F# の inline 関数中で Haskell のような多相数値リテラルを扱う

最近少し F# を触ってみています。

F# では `let inline` で関数定義をすることでインライン関数を定義することができるようになっており、この中では SRTP (Statically Resolved Type Parameter) と呼ばれる特殊な型変数が使えるようになっています。
詳しくは [こちらの記事](https://qiita.com/cannorin/items/59d79cc9a3b64c761cd4) を参照してみてください。

SRTP を用いることで、通常は認められないような型クラスに相当する型制約が実現できます。
ちょっと具体例を見てみましょう。

```fsharp
// 通常の関数定義では以下の関数は多相にはならず、オーバーロードされた (+) のデフォルト実装 int -> int -> int が使われる.
> let add x y = x + y ;;
val add : x:int -> y:int -> int

// SRTP を用いることで、(+) の実装が存在するあらゆる型に対して適用可能な関数となる.
> let inline add (x : ^T when ^T : (static member (+) : ^T -> ^T -> ^T))
                 (y : ^T) =
    x + y ;;
val inline add :
  x: ^T -> y: ^T ->  ^T when  ^T : (static member ( + ) :  ^T *  ^T ->  ^T)

// SRTP は明示的に書かずとも型推論によって導出できる.
> let inline add x y = x + y ;;
val inline add :
  x: ^a -> y: ^b ->  ^c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c)
```

通常の多相関数の型変数は `'a` や `'T` と表記されますが、SRTP は `^a` や `^T` のように表記されます。

`let inline` で定義した `add` 関数は `(+)` の実装が存在する型ならばどのような型に対しても適用することができます。

```fsharp
> add 1 2 ;;
val it : int = 3

> add 1L 2L ;;
val it : int64 = 3L

> add 1.0 2.0 ;;
val it : float = 3.0

> add 1.0f 2.0f ;;
val it : float32 = 3.0f
```

便利ですね。

では、この SRTP を利用して任意の数値型に対して `10` を加える関数を書いてみたいとしましょう。
以下のように書けそうですが...

```fsharp
> let inline add10 x = x + 10 ;;
val inline add10 : x:int -> int
```

しかしこのように書いてしまうと `int -> int` と推論されてしまいます。これは `10` という数値リテラルが `int` 型であると推論されるためです。
Haskell では数値リテラルは多相定数として扱われますが、F# では OCaml と同様に数値リテラルは多相ではないため、このように数値リテラルをそのまま使用してしまうと SRTP を使っても多相が実現できないという問題が生じます。


##  解決策

[ここ](https://stackoverflow.com/questions/13016257/using-fs-static-type-parameters-and-encoding-numeric-constants) や [ここ](https://stackoverflow.com/questions/4732672/how-to-write-a-function-for-generic-numbers) でも紹介されていますが、`NumericLiteralG` のように `NumericLiteral` の後ろに `Q`、`R`、`Z`、`I`、`N`、`G` のいずれかのアルファベットを付与した名前のモジュールを定義することで多相数値リテラルを独自に定義するという方法が使われるようです。

これは [F# の仕様書](https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf) では 6.3.1 Simple Constant Expressions で説明されており、**`Q`、`R`、`Z`、`I`、`N`、`G` のいずれかの文字を数値リテラルの suffix として使用すると、その suffix に対応するモジュールの関数呼び出しへと syntactic translation が行われる** という性質を利用したものです。

例えば suffix として `G` を使用する場合、`100G` のようにして通常の数値リテラルの後ろにこの suffix を記述すると、これが `NumericLiteralG.FromInt32(100)` という関数呼び出しへと変換されることとなります。

以下に F# の仕様書に記載されている変換ルールを転載します。

```
xxxx<suffix>
  For xxxx = 0                 →  NumericLiteral<suffix>.FromZero()
  For xxxx = 1                 →  NumericLiteral<suffix>.FromOne()
  For xxxx in the Int32 range  →  NumericLiteral<suffix>.FromInt32(xxxx)
  For xxxx in the Int64 range  →  NumericLiteral<suffix>.FromInt64(xxxx)
  For other numbers            →  NumericLiteral<suffix>.FromString("xxxx")
```

したがって、以下のようにして `FromZero`、`FromOne`、`FromInt32` といった関数を `let inline` で定義したモジュールを用意することで、SRTP と一緒に使える多相数値リテラルを実現することができます。

```fsharp
module NumericLiteralG = 
    let inline FromZero() = LanguagePrimitives.GenericZero
    let inline FromOne() = LanguagePrimitives.GenericOne
    let inline FromInt32 (n:int) =
        let one : ^a = FromOne()
        let zero : ^a = FromZero()
        let n_incr = if n > 0 then 1 else -1
        let g_incr = if n > 0 then one else (zero - one)
        let rec loop i g = 
            if i = n then g
            else loop (i + n_incr) (g + g_incr)
        loop 0 zero 
```

やっていることは基本的に `LanguagePrimitives.GenericZero : ^a` および `LanguagePrimitives.GenericOne : ^a` の加算を繰り返すことで任意の整数値を計算するという単純なものです。


##  FSharpPlus を使う

このようなモジュールを用意すれば多相的な数値リテラルを扱うことはできるのですが、いちいち自分でこんなモジュールを用意するのは面倒ですね。
`FSharpPlus` ライブラリには既にこのようなモジュールが用意されているので、このライブラリを使えばすぐに多相数値リテラルが使えます。

`FSharpPlus` は NuGet で配布されており、.NET Core では `dotnet` コマンドから簡単にインストールできます。

```bash
$ dotnet add package FSharpPlus
```

インストールが問題なく行われれば、`.fsproj` ファイルに以下の内容が追記されるはずです。

```xml
<ItemGroup>
  <PackageReference Include="FSharpPlus" Version="1.0.0" />
</ItemGroup>
```

`FSharpPlus` の[ソースコード](https://github.com/fsprojects/FSharpPlus/blob/master/src/FSharpPlus/Operators.fs#L879) を見てみると、`FSharpPlus.Math.Generic` モジュールの中で `NumericLiteralG` モジュールが定義されています。
したがって `FSharpPlus.Math.Generic` モジュールを `open` することによって `100G` などの多相数値リテラルが使用できるようになります。
(`100G` のようなリテラルが `NumericLiteralG.FromInt32(100)` のように読み替えられることになるので、`NumericLiteralG` モジュールが見えるように `FSharpPlus.Math.Generic` モジュールを開いておく必要があります。)

早速試してみましょう。`dotnet fsi` で F# Interactive を起動します。

```fsharp
> #r "<ユーザのホームディレクトリ>/.nuget/packages/fsharpplus/1.0.0/lib/netstandard2.0/FSharpPlus.dll" ;;

> open FSharpPlus.Math.Generic ;;

> let inline add10 x = x + 10G ;;
val inline add10 :
  x: ^a ->  ^a
    when (FSharpPlus.Control.FromInt32 or  ^a) : (static member FromInt32 :  ^a *
                                                                            FSharpPlus.Control.FromInt32
                                                                              ->
                                                                              int32 ->
                                                                                 ^a) and
         (FSharpPlus.Control.Plus or  ^a) : (static member ( + ) :  ^a *  ^a *
                                                                   FSharpPlus.Control.Plus
                                                                     ->  ^a)

> add10 5 ;;
val it : int = 15

> add10 5L ;;
val it : int64 = 15L

> add10 5. ;;
val it : float = 15.0

> add10 5.f ;;
val it : float32 = 15.0f

> add10 5M ;;
val it : decimal = 15M
```

このように、簡単に多相数値リテラルが使えるようになります。

注意点としては、この多相数値リテラルは **整数としてしか書けない** という点が挙げられます。
したがって、`10G` とは書けても `10.0G` や `10.G` とは書けません。
これはちょっと不便ですが、通常の用途の範疇ならば多相数値リテラル同士の分数として表現すればよいのでそこまで大きな問題にはならないかと思います。

```fsharp
> let pi: float = 3141592654G / 1000000000G ;;
val pi : float = 3.141592654

> let pi: float32 = 3141592654G / 1000000000G ;;
val pi : float32 = 3.141592503f

> let pi: decimal = 3141592654G / 1000000000G ;;
val pi : decimal = 3.141592654M

> let pi: int = 3141592654G / 1000000000G ;;
val pi : int = -1

> let pi: int64 = 3141592654G / 1000000000G ;;
val pi : int64 = 3L
```

(上の例では `int` 型の値がおかしなことになっていますが、これは `3141592654` が 32 ビットの範囲では表現できない整数であるためです。)

```fsharp
> let i = int32 3121592564L ;;
val i : int32 = -1173374732

> let j = int32 1000000000L ;;
val j : int32 = 1000000000

> i / j ;;
val it : int32 = -1
```

`FSharpPlus` では `Functor` や `Monad` も提供されており、多相数値リテラルも使うことで Haskell のようなコードを書くことができます。

```fsharp
> open FSharpPlus ;;

// Functor
> map ((+) 10G) (Some 5) ;;
val it : int option = Some 15

> map ((+) 10G) (Some 5.) ;;
val it : float option = Some 15.0

> map ((+) 10G) [ 1L ; 2L ; 3L ] ;;
val it : int64 list = [11L; 12L; 13L]

> map ((+) 10G) [ 1.f ; 2.f ; 3.f ] ;;
val it : float32 list = [11.0f; 12.0f; 13.0f]


// Applicative
> (+) <!> Some 1. <*> Some 2G ;;
val it : float option = Some 3.0

> (+) <!> [| 1L ; 2L ; 3L |] <*> [| 10G ; 100G ; 1000G |] ;;
val it : int64 [] = [|11L; 101L; 1001L; 12L; 102L; 1002L; 13L; 103L; 1003L|]

> (+) <!> seq { 1I ; 2I } <*> seq { 10G } ;;
val it : seq<System.Numerics.BigInteger> =
  seq [11 {IsEven = false;
           IsOne = false;
           IsPowerOfTwo = false;
           IsZero = false;
           Sign = 1;}; 12 {IsEven = true;
                           IsOne = false;
                           IsPowerOfTwo = false;
                           IsZero = false;
                           Sign = 1;}]


// Monad
> Some 1L >>= (fun x -> Some (x + 100G)) ;;
val it : int64 option = Some 101L

> monad {
-   let! x = Some 10.f
-   let! y = Some 100G
-   return (x + y)
- } ;;
val it : float32 option = Some 110.0f

> monad {
-   let! x = [ 1M ; 2M ; 3M ]
-   let! y = [ 10G ; 100G ; 1000G ]
-   return (x + y)
- } ;;
val it : decimal list = [11M; 101M; 1001M; 12M; 102M; 1002M; 13M; 103M; 1003M]
```


##  References

*  <https://qiita.com/cannorin/items/59d79cc9a3b64c761cd4>
*  <https://stackoverflow.com/questions/13016257/using-fs-static-type-parameters-and-encoding-numeric-constants>
*  <https://stackoverflow.com/questions/4732672/how-to-write-a-function-for-generic-numbers>
*  <https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf>
*  <https://github.com/fsprojects/FSharpPlus/blob/master/src/FSharpPlus/Operators.fs#L879>
*  <http://tomasp.net/blog/fsharp-generic-numeric.aspx/>
*  <https://fsprojects.github.io/FSharpPlus/abstraction-functor.html>
*  <https://fsprojects.github.io/FSharpPlus/abstraction-applicative.html>
*  <https://fsprojects.github.io/FSharpPlus/abstraction-monad.html>