tech.guitarrapc.cóm

Technical updates

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

普段、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.dllSystem.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 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 です。

Redirecting…

しかしみてもいい感じの F# Tutorial みたいなサイトはありません。ということで、上からとググって見つけたいくつかを参考に見ていきます。

サイト 概要
言語ガイド - F# | Microsoft Learn 言語仕様です
F# Programming - Wikibooks, open books for an open world Wiki ですが、想像以上にきれいにまとまっていたりサンプルが多いのでいい感じです。
fsharp-cheatsheet チートシートといいつつ、結構ほんとここを参考にしました。
neue cc - F# TutorialをC#と比較しながらでF#を学ぶ 漂白されていません。何気に一番ピンとくるぐらい具体的に書かれています。
文字列 - F# と C# の記法比較 結構知りたい情報あって嬉しいです。
タプル - F# と C# の記法比較 Tuple もここがわかりやすかったです

F# チュートリアル、むむっと思っていたのですが、記事書きつつ見直していて Try F# がプレイグラウンドもあるし最高な気がします。

https://www.tryfsharp.org/Learn

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

変数 と関数の 宣言

基本中の基本です。open とかはいったん知らないふりで。

let で変数も関数も宣言できるようです。

let str = "stringだぞ" // System.String 型の変数 str を宣言

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

追記

むむっ...。

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