BETA

F#をはじめよう

投稿日:2019-09-12
最終更新:2019-09-12

最近、F#が気になって学習しているので復習も兼ねていくつかF#に触れた記事に書いていきます。

F#は Don Syme氏とMicrosoft Researchが開発した OCamlがベースとなっている .NET 向けの関数型メインのマルチパラダイム言語です。オープンソースで、クロスプラットフォームで……といった詳しい説明はMicrosoft DocsF# Software Foundation、Wikipedia等に譲ります。

F#をはじめる

.NET Core をインストールしましょう。
https://dotnet.microsoft.com/download

エディタはVisual Studio Codeと拡張機能のIonideを使うのがイイでしょう。
Visual Studioを使用することももちろん可能です。

Hello, World!

対話的に実行する

dotnet fsiのコマンドで対話的にコードを実行することができます。
セミコロン2つで区切ることでそれまで入力したコードを実行します。

> printfn "Hello, World!";;  
Hello, World!  
val it : unit = ()  

unitはC#などで言うところのvoidに相当するもので、関数の戻り値が存在しない、または必要ない場合に取りうる型(Type)です。itはインタラクティブでの動作時に直前に実行された関数を示します。

インタラクティブモードは#quit;;で抜けることができます。

ソースコードをビルドして実行する

dotnet new console -o HelloFsharp -lang F#のコマンドを実行することで HelloFsharp という名称のコンソールアプリケーションプロジェクトを作成します。.NET Coreのデフォルト言語はC#なので明示的にF#を指定する必要があります。C#のプロジェクトを作ったとしてもプロジェクトファイルの拡張子を.fsprojに書き換えて、いくつかの追記をしてあげることでF#でも使うことが可能です。

// Learn more about F# at http://fsharp.org  

open System  

[<EntryPoint>]  
let main argv =  
    printfn "Hello World from F#!"  
    0 // return an integer exit code  

プロジェクト作成時に既にProgram.fsにHello, Worldを表示するコードがあります。 書かれている内容について簡単に説明しましょう。

まず//はコード上に記述するコメントです。これについてはあまり説明の必要はないかと思います。

次にopenはC#で言うところの名前空間を使用する際に記述するusing句に相当し、他のモジュール(module)や名前空間(namespace)に定義されたリソースを使用する際に使います。System名前空間は .NETの基底クラスが含まれている名前空間ですね。上記のコードでは使用されていません。

[<EntryPoint>]はプログラムの開始地点を表す属性(Attribute)です。属性についてはC#の属性に関するドキュメントを参照していただきたいと思います。この属性を付けることでプログラムの実行時に始めに呼び出される関数に指定できる、いくつかのプログラミング言語に存在するmain関数(メソッド)に相当するものですが、名称がmainである必要はありません。ただし必ずstring[]型の引数を取って、int型の値を返す必要があります。

letは値や関数を定義するのに用いるキーワードで、F#ではメインとなるキーワードです。後ほど詳しく書きたいと思います。

ターミナル上でプロジェクトフォルダへ移動(cd HelloFsharp)し、 dotnet runのコマンドを入力することで、プロジェクトをビルドして実行することができます。

> dotnet run  
Hello World from F#!  

C#のメソッドを使ったHello, World

open System  

Console.WriteLine("Hello, World from F#!")  

このように .NETのライブラリで定義されたメソッドやクラス群も使用することができます。オブジェクト指向的なC#ライクの書き方も可能ではありますが、関数型パラダイムの書き方をメインしていくことがF#では推奨されています。

let束縛(let bindings)で値や関数を定義する

open System  
// 値の定義  
let helloWorld = "Hello, World!"  
let integer = 100  
let pi = 3.14  
let date = DateTime.Now  

// 関数の定義  
let square x = x * x  
let addOne x = x + 1  

// 関数同士の合成。let addOneAndSquare x = square (addOne x)と同じ  
let addOneAndSquare = addOne >> square  

// printfn "%i" (square (addOne 10))  
printfn "%i" (addOneAndSquare 10) // 121  

// パイプラインでこのようにも書ける  
10 |> addOne |> square |> printfn "%i"  

// 関数の中に関数を定義できる  
let f x =     
    let g x = x * 2   
    let h x = x + 10  
    g(h(x))  

f 5 // (5 + 10) * 2 = 30  
// g 5 は実行時エラー(スコープ外で関数gは存在しないとされる)  
// f "5" も実行時エラー(型が異なる為)  

このようにletを利用して値や関数を識別子(identifier)へバインディングすることができます。F#では関数も第一級オブジェクト(first-class object)として扱われるので整数や文字列と同様に扱うことができ、型推論の機能が推測できる範囲内で型を明記する必要がありません。また、Pythonなどと同じようにスコープは基本的にインデントで表現します。タブ文字は使えません。

関数の引数は半角スペース区切りで表現され、括弧は用いません。
let f x y z = x + y + z
これは数学の式的に書くと、f(x,y,z) = x + y + zとなり、x, y, zの3つの値を引数として受け取り、x + y + zの結果を返す関数となります。この段階ではx,y,zは整数なのか実数なのか文字列なのかということは推定できません。

let f x y z = x + y + z  
f 2 3 -4 |> printfn "%i" // 1  

こうして値を適用することで関数fの引数の型が決定されます。

第n項のフィボナッチ数を計算する

フィボナッチ数の計算からF#の強力なツールであるパターンマッチングや再帰関数、Listについて触れてみます。

if...then...else

let fibonacci n =  
    let rec inner n now prev =  
        if n = 0 then now  
        else inner (n - 1) (now + prev) now  
    inner n 0 1  

//55  
fibonacci 10 |> printfn "%i"  

まずrec再帰関数(recursive function:自身で自身を呼び出す関数)を表します。関数fibonacciの内部で定義しているinnerはその中でinnerを再び呼び出しています。

if...then...else条件式を表し、if以下の条件が真であればthen以降の値を、偽であればelse以降の値を返します。F#では左辺と右辺が等しいことを表す演算子も=====ではなく=だけで表すことができます。

パターンマッチング

let fibonacci n =  
    let rec inner n (now : bigint) (prev : bigint) =  
        match n with  
        | 0 -> now  
        | _ -> inner (n - 1) (now + prev) now  
    inner n 0I 1I    

上記のコードは前述したif...then...elseの式を用いたものとほぼ同じ動作をします。より大きな値を計算できるようint型からbigint型(System.Numerics.BigInteger)に変更しています。

上記では0かそれ以外かという判別しかしていませんが、リスト、配列のパターンやレコード、タプルなど様々な表現方法が可能です。

0から100項目までを一気に計算して表示する

let fibonacci n =  
    let rec inner n (now : bigint) (prev : bigint) =  
        match n with  
        | 0 -> now  
        | _ -> inner (n - 1) (now + prev) now  
    inner n 0I 1I    

[0..100] |> List.map fibonacci |> List.iter (printf "%A ")  

この一行だけで0から100項目までを表示できます。
[0..100]は0から100まで1刻みの値を取るリストを作成します。List.mapはリストの全要素へ指定する関数を適用した新しいリストを返します。List.iterはリストの全要素へ指定した戻り値がunit型の(つまり存在しない)関数を適用します。

つまり、[0..100] |> List.map fibonacci |> List.iter (printf "%A ")は0から100項目までのフィボナッチ数を持つリストを作成して、それぞれを標準出力へ末尾に半角スペースを付けて表示する処理を行います。

実行結果は以下のようになります。

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 2971215073 4807526976 7778742049 12586269025 20365011074 32951280099 53316291173 86267571272 139583862445 225851433717 365435296162 591286729879 956722026041 1548008755920 2504730781961 4052739537881 6557470319842 10610209857723 17167680177565 27777890035288 44945570212853 72723460248141 117669030460994 190392490709135 308061521170129 498454011879264 806515533049393 1304969544928657 2111485077978050 3416454622906707 5527939700884757 8944394323791464 14472334024676221 23416728348467685 37889062373143906 61305790721611591 99194853094755497 160500643816367088 259695496911122585 420196140727489673 679891637638612258 1100087778366101931 1779979416004714189 2880067194370816120 4660046610375530309 7540113804746346429 12200160415121876738 19740274219868223167 31940434634990099905 51680708854858323072 83621143489848422977 135301852344706746049 218922995834555169026 354224848179261915075
あってるかどうかは知らない

if...then...else式、パターンマッチングいずれの場合においても極めてシンプルに記述できます。
どれくらいシンプルかというとC#で書くとこんな感じになります。

using System;  
using System.Numerics;  
using System.Linq;  

namespace Fibonacci  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            foreach(var fib in Enumerable.Range(0,101).Select(x => CalcFibonacci(x)))  
            {  
                Console.Write($"{fib} ");  
            }  
        }  

        private static BigInteger CalcFibonacci(int n)  
        {  
            BigInteger Calc(int n, BigInteger now, BigInteger prev)  
            {  
                switch(n)  
                {  
                    case 0: return now;  
                    default: return Calc(n - 1, now + prev, now);  
                }  
            }  

            return Calc(n,0,1);  
        }  
    }  
}  

なるべく簡素に書こうとしましたがusingやnamespace, classの記述がなかった場合でも結構長いですね。
.NET Frameworkのバージョンが古くて仕事で使えないローカル関数とかも使ってみました。Mainメソッドの中に全部入れることもできますが、流石にそれはイケてないのでやめました。Enumerable.Rangeメソッドはもう少し便利なものができていそうな気がしますが見つけられなかった。

あとがきと今後について

F#が仕事で使われている事例はF# Software Foundationのサイトでいくつか見ることができますが、日本では.NETファミリーではあるもののC#に比べて極めてマイナーな印象を受けます。おそらく日本語の書籍は2013年ぐらいのものを最後に出ていないと思います。.NETの言語の例に漏れず検索性が悪いところもアレです。しかし、その見た目の簡素さ・綺麗さやマイナーさ等がイイと思ったのでどこかで使えたらいいと思っています。

次回はパターンマッチングとアクティブパターン、レコードや判別共用体(descriminated unions)あたりについて触れたいと思います。.NET Core 3.0のPreview版であればWinFormsにも触れられるのでそちらも少しやるかもしれません。

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@hiro3の技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!