Getting a Good Grasp of F# (仮)

関数型言語F#をもっと楽しみたい

F# と Sodium でリアクティブ・プログラミング(2)

Sodium のCellとアキュームレータの作成

Sodium のStreamが値を持つのはイベント発生の瞬間であるのに対して、Cellはいったん獲得した値を保持できる。Cellの値は書き換え可能で、次の書き換えまでその値を保持しており、いつでも自由に読み出すことができる。

本記事は SodiumFRP.FSharp を使用する。

www.nuget.org

以下に示すサンプルコードの動作は、

  • コンソール画面で、キー入力を待つ
  • 入力した文字をコンソール画面に出力する。ただし、数字とアルファベット以外のキー入力は無視する。
  • 入力が数字キーならば、数字キーが累積で何回押下されたかをコンソール出力する。
  • 入力がアルファベットキーならば、アルファベットキーが累積で何回押下されたかをコンソール画面に出力する。
  •  Esc キーを押せば、入力受付をやめてアプリケーションを終了する。

サンプルコード


サンプルコードの解説
/// キー入力イベント用ストリーム
    let sKeyInput: StreamSink<ConsoleKeyInfo> = StreamSink.create()

7行目:キー入力イベントを流すStreamSink<ConsoleKeyInfo>としてsKeyInputを作成(StreamSinkStream型の派生クラス)。StreamSink型はsendS関数(49行目で使用)を使って明示的にイベントを送信できる。


use lKeyInput: IStrongListener =
        sKeyInput
        |> mapS (fun (ki: ConsoleKeyInfo) ->
            match ki.KeyChar with
            | c when Char.IsNumber c -> Some c 
            | c when Char.IsLetter c -> Some c
            | _ -> None)
        |> filterOptionS
        |> listenS (fun (c: char) -> printfn $" %c{c}")

9~17行目:キー入力イベント用ストリームsKeyInputStreamSink<ConsoleKeyInfo>型)を、mapS関数とfilterOptionS関数を使ってStream<char>に変換する。また、数字・アルファベット以外のキー入力イベントを破棄する。
更にlistenSを使ってリスナー(IStrongListener型)を作成(17行目)、イベントデータ処理用のリスナー関数を登録する。このリスナー関数では入力文字をそのままコンソール出力する。


/// 数字キーを押した回数を保持するセル
    let cNumberCounter: Cell<int> = 
        loopWithNoCapturesC (fun (lpcCount: LoopedCell<int>) -> 
            sKeyInput
            |> snapshotC lpcCount (fun (ki: ConsoleKeyInfo) (acc: int) -> 
                                                if Char.IsNumber ki.KeyChar then acc + 1 else acc) 
            |> calmS
            |> holdS 0)

20~26行目loopWithNoCapturesC関数を使って、数字キー押下の累積回数を保持するアキュームレータとなるcNumberCounterCell<int>型)を作成する。ストリームであるsKeyInputにてイベントが発生する毎に、lpcCountLoopedCell<int>型)の値を読み取り、数字キー入力と判定された場合のみカウンタ値をインクリメントして保持している。(LoopedCellCell型の派生クラス)

calmS関数を使うことにより、Streamで発生したイベントの値が直前イベントと重複した場合にはイベントを破棄している。


/// アルファベットキーを押した回数を保持するセル
    let cLetterCounter: Cell<int> =
        sKeyInput
        |> accumS 0 (fun (ki: ConsoleKeyInfo) (acc: int) -> 
            if Char.IsLetter ki.KeyChar then acc + 1 else acc)
        |> calmC

34~38行目accumS関数を使って、アルファベットキー押下の累積回数を保持するアキュームレータとなるcLetterCounterCell<int>型)を作成する。このとき、calmC関数を使うことにより、Cellの値の更新が発生しても既存の値から変化しないときはリスナー呼び出しを行わないように設定できる。

SodiumFRP.FSharp においては、アキュームレータを実現する方法は複数用意されている。本記事のサンプルコードのように、処理が単純なアキュームレータならば、21行目loopWithNoCapturesCを使うよりもaccumS関数の方が簡潔な記述ができる。

 

※ソースコードはGitHubで公開しています

《参考資料》

関数型リアクティブプログラミング Functional Reactive Programming

 

F# と Sodium でリアクティブ・プログラミング(1)

F# 向けのリアクティブ・ブログラミングには SodiumFRP.FSharp パッケージが利用できます。

Sodium においては、GUI のボタンクリックイベントやキーボードによるキー入力など何であれ、そういったイベント群をストリーム(Stream<'a>型)上に並べたデータ群として扱います。
SodiumFRP.FSharp では、イベント処理を、静的な F# のシーケンスリストに対する処理と同等に扱うコードが書けます。F# のパイプライン演算子も利用できるので便利です。

イベント処理結果の I/O 出力は、 Sodium ではストリームへ登録したリスナーから行います。

簡単な例として、キーボードから入力したキーコードが数字かアルファベットかを判定して、判定結果を文字列としてコンソールに出力するコードを書いてみます。(数字とアルファベット以外のキー入力は無視します。 Esc キーを押せばアプリケーションを終了します。)

f:id:pongitsune:20211006211318p:plain

ソースコード



6行目:イベント送信可能なストリームとしてStreamSink<ConsoleKeyInfo>を作成します。(StreamSink<'a>Stream<'a>の派生型です。)

7~16行目ConsoleKeyInfo型のデータのうち、数字とアルファベット入力だけを有効なイベントとして選抜し、イベントが運搬するデータ(ConsoleKeyInfo型)を表示用文字列(string型)に変換する新たなストリーム(Stream<'string>型)を作成します。

Stream<string option>に対する filterOptionalのフィルタ処理(16行目)ではNone以外の値を選抜し、string optionからstringへ型変換してイベントを通過させています。

18~20行目sStrOutput用のリスナー(IStrongListener型)を作成します。listenSで、イベントデータを受け取って処理する関数を登録します。(ここでは文字列をコンソール出力する関数を登録しています)

22~24行目:キー入力イベント群を表す無限長のシーケンス(ConsoleKeyInfo seq型)を作成しています。Escキー入力が来るまで無限にキー入力を受け入れます。

24行目sendS関数によってイベントを発生させてConsoleKeyInfo型のデータを送信しています。

26行目20行目sStrOutputへ登録したリスナーを登録解除します。

※ソースコードはGitHubで公開しています

 

●概念図

f:id:pongitsune:20211006223219p:plain

《参考資料》

関数型リアクティブプログラミング Functional Reactive Programming

F# クラスのプロパティ定義をシンプルに記述する

メンバとして public なプロパティ Namestring 型、読み書き可)を持つ単純なクラス Person1C# と F# 双方で定義してみます。

  • [注意] F# のクラスのアクセシビリティはデフォルトで public です。同様に、メソッドやプロパティもアクセシビリティはデフォルトで public です。ただしクラス内でlet束縛された値は  privateです。
C# と F# でクラス宣言のコード比較
public class Person1
{
    private string _name = "Tom";
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}
type Person1() =
    let mutable _name = "Tom"
    member __.Name  
        with get(): string = _name
        and set (v: string) = _name <- v

 

C# では自動実装プロパティ(Auto-Implemented Properties)の文法があるので上記のようなプロパティの定義を意味を変えずに以下のようにシンプルに記述できます。

class Person2
{
    public string Name { get; set; } = "John";
}

 

F# でも同様にシンプルな記述に変えることができます。

type Person2() =
    member val Name: string = "John" with get, set

 

ここで、プロパティの getter / setter のアクセシビリティをそれぞれ別のものを指定する(以下のコードでは getter は public に、 setter は privateにそれぞれ指定)場合を考えてみます。

C# では以下のようにシンプルに記述できますが、

public class Person3
{
    public string Name { get; private set; } = "Alice";
}

 

こういう場合、F# ではどうしても冗長な書き方をするしかありませんが、 getter / setter それぞれのアクセシビリティを個別に指定することはできます。

type Person3() =
    let mutable _name = "Alice"
    member __.Name  
        with get(): string = _name
        and private set (v: string) = _name <- v

 

また、プロパティの getter / setter は以下のように分けて定義することもできます。

type Person3'() =
    let mutable _name = "Jane"
    member __.Name  with get(): string = _name
    member __.Name  with private set (v: string) = _name <- v

 

煩雑な条件分岐を F# のコンピューテーション式で簡潔に表現する

条件分岐の多い処理を、F# の Option 型コンピューテーション式の文法を活用してコードの可読性を向上させる例を示します。

関数プログラミング実践入門 ──簡潔で、正しいコードを書くために」《第5章 モナド / 5.4 他の言語におけるモナド》に載っている JavaHaskell のコード片を参考に、 F# のコードで同様の動作をするように独自に解釈して実装を試みています*1

 F#で実装したコード

  1. open System
  2.  
  3. type Ticket = FilmTicket
  4. type Money = int
  5. type Wallet = { mutable Amount: Money }
  6.     with
  7.     member this.getMoney (cost: Money) =
  8.         if this.Amount >= cost then
  9.             this.Amount <- this.Amount - cost
  10.             printfn "    財布から%A円出す 残金=%A円" cost this.Amount
  11.             Some cost
  12.         else
  13.             printfn "    お金が足りない"
  14.             None
  15.  
  16. type Pocket = { Wallet: Wallet option; Ticket: Ticket option }
  17.     with
  18.     member this.getWallet(): Wallet option =
  19.         this.Wallet
  20.     member this.getTicket(): Ticket option =
  21.         this.Ticket
  22.  
  23. type Person = { Name: string; Pocket: Pocket option }
  24.     with    
  25.     member this.getWallet(): Wallet option =
  26.         Option.bind (fun x -> x.Wallet) this.Pocket
  27.     member this.getPocket(): Pocket option =
  28.         this.Pocket
  29.  
  30. /// Option型の型拡張:JavaのOptional型と同名・同機能のメソッドを追加
  31. /// オリジナルのJavaコードに外観を似せるための措置
  32. type Option<'T> with
  33.     member this.get() =         this |> Option.get
  34.     member this.isPresent() =   this |> Option.isSome
  35.     member this.flatMap(f) =    this |> Option.bind f
  36.     member this.ifPresent(f) =  this |> Option.iter f
  37.  
  38. type Optional<'T> = Option<'T> // 型のエイリアス
  39.  
  40. type Persons() =
  41.     let personDB = seq [
  42.         {Name = "Alice";
  43.             Pocket = Some {Wallet = {Amount = 10000} |> Some; Ticket = FilmTicket |> Some }}
  44.         {Name = "Becky";
  45.             Pocket = Some {Wallet = {Amount = 20000} |> Some; Ticket = None(* 引換券なし *)}}
  46.         {Name = "Cathy";
  47.             Pocket = Some {Wallet = {Amount = 1000} |> Some; Ticket = FilmTicket |> Some}}
  48.     ]
  49.     let findBase (db: Person seq) (name: string): Person option =
  50.         Seq.tryFind (fun x -> x.Name = name) db
  51.         |> fun person ->
  52.             if person.IsNone then
  53.                 printfn "該当する名前の人物が存在しません: %A" name
  54.             person
  55.  
  56.     member __.find = findBase personDB
  57.  
  58. /// お金を払う(その1)
  59. let pay1 (money: Money): unit =
  60.     printfn "    pay1 実行  Money= %A"  money
  61.  
  62. /// お金を払う(その2)
  63. let pay2 (ticket: Ticket, money: Money): unit =
  64.     printfn "    pay2 実行  Ticket= %A, Money= %A" ticket money
  65.  
  66. //--------------------------------------------------------------------
  67.  
  68. (*  参考資料: 関数プログラミング実践入門 [技術評論社]
  69.        第5章 モナド / 5.4 他の言語におけるモナド / Optionalクラス p.264 *)
  70.  
  71. /// 関数[1-1] JavaのOptional型を模したコード
  72. let simulateJavaOptional_1(personName: string): unit =
  73.     printfn "\n 関数[1-1] simulateJavaOptional_1 を実行..."
  74.     let persons = new Persons()
  75.     let person: Optional<Person> = persons.find(personName)
  76.     if person.isPresent() then
  77.         let wallet: Optional<Wallet> =  person.get().getWallet()
  78.         if wallet.isPresent() then
  79.             let money: Optional<Money> = wallet.get().getMoney(10000)
  80.             if money.isPresent() then
  81.                 pay1(money.get())
  82.  
  83. /// 関数[1-2] Javaのメソッドチェーンを模したコード(その1)
  84. let simulateJavaMethodChain_1(personName: string): unit =
  85.     printfn "\n 関数[1-2] simulateJavaMethodChain_1 を実行..."
  86.     let persons = new Persons()
  87.     persons
  88.         .find(personName)
  89.         .flatMap(fun person -> person.getWallet())
  90.         .flatMap(fun wallet -> wallet.getMoney(10000))
  91.         .ifPresent(fun money -> pay1(money))
  92.  
  93. /// 関数[1-3] F# のパイプライン演算を利用したコード(その1)  
  94. let usePipeline_1(personName: string): unit =
  95.     printfn "\n 関数[1-3] usePipeline_1 を実行..."
  96.     let persons = new Persons()
  97.     persons.find(personName)
  98.     |> Option.bind(fun person -> person.getWallet())
  99.     |> Option.bind(fun wallet -> wallet.getMoney(10000))
  100.     |> Option.iter(fun money -> pay1(money))
  101.     
  102. (*  参考資料: 関数プログラミング実践入門 [技術評論社]
  103.         第5章 モナド / 5.4 他の言語におけるモナド / メソッドチェインの弊害 ―do記法のありがたみ  p.265 *)
  104.  
  105. /// 関数[2-1] 条件分岐が煩雑な処理の例
  106. let simulateJavaOptional_2(personName: string): unit =
  107.     printfn "\n 関数[2-1] simulateJavaOptional_2 を実行..."
  108.     let persons = new Persons()
  109.     let person: Optional<Person> = persons.find(personName)
  110.     if person.isPresent() then // Aliceがいて
  111.         let pocket: Optional<Pocket> = person.get().getPocket() // personからpocketを取り出す
  112.  
  113.         if pocket.isPresent() then // ポケットがあって
  114.             let ticket: Optional<Ticket> = pocket.get().getTicket()
  115.             if ticket.isPresent() then // 引換券を持っている
  116.                 let wallet: Optional<Wallet> = person.get().getWallet() // personからwalletを取り出す
  117.  
  118.                 if wallet.isPresent() then // 財布を持っていて
  119.                     let money: Optional<Money> = wallet.get().getMoney(10000)                       
  120.                     if money.isPresent() then // 10000円入っている
  121.                         pay2(ticket.get(), money.get())
  122.  
  123. /// 関数[2-2] Javaのメソッドチェーンを模したコード(その2)
  124. let simulateJavaMethodChain_2(personName: string): unit =
  125.     printfn "\n 関数[2-2] simulateJavaMethodChain_2 を実行..."
  126.     let persons = new Persons()   
  127.     persons.find(personName)
  128.         .ifPresent(fun person ->
  129.             person.getPocket()
  130.                 .flatMap(fun pocket ->
  131.                     pocket.getTicket())
  132.                         .ifPresent(fun ticket ->
  133.                             person.getWallet()
  134.                                 .flatMap(fun wallet -> wallet.getMoney(10000))
  135.                                 .ifPresent(fun money -> pay2(ticket, money))))
  136.  
  137. /// 関数[2-3] F# のパイプライン演算を利用したコード(その2)  
  138. let usePipeline_2(personName: string): unit =
  139.     printfn "\n 関数[2-3] usePipeline_2 を実行..."
  140.     let persons = new Persons()   
  141.     persons.find(personName)
  142.     |> Option.iter(fun person ->
  143.                     person.getPocket()
  144.                     |> Option.bind(fun pocket -> pocket.getTicket())
  145.                     |> Option.iter(fun ticket ->
  146.                                         person.getWallet()
  147.                                         |> Option.bind(fun wallet -> wallet.getMoney(10000))
  148.                                         |> Option.iter(fun money -> pay2(ticket, money))))
  149.     
  150. //--------------------------------------------------------------------
  151.  
  152. /// Option型を扱うコンピューテーション式
  153. type OptionalBuilder() =
  154.     member __.Bind(x: 'T option, rest: 'T -> 'U option): 'U option =   
  155.         Option.bind (fun x -> rest x) x
  156.     member __.Return(v: 'T): 'T option =
  157.         Some v
  158.     member __.Zero(): unit option = // returnがない場合の対策
  159.         Some ()
  160.  
  161. /// 関数[2-4] コンピューテーション式で可読性を上げた処理
  162. let useComputationExpression(personName: string): unit =
  163.     printfn "\n 関数[2-4] useComputationExpression を実行..."
  164.     let optional = new OptionalBuilder()
  165.     optional {
  166.         let persons = new Persons()
  167.         let! person = persons.find personName    // Aliceがいて
  168.         let! pocket = person.Pocket         // ポケットがあって
  169.         let! ticket = pocket.Ticket         // 引換券を持っている
  170.         let! wallet = person.getWallet()    // 財布を持っていて
  171.         let! money = wallet.getMoney(10000) // 10000円以上入っていれば出す
  172.         pay2(ticket, money)
  173.     }
  174.     |> ignore
  175.  
  176. #if !INTERACTIVE
  177. [<EntryPoint>]
  178. #endif
  179. do   
  180.     let personName = "Alice"
  181.     printfn "\n---------------------------------------------"
  182.     simulateJavaOptional_1 personName
  183.     simulateJavaMethodChain_1 personName
  184.     usePipeline_1 personName
  185.     printfn "\n---------------------------------------------"
  186.     simulateJavaOptional_2 personName
  187.     simulateJavaMethodChain_2 personName
  188.     usePipeline_2 personName
  189.     useComputationExpression personName
  190.     printfn "\n---------------------------------------------"
  191.  
  192. #if !INTERACTIVE
  193.     Console.ReadKey() |> ignore
  194. #endif

 

64行目までは以降の関数群を実行する準備で、処理対象となるデータを構成する判別共用体レコード型、クラスの宣言などを行っています。判別共用体レコード型にはメソッド定義も付加できるのでクラスの代わりに利用しています。

以下の図は上記の F# コードで定義している Person 型の構造を表しています。

Person型(レコード型)
フィールド Name: string型
フィールド Pocket: Option<Pocket>型(Pocket型はレコード型)
フィールド Ticket: Option<Ticket>型(Ticket型は判別共用体)
フィールド Wallet: Option<Wallet>型(Wallet型はレコード型)
フィールド Amount: Money型(int型のエイリアス

 

以下は条件分岐が多い処理の一例としての関数定義 simulateJavaOptional_172行目)です。 Persons オブジェクトから名前(文字列)を使って Person オブジェクトを取り出し、if 文で様々な条件を検査しすべて合格した場合は関数 pay159行目)を呼び出しています。条件分岐が多く、ネストが深くなってしまったコードです。 Option 型型拡張32~36行目)を利用して、 Java の Optional 型のメソッド呼び出しを F# のコードで模しています。

  1. /// 関数[1-1] JavaのOptional型を模したコード
  2. let simulateJavaOptional_1(personName: string): unit =
  3.     printfn "\n 関数[1-1] simulateJavaOptional_1 を実行..."
  4.     let persons = new Persons()
  5.     let person: Optional<Person> = persons.find(personName)
  6.     if person.isPresent() then
  7.         let wallet: Optional<Wallet> =  person.get().getWallet()
  8.         if wallet.isPresent() then
  9.             let money: Optional<Money> = wallet.get().getMoney(10000)
  10.             if money.isPresent() then
  11.                 pay1(money.get())

 

上記の関数と同じ動作を、  Java の Optional 型のメソッドチェーンを模した簡潔なコードに直したのが以下のコード(84~91行目)です。

  1. /// 関数[1-2] Javaのメソッドチェーンを模したコード(その1)
  2. let simulateJavaMethodChain_1(personName: string): unit =
  3.     printfn "\n 関数[1-2] simulateJavaMethodChain_1 を実行..."
  4.     let persons = new Persons()
  5.     persons
  6.         .find(personName)
  7.         .flatMap(fun person -> person.getWallet())
  8.         .flatMap(fun wallet -> wallet.getMoney(10000))
  9.         .ifPresent(fun money -> pay1(money))

 

F# にはパイプライン演算子があるので、メソッドチェーンを真似しなくとも簡潔なコード表現ができます。

  1. /// 関数[1-3] F# のパイプライン演算を利用したコード(その1)  
  2. let usePipeline_1(personName: string): unit =
  3.     printfn "\n 関数[1-3] usePipeline_1 を実行..."
  4.     let persons = new Persons()
  5.     persons.find(personName)
  6.     |> Option.bind(fun person -> person.getWallet())
  7.     |> Option.bind(fun wallet -> wallet.getMoney(10000))
  8.     |> Option.iter(fun money -> pay1(money))

 

さらに面倒な分岐処理を含む関数(106行目)の例を考えてみます。

  1. /// 関数[2-1] 条件分岐が煩雑な処理の例
  2. let simulateJavaOptional_2(personName: string): unit =
  3.     printfn "\n 関数[2-1] simulateJavaOptional_2 を実行..."
  4.     let persons = new Persons()
  5.     let person: Optional<Person> = persons.find(personName)
  6.     if person.isPresent() then // Aliceがいて
  7.         let pocket: Optional<Pocket> = person.get().getPocket() // personからpocketを取り出す
  8.  
  9.         if pocket.isPresent() then // ポケットがあって
  10.             let ticket: Optional<Ticket> = pocket.get().getTicket()
  11.             if ticket.isPresent() then // 引換券を持っている
  12.                 let wallet: Optional<Wallet> = person.get().getWallet() // personからwalletを取り出す
  13.  
  14.                 if wallet.isPresent() then // 財布を持っていて
  15.                     let money: Optional<Money> = wallet.get().getMoney(10000)                       
  16.                     if money.isPresent() then // 10000円入っている
  17.                         pay2(ticket.get(), money.get())

これをメソッドチェーンを使って書き直すと以下のようになります。今度は読みづらいコードになってしまいました。

  1. /// 関数[2-2] Javaのメソッドチェーンを模したコード(その2)
  2. let simulateJavaMethodChain_2(personName: string): unit =
  3.     printfn "\n 関数[2-2] simulateJavaMethodChain_2 を実行..."
  4.     let persons = new Persons()   
  5.     persons.find(personName)
  6.         .ifPresent(fun person ->
  7.             person.getPocket()
  8.                 .flatMap(fun pocket ->
  9.                     pocket.getTicket())
  10.                         .ifPresent(fun ticket ->
  11.                             person.getWallet()
  12.                                 .flatMap(fun wallet -> wallet.getMoney(10000))
  13.                                 .ifPresent(fun money -> pay2(ticket, money))))

 パイプライン演算を用いてもこの場合はやはり読みやすくはなりません。

  1. /// 関数[2-3] F# のパイプライン演算を利用したコード(その2)  
  2. let usePipeline_2(personName: string): unit =
  3.     printfn "\n 関数[2-3] usePipeline_2 を実行..."
  4.     let persons = new Persons()   
  5.     persons.find(personName)
  6.     |> Option.iter(fun person ->
  7.                     person.getPocket()
  8.                     |> Option.bind(fun pocket -> pocket.getTicket())
  9.                     |> Option.iter(fun ticket ->
  10.                                         person.getWallet()
  11.                                         |> Option.bind(fun wallet -> wallet.getMoney(10000))
  12.                                         |> Option.iter(fun money -> pay2(ticket, money))))

 

コンピューテーション式の利用

F# にはコンピューテーション式(Computation Expressions)という文法があります。これを利用して、式を評価する際に背後で動作する副作用を独自に定義できます。F# のコード内でモナドの動作を実現するために利用されることも多いと思います。

153行目からがコンピューテーション式の定義(ビルダークラスとも呼ばれる)です。文法上は F# のクラス定義と変わりませんが、BindReturn などの規定の名前とシグネチャに従ったメソッド群をクラス内で定義しておく必要があります。

  1. /// Option型を扱うコンピューテーション式
  2. type OptionalBuilder() =
  3.     member __.Bind(x: 'T option, rest: 'T -> 'U option): 'U option =   
  4.         Option.bind (fun x -> rest x) x
  5.     member __.Return(v: 'T): 'T option =
  6.         Some v
  7.     member __.Zero(): unit option = // returnがない場合の対策
  8.         Some ()

 

162行目から始まる関数 useComputationExpression において、165~173行目のコードブロック optional {...} の内部が、前出の関数 simulateJavaOptional_2 (106行目)と同じ動作をするコードになります。

ここでは F# の Option 型コンピューテーション式を活かして、 Haskell における Maybe モナドと do 構文の組み合わせに近いコード表現を試みており、関数 simulateJavaOptional_2 (106行目)と同じ機能でありながら、F# のコードをより簡潔に書くことができます。

  1. /// 関数[2-4] コンピューテーション式で可読性を上げた処理
  2. let useComputationExpression(personName: string): unit =
  3.     printfn "\n 関数[2-4] useComputationExpression を実行..."
  4.     let optional = new OptionalBuilder()
  5.     optional {
  6.         let persons = new Persons()
  7.         let! person = persons.find personName    // Aliceがいて
  8.         let! pocket = person.Pocket         // ポケットがあって
  9.         let! ticket = pocket.Ticket         // 引換券を持っている
  10.         let! wallet = person.getWallet()    // 財布を持っていて
  11.         let! money = wallet.getMoney(10000) // 10000円以上入っていれば出す
  12.         pay2(ticket, money)
  13.     }
  14.     |> ignore

 

実行結果

 

参考資料:

関数プログラミング実践入門 ──簡潔で、正しいコードを書くために技術評論社

 第5章 モナド / 5.4 他の言語におけるモナド

 

*1:ただし、同書に掲載されているコード片は不完全なものなので、本記事においては、クラス定義など足りない部分については独自解釈でコードを大幅に追加しています。

開発環境メモ - VS 2019 Preview 3.0 のタイトルバーを復活させる


f:id:pongitsune:20190630041438p:plain

Visual Studio 2019 Preview 3.0 ではタイトルバーの有無を選択するメニュー項目が出来ました。

メニューバーから「 ツール → オプション → 環境 → プレビュー機能」で設定項目にたどり着けます。

コンパクトメニューと検索バーを使用する

タイトルバーを復活させるには、項目「コンパクトメニューと検索バーを使用する」のチェックボックスOFFに設定してから Visual Studio を再起動させる必要があります。

コンパクトメニュー ON設定時の外観

コンパクトメニュー ONの設定時の外観

コンパクトメニュー OFF設定時の外観

コンパクトメニュー >OFFの設定時の外観