The Lazy.Force Awakens #FsAdvent

この記事は [F# Advent Calendar 2015](http://connpass.com/event/22056/) の19日目の記事です。 18日目は @CallMeKohei さんの 「[コンソールにマリオを描いてみた！](http://callmekohei.hatenablog.com/entry/2015/12/17/123955)」でした。

# The Lazy.Force Awakens

最近なんか「Forceの覚醒」とか「Forceを使え」という言葉を聞きます。そういえばF#にもForceがありますので、今日はF#のForceについて調べてみます。

## Lazy.Force()

F#は正格評価の言語ですが、たまには遅延評価を使いたい時もあります。そんな時は評価を遅延させたい式を`lazy (式)`という風にlazyでくるみます。

`lazy`でくるまれた中の式が`'a`型を返す場合、`lazy`全体の式が返す型は`Lazy<'a>`型になります。この時点では`lazy`にくるまれた中の式はまだ評価されません。

そして`Lazy<'a>`型の値から元の`'a`型の値を取り出す際に初めて`lazy`にくるまれた中の式が評価されます。この時、元の値を取り出すためのメソッドが`Force()`です。

なお、一度既に評価された式の値はキャッシュされ、2回目以降の`Force()`の呼び出しには単にキャッシュされた値を返すだけになります。

```fsharp
let lazy1 = lazy (printfn "hoge"; 1)
// val lazy1 : Lazy<int> = 値は作成されていません。

// The Force Awakens
lazy1.Force()
// hoge
// val it : int = 1

lazy1.Force()
// val it : int = 1
```

1回目の`lazy1.Force()`の呼び出し時に初めて`lazy`でくるまれた式が評価され、副作用の`printfn "hoge"`が実行された後に式の結果の`1`を返します。

2回目の`lazy1.Force()`の呼び出し時は単にキャッシュされた式の結果`1`を返しているだけなので、副作用の`printfn "hoge"`は実行されていません。

なお、`lazy`は関数ではなく構文なので、以下のようなパイプラインで渡すような書き方はできません。

```fsharp
let lazy1 = (printfn "hoge"; 1) |> lazy
```

## 式の関数化による遅延実行との違い

わざわざ`lazy`を使わなくても、似たような遅延実行を行うことは実は可能です。単に遅延実行させたい式を関数化してしまえばいいだけです。

ただ`lazy`を使った場合と異なるのは、式の評価結果が自動的にキャッシュされるわけではないため毎回式が評価されることになります。

```fsharp
let func1 = fun () -> printfn "fuga"; 2
// val func1 : unit -> int

func1()
// fuga
// val it : int = 2

func1()
// fuga
// val it : int = 2
```

他の例として、mutableな値を参照する式について、両者の違いを見てみます。

```fsharp
let mutable s = "hoge"
let lazy2 = lazy (s)
let func2 = fun () -> s

lazy2.Force()
// val it : string = "hoge"
func2()
// val it : string = "hoge"

s <- "fuga"
lazy2.Force()
// val it : string = "hoge"
func2()
// val it : string = "fuga"
```

副作用がない式は毎回評価しても必ず同じ値を返すので、2回目以降は計算せずともキャッシュを返せば事足りるはずです。従って副作用がない式の評価を遅延させたい場合は`lazy`を使えばよいでしょう。逆に副作用がある式の評価を遅延させたい場合は式の関数かを使う必要があります。

## Lazy<'a> と unit -> 'a の相互変換

`unit -> 'a`の関数を`Lazy<'a>`に変換したり、その逆変換を行うことは可能でしょうか？

まず`unit -> 'a`→`Lazy<'a>`の変換ですが、それを可能とする関数`Lazy.Create`が用意されています。

```fsharp
// unit -> 'a を Lazy<'a> に変換する関数
let lazy3 = func1 |> Lazy.Create
// val lazy3 : Lazy<int> = 値は作成されていません。

lazy3.Force()
// fuga
// val it : int = 2

lazy3.Force()
// val it : int = 2
```

実は`Lazy.Create`と同じことが`Lazy`型のコンストラクタ呼び出しで可能です。F# 4以降はコンストラクタが関数と同じ扱いとなり、パイプラインで扱いやすくなったりしたので、もはや`Lazy.Create`を使う意味はないかもしれません。

```fsharp
// System.Lazyクラスのコンストラクタ呼び出し。F#4以降は関数のように扱える
let lazy3' = func1 |> Lazy
```

逆に`Lazy<'a>`→`unit -> 'a`の変換ですが、シグネチャ上の変換であれば`Lazy.Force`がそれに当たります。

しかし関数のシグネチャに変換された後でもキャッシュされる機能は残ったままなので、結局変換後の関数を何度呼び出しても式の評価は初回しかされません。

```fsharp
// Lazy<'a> を unit -> 'a に(シグネチャだけ)変換する方法
let func3 = lazy1.Force
// val func3 : (unit -> int)

func3()
// hoge
// val it : int = 1

func3()
// val it : int = 1
```

## The dark side of the Force

Forceを使うものは時として「例外」という暗黒面に堕ちる場合があります。

`lazy`内の式の評価中に例外が発生した場合はどうなるのでしょうか？

結論としては、`Force()`実行時に普通に例外が投げられます。そしてこの例外もキャッシュされ、2回目以降の呼び出し時は即座にキャッシュされた例外が投げられます。

(ただし例外がキャッシュされるのは後述する`LazyThreadSafetyMode`が`PublicationOnly`以外の場合、かつ、Lazyコンストラクタで評価用の関数を渡した場合のみです。`lazy`構文を使用した場合は普通にキャッシュされます)

```fsharp
// lazy内で例外が出た場合は例外がキャッシュされて、評価のたびにキャッシュされた例外が投げられる
let mutable x = "Jedi"
let f() = if x = "Jedi" then 0 else failwithf "oops! : %s" x
let lazyDarkSide = f |> Lazy

x <- "Darth Vador"
lazyDarkSide.Force()
(*
System.Exception: oops! : Darth Vador
>    場所 FSI_0233.f@65-11.Invoke(String message) 場所 force.fsx:行 65
   場所 System.Lazy`1.CreateValue()
   場所 System.Lazy`1.LazyInitValue()
   場所 <StartupCode$FSI_0234>.$FSI_0234.main@() 場所 force.fsx:行 69
エラーのため停止しました
*)

x <- "Jedi"
lazyDarkSide.Force()
(*
> System.Exception: oops! : Darth Vador
   場所 FSI_0233.f@65-11.Invoke(String message) 場所 force.fsx:行 65
   場所 System.Lazy`1.CreateValue()
--- 直前に例外がスローされた場所からのスタック トレースの終わり ---
   場所 System.Lazy`1.get_Value()
   場所 <StartupCode$FSI_0236>.$FSI_0236.main@() 場所 force.fsx:行 80
エラーのため停止しました
*)

x <- "Anakin Skywalker"
lazyDarkSide.Force()
(*
System.Exception: oops! : Darth Vador
>    場所 FSI_0233.f@65-11.Invoke(String message) 場所 force.fsx:行 65
   場所 System.Lazy`1.CreateValue()
--- 直前に例外がスローされた場所からのスタック トレースの終わり ---
   場所 System.Lazy`1.get_Value()
   場所 <StartupCode$FSI_0240>.$FSI_0240.main@() 場所 force.fsx:行 92
エラーのため停止しました
*)
```

## The Multi-thread of the Force

`lazy`内の式の評価時にはロックを取って行われるためスレッドセーフになります(`lazy`内での式中にて使用される型がすべてスレッドセーフである前提ですが)。

ただしキャッシュされた値を取得するだけの場合でもロックを取ることになるため、その分パフォーマンスに影響を及ぼす可能性があります。また、ロックが発生するということは当然デッドロックの危険性もあります。

`lazy`構文を使わずに`Lazy<'a>`型のコンストラクタを使用することで、ロックを取るかどうかを指定することができます。

```fsharp
// Lazyのコンストラクタの別バージョン。シングルスレッド用
let lazy4 = Lazy((fun () -> printfn "piyo"; 3), false)
// Lazyのコンストラクタの別バージョン。初期化処理は同時に行われるがキャッシュは1つだけ作成される
let lazy5 = Lazy((fun () -> printfn "foo"; 4), System.Threading.LazyThreadSafetyMode.PublicationOnly)
```

指定の方法は`bool`をパラメータに渡す方法と`System.Threading.LazyThreadSafetyMode`を渡す方法の2種類があります。

* `true`または`LazyThreadSafetyMode.ExecutionAndPublication`を渡した場合、最初に実行するスレッドがロックを取って式の評価と評価結果の値のキャッシュを行います。2回目以降の実行時はキャッシュ読み込みのためにロックを取ります。`lazy`構文を使用した場合はこれになります。
* `false`または`LazyThreadSafetyMode.None`を渡した場合、シングルスレッドで使用される前提となり、ロックを取りません。複数スレッドから使用された場合の動作は未定義です。
* `LazyThreadSafetyMode.PublicationOnly`を渡した場合、同時に式の評価が行われる可能性がありますが、キャッシュされるのは最初に実行されたスレッドでの結果のみで、他のスレッドでの実行結果は捨てられます。

通常は`lazy`構文を使い、シングルスレッドのみで使われることがわかっていてパフォーマンス重視の場合は`false`を渡すコンストラクタを使用すればよいでしょう。

## Lazy型のその他の操作

`Lazy<'a>`型には今まで紹介した以外にもコンストラクタやプロパティを持っていますので、一気に紹介します。ついでにコンストラクタに`LazyThreadSafetyMode.PublicationOnly`を渡した場合に例外がキャシュされないことの確認もしてみました。

```fsharp
// Lazyのコンストラクタの別バージョン。シングルスレッド用
let lazy4 = Lazy((fun () -> printfn "piyo"; 3), false)
// Lazyのコンストラクタの別バージョン。初期化処理は同時に行われるがキャッシュは1つだけ作成される
let lazy5 = Lazy((fun () -> printfn "foo"; 4), System.Threading.LazyThreadSafetyMode.PublicationOnly)
// Lazyのコンストラクタの別バージョン。指定した型のデフォルトコンストラクタを遅延呼び出しする
let lazyDefaultInt = Lazy<int>()

lazy4.IsValueCreated
// val it : bool = false

// Force()を使わなくてもValueプロパティで評価できる
lazy4.Value
// piyo
// val it : int = 3

lazy4.Value
// val it : int = 3

lazy4.IsValueCreated
// val it : bool = true

// Force()を使わなくてもパターンマッチで評価できる
lazy5 |> fun (Lazy x) -> x
// foo
// val it : int = 4

let lazy6 = Lazy(f, System.Threading.LazyThreadSafetyMode.PublicationOnly)

x <- "Darth Vador"
lazy6.Force()
(*
System.Exception: oops! : Darth Vador
>    場所 FSI_0233.f@65-11.Invoke(String message) 場所 force.fsx:行 65
   場所 System.Lazy`1.CreateValue()
   場所 System.Lazy`1.LazyInitValue()
   場所 <StartupCode$FSI_0243>.$FSI_0243.main@() 場所 force.fsx:行 126
エラーのため停止しました
*)

x <- "Jedi"
lazy6.Force()
// val it : int = 0
```

実は`Force()`使わなくても`Value`プロパティでよかったんですね。`Force`とはいったい……うごごご！！

[この記事で使用したソースコード](https://gist.github.com/adacola/e6d5acfbde0b5d042047)