普段、C# を触ってるのですが、switch
文で苦しい思いをすることがたびたびあります。C# 7.0リリースはよ、と思いつつもいい書き方ないかなぁと探すといい記事に巡りあうことができます。
単純にswitch-caseの代替としてのパターンマッチングも十分好きなのですが*1 、複数の値の組み合わせやタプルの名前つけと分解は直感的でもあり好きです。そんなときに、@t_tetsuzinさんから、LINQPadで触れること、OptionやEither、Maybeからアクティブパターンなどもっと面白いといわれたのでいい機会です、触ってみましょう。
私の頭はアクティブパターンのURL送られてもすぐに理解できるような賢さは持ってないのですよ。
https://msdn.microsoft.com/ja-jp/library/dd233248(v=vs.110).aspx
ごにょごにょ触る環境の構築
C# でごにょごにょコード篇を書いては捨て、値試しつつ書き方とか探すときに使っているのがLINQPadです。LINQPad自体は伝道師の素晴らしい記事があるのでそちらで。
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をインポートします。
それでもエラーが出ます。今度はMetadataExchangeClientです。
System.ServiceModel.Description.dllはありません。System.ServiceModel.dllをインポートします。
これでSystem.ServiceModel.dll
とSystem.ServiceModel.dll
を参照に加えました。
無事にインテリセンスが出ました。まずは第一歩です。
LINQPad で dump
個人的に、LINQPadを使う良さの90%は .Dump() メソッドにあります。VSのローカル変数と同様にオブジェクトを覗きつつ次にメソッドをつなげることもできるので、ひじょーに強力です。
ただ、F# でメソッド呼び出しは気持ち悪いので、パイプラインからdumpを呼べるようにします。
徹人 (@t_tetsuzin) September 3, 2016
F# はsmallCamelベースのようなので、当初、Dumpではなくdump関数をつくってたのです。
let dump x = Dump x:Object
が、まぁ当然型情報が失われます。
ジェネリクスどう書けばいいのかなぁ、とおもっていたらinlineを使うといいよという情報が。
徹人 (@t_tetsuzin) September 3, 2016
うまく取れました。
inlineって何かと思ったら、型推論しておいてコンパイル時に型確定してその型の分だけ関数を生成するらしい... すごい..。
- 徹人 (@t_tetsuzin) September 3, 2016
- にゃーん (@kekyo2) September 3, 2016
- にゃーん (@kekyo2) September 3, 2016
- 徹人 (@t_tetsuzin) September 3, 2016
inlineとrecの併用できないんですねー。ほむほむ。
2016/9/4 修正
型注釈...! なるほどキャストとは違うのですね。型推論が強力というか前提というか、ほむほむ。象徴的に感じます。
はぇ~☆ (@haxe) September 3, 2016
修正ここまで
さて、もう1つほしいのが、.Dump(.Dump()
に名前を付けて表示できるので。適当に書くとエラーが。
むむっと思っていると、Overloadはツラいので別名の関数がよさそうとのこと。パイプラインの入力は優先度が低くて後から入ってくるのはPowerShellのValueFromRemainingArguments
も似た挙動ですね。
徹人 (@t_tetsuzin) September 3, 2016
適当に書いてみました。
let inline dumpTitle x y = y.Dump(x:string); y
ここで 関数の部分適用 を学びました。なにこれ便利。メソッドでも似たようものですが、変数と関数が両方let
でかけることもあり格段に便利です。
徹人 (@t_tetsuzin) September 3, 2016
さて、inlineでいいかなぁと思っていたら、なんとinline
使わずに普通にかけそう。
Kentaro Inomata (@matarillo) September 3, 2016
ということで最終版
https://gist.github.com/guitarrapc/7cb58d37f4379eb1fdd5ebc15c1cb338
これで、パイプラインで値を確認しつつ先につなぐことも簡単です。
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 inline 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
です。
しかしみてもいい感じのF# Tutorialみたいなサイトはありません。ということで、上からとググって見つけたいくつかを参考に見ていきます。
サイト | 概要 |
---|---|
https://docs.microsoft.com/ja-jp/dotnet/articles/fsharp/language-reference/index | 言語仕様です |
https://en.wikibooks.org/wiki/F_Sharp_Programming | Wiki ですが、想像以上にきれいにまとまっていたりサンプルが多いのでいい感じです。 |
https://dungpa.github.io/fsharp-cheatsheet/ | チートシートといいつつ、結構ほんとここを参考にしました。 |
https://neue.cc/2009/11/09_214.html | 漂白されていません。何気に一番ピンとくるぐらい具体的に書かれています。 |
https://fsharp.hatenablog.com/entry/Strings | 結構知りたい情報あって嬉しいです。 |
https://fsharp.hatenablog.com/entry/Tuples | Tuple もここがわかりやすかったです |
F# チュートリアル、むむっと思っていたのですが、記事書きつつ見直していてTry F#がプレイグラウンドもあるし最高な気がします。
気を取り直して順番に見ていきます。といっても、このあたりと軌跡は似てます。
変数 と関数の 宣言
基本中の基本です。let
で変数や関数を宣言できるようです。
let str = "stringだぞ" // System.String 型の変数 str を宣言
C# のvarと同様に型推論効くのですんなりです。末尾セミコロンレスなのですね。代入は不可、さらに=
オペレータを使うと比較になるのは少し驚きでした。C# なら=
、PowerShellなら=
のような比較演算子を別途用意しないのですね。
let str = "stringだぞ" str = "hoge" // これは比較
2016/9/4 修正
大事なことなのに忘れそう.... 理解しました。
はぇ~☆ (@haxe) September 3, 2016
修正ここまで
とりあえず定番ですね。
printfn "Hello World"
文字列埋め込み
print debugするなりなんでも、文字列へ変換できないことには学習もままならないのです。で、文字列どうやって埋め込むかと思ったらprintfn
にint埋め込みだとprintfn
とか書くらしく。
printfn "Hello World %d!!" 1
まさかletすると直でかけないとは...ほむ。
let hello = "Hello" + " World" printfn "%s" hello
string.FormatやConsole.WriteLineがかけるということで、もう、こっちのほうが書きやすい..。
Console.WriteLine("Hello World {0}!!", 1)
この時点の気分は完全にこれです。
- guitarrapc_tech (@guitarrapc_tech) September 3, 2016
- guitarrapc_tech (@guitarrapc_tech) September 3, 2016
Boxingかかってもこれでいいんじゃないか気分になったところです。
let print (x : Object) = Console.WriteLine(x);
ここで、sprintfn
なんて便利関数があるということでサクッと乗せ換えです。*2
これで、dump
関数につないで生きていけそうです。
sprintf "Hello %s! Number is %d" "world" 42 |> dump sprintf "Hello %s! Number is %d. %s" "world" 42 "hoge" |> dump
そういえば、型指定はstringでもいいのにメソッドの時はStringなんですね。
https://docs.microsoft.com/ja-jp/dotnet/articles/fsharp/language-reference/fsharp-types
2016/9/4 修正
System.Stringとstringというより、なるほど用途... 言われないと気付けなかった気がします。
- はぇ~☆ (@haxe) September 3, 2016
- はぇ~☆ (@haxe) September 3, 2016
- guitarrapc_tech (@guitarrapc_tech) September 4, 2016
- はぇ~☆ (@haxe) September 4, 2016
- はぇ~☆ (@haxe) September 4, 2016
- はぇ~☆ (@haxe) September 4, 2016
修正ここまで
正直、文字列にちょちょいとデバッグ見たいという時なら、%A
でいい感じに自動的に変換してくれるのばかり使いそうな気がします。
部分文字列を抽出
さて、続けて部分文字列抽出
です。C# ならstring.Substring()
ですが、 F# はスライシングっぽく書けるようです。便利。
let str1 = "hoge" printfn "%s" (str1.[0..2])
文字列連結は + と ^ の両方でいけるようで。なんで2つのオペレータがあるんですかね。不思議。
2016/9/4 修正
なるほどOCaml由来。
はぇ~☆ (@haxe) September 3, 2016
修正ここまで
StringのChar取り出しは、str.Chars(<int>)
かstr.Chars(<int>)
。ただしstr.Chars(<int>)
だとCharになってstr.Chars(<int>)
だとstringなのは型推論強いけど、うぬぬ。
let str1 = "hoge" printfn "%c" (str1.[0]) printfn "%s" (str1.[0..1])
2016/9/4 修正
言われてみると .CharsはC# で使ってことなかったです。
はぇ~☆ (@haxe) September 3, 2016
修正ここまで
List
List処理はびっくりでした。正直F# すごい。[]
でくくって、[]
で要素を分割。[]
で1つの文字とListの連結 *3、[]
で合成。
簡単です。PowerShellとか目じゃない。
let list1 = [ "a"; "b" ] let list2 = "c" :: list1 let list3 = list1 @ list2
さらに
徹人 (@t_tetsuzin) September 3, 2016
yield!
でList内Listの展開までされてあら素敵。seqやPowerShell同様yield!
でつなげたり、途中の値の指定もできて便利。
なるほど(?)
徹人 (@t_tetsuzin) September 3, 2016
関数とパイプライン
関数も変数同様let
で宣言できて、引数は関数名の後ろで指定と。
let f x = x*x f 2 |> dump
型指定はなるほど
にゃーん (@kekyo2) September 3, 2016
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
なるほど簡単。
この先はいずれということで。
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
名前付きで分解されるの便利ですねぇ。
殴り書きコード
いったんおいておきます。
https://gist.github.com/guitarrapc/70a7882e390583b74f240ad77f0ae0ea
続き
いずれ。
guitarrapc_tech (@guitarrapc_tech) September 3, 2016
https://pocketberserker.hatenablog.com/entry/20120503/1336041024
追記
*1:if-elseだと条件最後まで読まないとぐんにょりしますからね
*2:sprintf遅かったけど、 https://stackoverflow.com/questions/16742189/performance-of-sprintf-vs-string-format 3.1で40x爆速になったんですねhttps://t.co/5qlzKeMkg2
*3:便利!
*4:PowerShellやってたせいでしょうが。