tech.guitarrapc.cóm

Technical updates

F# をLearning F# をかじりながら学んでみる

普段、C# を触ってるのですが、switch文で苦しい思いをすることがたびたびあります。C# 7.0リリースはよ、と思いつつもいい書き方ないかなぁと探すといい記事に巡りあうことができます。

https://www.kekyo.net/2016/05/15/5859

単純に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自体は伝道師の素晴らしい記事があるのでそちらで。

https://takeshik.org/blog/2014/12/06/try-linqpad/

https://takeshik.org/blog/tags/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をインポートします。

https://msdn.microsoft.com/ja-jp/library/system.servicemodel.discovery.announcementclient(v=vs.110).aspx

それでもエラーが出ます。今度はMetadataExchangeClientです。

System.ServiceModel.Description.dllはありません。System.ServiceModel.dllをインポートします。

これでSystem.ServiceModel.dllSystem.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って何かと思ったら、型推論しておいてコンパイル時に型確定してその型の分だけ関数を生成するらしい... すごい..。

inlineとrecの併用できないんですねー。ほむほむ。

2016/9/4 修正

型注釈...! なるほどキャストとは違うのですね。型推論が強力というか前提というか、ほむほむ。象徴的に感じます。

はぇ~☆ (@haxe) September 3, 2016

修正ここまで

さて、もう1つほしいのが、.Dump() のオーバーロードです。.Dump()に名前を付けて表示できるので。適当に書くとエラーが。

むむっと思っていると、Overloadはツラいので別名の関数がよさそうとのこと。パイプラインの入力は優先度が低くて後から入ってくるのはPowerShellのValueFromRemainingArgumentsも似た挙動ですね。

徹人 (@t_tetsuzin) September 3, 2016

https://technet.microsoft.com/en-us/library/hh847743.aspx

適当に書いてみました。

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です。

https://fsharp.org/learn.html

しかしみてもいい感じの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#がプレイグラウンドもあるし最高な気がします。

https://www.tryfsharp.org/Learn

気を取り直して順番に見ていきます。といっても、このあたりと軌跡は似てます。

変数 と関数の 宣言

基本中の基本です。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)

この時点の気分は完全にこれです。

Boxingかかってもこれでいいんじゃないか気分になったところです。

let print (x : Object) = Console.WriteLine(x);

ここで、sprintfnなんて便利関数があるということでサクッと乗せ換えです。*2

https://stackoverflow.com/questions/2979178/f-string-format

これで、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というより、なるほど用途... 言われないと気付けなかった気がします。

修正ここまで

正直、文字列にちょちょいとデバッグ見たいという時なら、%Aでいい感じに自動的に変換してくれるのばかり使いそうな気がします。

部分文字列を抽出

さて、続けて部分文字列抽出です。C# ならstring.Substring()ですが、 F# はスライシングっぽく書けるようです。便利。

let str1 = "hoge"
printfn "%s" (str1.[0..2])

文字列連結は + と ^ の両方でいけるようで。なんで2つのオペレータがあるんですかね。不思議。

https://docs.microsoft.com/ja-jp/dotnet/articles/fsharp/language-reference/symbol-and-operator-reference/index

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

なるほど簡単。

この先はいずれということで。

https://www.kekyo.net/2016/05/15/5859

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やってたせいでしょうが。