普段、C# を触ってるのですが、switch
文で苦しい思いをすることがたびたびあります。C# 7.0 リリースはよ、と思いつつもいい書き方ないかなぁと探すといい記事に巡りあうことができます。
www.kekyo.net
単純に switch-case の代替としてのパターンマッチングも十分好きなのですが*1 、複数の値の組み合わせやタプルの名前つけと分解は直感的でもあり好きです。そんなときに、@t_tetsuzin さんから、LINQPad で触れること、Option や Either、Maybe から アクティブパターンなどもっと面白いといわれたのでいい機会なので触ってみようと思います。
私の頭はアクティブパターンのURL 送られてもすぐに理解できるような賢さは持ってないのですよ。
アクティブ パターン (F#) | Microsoft Learn
目次
ごにょごにょ触る環境の構築
C# でごにょごにょコード篇を書いては捨て、値試しつつ書き方とか探すときに使っているのが LINQPad です。LINQPad 自体は伝道師の素晴らしい記事があるのでそちらで。
takeshik.org
takeshik.org
LINQPadで F# 確かにサポートされてました。手元のは LINQPad Premium なのでちょうどよさそうです。
ではまず開発環境を作りましょう。
Visual Studio でのF#
Visual Studio を標準でインストールします。残念ながらカスタムにするのは HoloLens ぐらいのためです。F# ここで入れろ? 自動化してるのでカスタマイズめんどくさいです。却下。
VSのインストール後は、F# Tools を OneGetで入れます。
Install-Package visualfsharptools
これで Visual Studio で F# プロジェクトを触ったりビルドができるようになります。
LINQPad での F#インテリセンス
LinqPad で F# をするには少し前準備が必要です。単純に F# Expression や F# Class にしてもLINQPad エラーが発生します。
インテリセンス実行しようとするところでぐぬぬ
View More Details
から詳細エラーをみると、AnnouncementClient が見つからないとのこと
System.ServiceModel.Discovery 名前空間にあるので、System.ServiceModel.Discovery.dll をインポートします。
AnnouncementClient クラス (System.ServiceModel.Discovery) | Microsoft Learn
それでもエラーが出ます。今度は MetadataExchangeClient です。
System.ServiceModel.Description.dll はありません。System.ServiceModel.dll をインポートします。
これで System.ServiceModel.dll
と System.ServiceModel.Discovery.dll
を参照に加えました。
無事にインテリセンスが出ました。まずは第一歩です。
LINQPad で dump
個人的に、LINQPad を使う良さの90%は .Dump() メソッドにあります。VS のローカル変数と同様に オブジェクトを覗きつつ次にメソッドをつなげることもできるので、ひじょーに強力です。
ただ、F# でメソッド呼び出しは気持ち悪いので、パイプラインから dump を呼べるようにします。
F# は smallCamel ベースのようなので、当初、Dumpではなく dump関数をつくってたのです。
let dump x = Dump x:Object
が、まぁ当然 型情報が失われます。
ジェネリクスどう書けばいいのかなぁ、とおもっていたら inline を使うといいよという情報が。
うまく取れました。
inline って何かと思ったら、型推論しておいて コンパイル時に型確定してその型の分だけ関数を生成するらしい... すごい..。
inline と rec の併用できないんですねー。ほむほむ。
2016/9/4 修正
型注釈...! なるほどキャストとは違うのですね。型推論が強力というか前提というか、ほむほむ。象徴的に感じます。
修正ここまで
さて、もう1つほしいのが、.Dump() のオーバーロードです。.Dump()
に名前を付けて表示できるので。適当に書くとエラーが。
むむっと思っていると、Overload はツラいので別名の関数がよさそうとのこと。パイプラインの入力は優先度が低くて後から入ってくるのは PowerShell の ValueFromRemainingArguments
も似た挙動ですね。
about Functions Advanced Parameters - PowerShell | Microsoft Learn
適当に書いてみました。
let inline dumpTitle x y = y.Dump(x:string); y
ここで 関数の部分適用 を学びました。なにこれ便利。メソッドでも似たようものですが、変数と関数が両方 let
でかけることもあり格段に便利です。
さて、inline でいいかなぁと思っていたら、なんとinline
使わずに普通にかけそう。
ということで最終版
gist.github.com
これで、パイプラインで値を確認しつつ先につなぐことも簡単です。
let dump x = x.Dump(); x
let add x y = x + y
let square x = x * x
let negete x = x * -1
let triple x = x * x * x
let print (x : Object) = Console.WriteLine(x);
square 42 |> negete |> triple |> dump |> square |> print
生きていけそう。
let dump x = x.Dump(); x
let dumpTitle (x : string) y = y.Dump(x); y
let titleDump = dumpTitle "れんしゅー"
let hello = "Hello" + "World" |> dump
hello |> dumpTitle "文字列"
hello |> titleDump
チュートリアル
さて、F# といえば、The F# Software Foundation
です。
Redirecting…
しかしみてもいい感じの F# Tutorial みたいなサイトはありません。ということで、上からとググって見つけたいくつかを参考に見ていきます。
F# チュートリアル、むむっと思っていたのですが、記事書きつつ見直していて Try F# がプレイグラウンドもあるし最高な気がします。
https://www.tryfsharp.org/Learn
気を取り直して順番に見ていきます。といっても、このあたり と軌跡は似てます。
変数 と関数の 宣言
基本中の基本です。open とかはいったん知らないふりで。
let
で変数も関数も宣言できるようです。
let str = "stringだぞ"
C# のvar と同様に型推論効くのですんなりです。末尾セミコロンレス なのですね。代入は不可なのはいいとして、これにさらに =
オペレータを使うと比較になるのは少し驚きでした。C# なら ==
、PowerShell なら-eq
のような比較演算子を別途用意しないのですね。
let str = "stringだぞ"
str = "hoge"
2016/9/4 修正
大事なことなのに忘れそう.... 理解しました。
修正ここまで
とりあえず 定番ですね。
printfn "Hello World"
文字列埋め込み
print debug するなりなんでも、文字列へ変換できないことには学習もままならないのです。で、文字列どうやって埋め込むかと思ったら printfn
に int埋め込み だと %d
とか書くらしく。
printfn "Hello World %d!!" 1
まさか let すると 直でかけないとは...ほむ。
let hello = "Hello" + " World"
printfn "%s" hello
string.Format や Console.WriteLine がかけるということで、もう、こっちのほうが書きやすい..。
Console.WriteLine("Hello World {0}!!", 1)
この時点の気分は完全にこれです。
Boxing かかってもこれでいいんじゃないか気分になったところです。
let print (x : Object) = Console.WriteLine(x);
ここで、sprintfn
なんて便利関数があるということでサクッと乗せ換えです。*2
stackoverflow.com
これで、dump
関数につないで生きていけそうです。
sprintf "Hello %s! Number is %d" "world" 42 |> dump
sprintf "Hello %s! Number is %d. %s" "world" 42 "hoge" |> dump
そういえば、型指定は string でもいいのにメソッドの時は String なんですね。
docs.microsoft.com
2016/9/4 修正
System.String と string というより、なるほど用途... 言われないと気付けなかった気がします。
修正ここまで
正直、文字列にちょちょいとデバッグ見たいという時なら、%A
でいい感じに自動的に変換してくれるのばかり使いそうな気がします。
部分文字列抽出
さて、続けて部分文字列抽出です。C# なら string.Substring()
ですが、 F# はスライシングっぽく書けるようです。便利。
let str1 = "hoge"
printfn "%s" (str1.[0..2])
文字列連結は + と ^ の両方でいけるようで。なんで2つのオペレータがあるんですかね。不思議。
docs.microsoft.com
2016/9/4 修正
なるほど OCaml 由来。
修正ここまで
String の Char 取り出しは、str.Chars(<int>)
か str1.[0]
。ただし .[<int>]
だと Char になって .[<int>..<int>]
だと string なのは型推論強いけど、うぬぬ。
let str1 = "hoge"
printfn "%c" (str1.[0])
printfn "%s" (str1.[0..1])
2016/9/4 修正
言われてみると .Chars は C# で使ってことなかったです。
修正ここまで
List
List 処理はびっくりでした。正直F# すごい。[]
でくくって、;
で要素を分割。::
で1つの文字とListの連結 *3、@
で合成。
簡単です。PowerShell とか目じゃない。
let list1 = [ "a"; "b" ]
let list2 = "c" :: list1
let list3 = list1 @ list2
さらに
yield!
で List内 List の展開までされてあら素敵。seq や PowerShell 同様 ..
でつなげたり、途中の値の指定もできて便利。
なるほど(?)
関数とパイプライン
関数も変数同様 let
で宣言できて、引数は 関数名の後ろで指定と。
let f x = x*x
f 2 |> dump
型指定はなるほど
F# といえば、私の中では パイプライン です。*4印象は素直だにゃぁと。これは凄く直感的で使いやすいと思います。ぐいぐいつなげられます。
let add x y = x + y
let square x = x * x
let negete x = x * -1
let triple x = x * x * x
let print (x : Object) = Console.WriteLine(x);
let dump x = x.Dump(); x
let dumpTitle (x : string) y = y.Dump(x); y
square 42 |> negete |> triple |> dump |> square |> print
ということで、List と合わせて触ってみると同じ結果が色々かけるのですが、どれがいいんですかねぇ。
List.map square [0..10] |> dumpTitle "まっぷ"
List.map (fun x -> x*x) [0..10] |> dumpTitle "まっぷ2"
[0..10] |> List.map square |> dumpTitle "まっぷ3"
[ for x in 0..10 -> square x ] |> dumpTitle "リスト内包表記"
あ、リスト内包表記の時だけ、Dump壊れるのなんとかならないでしょうか.... ツラい。
2016/9/4 修正
式が返ってました。[ for x in 0..10 -> square x ] |> dumpTitle "リスト内包表記"
でok
修正ここまで
再帰関数は rec
キーワードを付けた関数にすると。個人的に再帰の明示は結構好きです。お勉強かねてif使いつつフィボナッチ数列書いてみました。
let dump x = x.Dump(); x
let dumpTitle (x : string) y = y.Dump(x); y
let rec fibonacc n = if n=0 then 0 elif n=1 then 1 else fibonacc(n-1) + fibonacc(n-2);
fibonacc 7 |> dump
なるほど、そのまんまひねりなしでok。
パターンマッチ
先ほどのif..else 連打きもいので導入篇ということで触ってみます。
let dump x = x.Dump(); x
let dumpTitle (x : string) y = y.Dump(x); y
let rec fib n =
match n with
| 0 -> 0
| 1 -> 1
| n -> fib(n-1) + fib(n-2)
fib 7 |> dump
なるほど簡単。
この先はいずれということで。
www.kekyo.net
Tuple
Tuple とパターンマッチが組み合わさると確かに便利です。Value Tupleが似てるっていうのもなるほど、ほむり。
let dump x = x.Dump(); x
let tuple = (1, "person", "Joeh doe")
tuple |> dump
let hoge tuple1 =
match tuple1 with
| (no, kind, name) -> sprintf "No : %A, Kind : %A, Name : %A" no kind name
hoge tuple |> dump
名前付きで分解されるの便利ですねぇ。
殴り書きコード
いったんおいておきます。
gist.github.com
続き
は、いずれ。
pocketberserker.hatenablog.com
追記
むむっ...。