高専カンファ in 三重2 行ってきた
高専カンファレンス in 三重2 - 高専カンファレンス Wiki
授業発表ということで、発表もしてきました。
「高専生だし、このくらい飛ばしても大丈夫やろー」と思って飛ばしすぎましたごめんなさい。
想定聴衆を、
としていたんですが、完全に読み違えました。
それならそれで、Visitor の説明とかせずに「面倒だね」くらいで流してしまえばよかったかもしれません。
それでも、判別共用体のところはなんとなくわかってもらえたようでした。
懇親会では、「F# 使ってみたい!」という人も出てきたので発表してよかったです。
発表が難しかったようであれば、前提知識ほとんどなくてもそれなりに分かるように作ったものがありますのでどうぞ(ページ数圧縮してなくてすみません・・・)。
あと、忘れかけてたんですけど、
こんなのも書いていますのでどうぞ。
ここら辺までで F# 気になった方は、以下の書籍をお勧めしておきます。
日本語で読める範囲であれば、この 2 冊以外は考えなくていいです。
F# とは異なりますが、F# の元となった OCaml という言語の light な部分を使って書かれた
プログラミングの基礎 (Computer Science Library)
- 作者: 浅井健一
- 出版社/メーカー: サイエンス社
- 発売日: 2007/03
- メディア: 単行本
- 購入: 15人 クリック: 383回
- この商品を含むブログ (97件) を見る
もお勧めです。
第2回 関数型言語勉強会 大阪でしゃべってきてた
もう一か月以上前じゃないですか・・・
ということで、しゃべってきました。
C# (や Java) を使っているときよりも型の力を借りましょう、ということを主軸に話しました。
C# では色々と「型を定義することをためらわせる」力が働きます。
それに慣れていると、F# を使っても型を全然定義せずに string や int といった型をそのまま使ってしまいます。
Better C# として F# を使うのであればそれでも全然かまわないと思うのですが、やはり F# であることの恩恵は知ってほしいし感じてほしいです。
で、発表では簡単な値クラスを例にとって説明しました。
Twitter の反応を見ると、「そもそも Equals や GetHashCode を実装するようなことがない」との意見がありましたが、これの原因を考えてみてほしいのです。
もしそれが、面倒だからという理由なのだとしたら、それこそが「型を定義することをためらわせる力」です。
値クラスを導入することで、コードの表現力は増し、より説明的な記述をすることができ、型チェックも付いてきます。
型を定義したくないがためにコメントや変数名にその情報をエンコードしているのであれば、それはその言語が持つ問題として認識しましょう。
LL/ML Advent Calendar を振り返って
こんにちは、bleis (狼) です。
狼の由来はこんなかんじです。
bleisさんAdvent Calendar書いたのかしらと検索してたらbleisの画像検索結果なるものが。狼男なんなの。 URL
これがbleisさんだ! URL
bleisさん、肉食っぽすぎてやばい
bleisさん、おじいさんっこだったの・・・。 URL
やばい。
やばいですね。
さて、LL/ML Advent Calendar を振り返ります。
ふりかえりということで、やはりここは KPT でしょうかね。
Keep
- 来年もやる
- 自動登録システム(手動)
- 参加者の皆さん案外ノリがよくて大変よろしいですね
Problem
- よんたの暴走
- やっぱり LL 的なイベント開きたい
- もっとちゃんと運用する
- 書いたらどこに (誰に) 通知するの?
- カレンダーへの反映は誰がどうやるの?
- 参加表明の仕方
Try
- めざせ 3 トラック!
- クリスマソンとの合同企画?
ということで、来年もやりたいです。
あ、そういえばこれってクリスマスまでを数える系イベントでしたね。
みなさんクリスマスはいかがお過ごしでしょうか?楽しかったですか?
ぼくは普通に仕事してました。
はぁ・・・
LL/ML Advent Calendar について
さて始まってしまいました、LL/ML Advent Calendar です。
LL/ML 名古屋についてご存じでない方もいると思いますので、初日は LL/ML Advent Calendar について説明することにします。
LL 名古屋とは?
まず、LL 名古屋についてです。
LL 名古屋は、Lightweight Language 名古屋の略ではありません。
いいですか? Lightweight Language 名古屋の略ではないのです (大事なことなので 2 回言いました)。
LL 名古屋は、輪番休日制度により勉強会が開きにくくなっていた鬱憤と、LL Planet に行けない残念さと、Coq 食えずにドーナツ食った美味しさと、よくわからないテンションによってスタートしました。
詳しくは、
から数枚見ていただければ分かると思います。
さて、LL が Lightweight Language の略ではないとしたら、何の略なのか、という点が気になると思いますが・・・
えっと、何でもよいです。あなたが LL と思うものが LL です。
タイトルに L が 2 つ付きさえすれば、それを LL と言い張ることができます!
こんな感じに、各種方面にケンカ売ってるような感じがする LL の定義ですが、まぁ、その。
で、発表者を募らねばなりません。が、ページを作ったらそれに反応する人々が・・・
これはもう発表者に突っ込まざるを得ない、ということで、片っ端から突っ込んでいきました。
これがすべての始まりです。
ML 名古屋とは?
LL 名古屋を開催した後、
LL名古屋 開催経緯 - みずぴー日記
- 楽しかったけど、こういうのは年に一回ぐらいでお腹いっぱいです。
とか言ってたくせに、約半年後に 2 回目開催ですよ。
それがML 名古屋です。
LL 名古屋との違いは、L が 2 つ、ではなく、M と L がタイトルに含まれていればよい、という程度の違いしかありません。
LL/ML Advent Calendar とは?
で、LL/ML Advent Calendar です。
全てはこの一言から始まりました。
LL/ML名古屋と同じ要領で、LL/ML Advent Calendarとか。
これに乗っかるよんたさん (と俺)
@athos0220 あたまいい・・・!
2012-11-20 18:27:31 via Tween to @athos0220
@bleis @athos0220 まじあたまいい!参加者も指名式ですね。
2012-11-20 18:34:12 via twicca to @bleis
他にも色々反応する人がいたので、その人たちを編集者に突っ込みつつイベントページを作りました。
何だお前らそんなに立てて欲しいのか?> LL/ML Advent Calendar
ハッシュタグに、何を血迷ったか #LLAdventJP なんてものを付けるみずぴーさん。
ハッシュタグつけた: LL/ML Advent Calendar #LLAdventJP - [PARTAKE] URL via @partakein
そしてちょっとでも反応した人々を担当に突っ込んでいくよんた・・・
あっという間に 24 日まで埋まってしまいました(募集開始から 2 時間半くらい。ここまで早く埋まった Advent Calendar もあまりないのでは?)。
その後も、埋まったことにより安心して言及してしまった人を片っ端から「予備」として登録していき・・・
予備の人がもう少し増えれば、並列化できるね・・。 #lladventjp
まさかの 2 トラック!
そんなこんなで今に至ります。
ルールとしては、タイトルに L が 2 つか、M 1 つに L 1 つ含めばよいという、非常にゆるい感じになっています。
普通に Lightweight Language について書いてもいいですし、無理やり L とか M とか捻出してもいいです。
まぁカオスになることは火を見るよりも明らかですね!
楽しければそれでいいのさ!
ということで、明日はえいとすさんとでこちゃんです。
LISP 勢ですね。こわいです。
C# から使いやすい F# コードの書き方
さて始まりました、F# Advent Calendar 2012 です。
今年は、「実用」がテーマと言うことで、F# で書いたコードを C# から使いたくなった時に気を付けるべきポイントなどをまとめました。
F# と C# で異なる名前を付ける
F# では、module に定義する関数や変数の名前は、lowerCamel で付けるのが一般的です (List.map など)。
しかし .NET の世界では、これらの名前は基本的に PascalCase で付けることになっています。
CompiledName 属性を使うことで、この差を埋め、F# からは lowerCamel に、C# からは PascalCase に見える名前を付けることができるようになります。
(* F# *) module Util = [<CompiledName "ToStr">] let toStr x = sprintf "%A" x
これで、F# からは Util.toStr という名前で、C# からは Util.ToStr という名前でアクセスできる関数が定義できます。
CompiledName 属性は、関数や変数だけでなく、型に付けることも可能です。
(* F# *) [<CompiledName "MyUtil">] module Util = [<CompiledName "ToStr">] let toStr x = sprintf "%A" x
こうすることで、Util ではなく、MyUtil というモジュールとして公開されることになります。
C# とは関係ないですが、型引数を持たない型名とモジュール名がかぶってしまうとコンパイルが出来ません。
(* F# *) type User = { Name: string; Age: int } (* Userが重複しているのでコンパイルエラー *) module User = let name user = user.Name
この場合、CompilationRepresentation 属性を使うことで回避できます。
(* F# *) type User = { Name: string; Age: int } [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] module User = let name user = user.Name
こうすると、C# からは User モジュールは UserModule という名前で見えるようになります。
C# からはオーバーロードされて見えるメソッド
CompiledName 属性について、少し前にこんなやり取りがありました。
F# と C# を併用して組んでると、関数名を camelCase にするか CamelCase にするかすごく迷う・・・。どうしたらいいのん? .
@u_1roh CompiledName属性を使うといいですよ
2012-10-25 15:53:14 via Tween to @u_1roh
@bleis おお!おおーっ!もしかして関数のオーバーロードもこれで勝つる!?
2012-10-25 16:06:30 via Tween to @bleis
@u_1roh 関数のオーバーロードは、クラスを使うとできます
2012-10-25 16:07:31 via Tween to @u_1roh
@bleis あ、それは分かってます。言葉足らずですいません。F# 側では違う名前でも(オーバーロードしていなくても)、CompiledName 属性で同じ名前を与えれば C# 側からはオーバーロードに見えるのかな?と思いまして・・・。
2012-10-25 16:09:34 via Tween to @bleis
@u_1roh あ、なるほど。F#上は関数fと関数gだけど、C#上からはどちらもFとして見せたい、ということですね。その発想はなかった!
2012-10-25 16:22:56 via Tween to @u_1roh
@bleis そーです、そーです!
2012-10-25 16:23:48 via Tween to @bleis
つまりこういうことができるわけです。
(* F# *) module StreamUtil = [<CompiledName "Write">] let writeByte (b: byte, stream: Stream) = ... [<CompiledName "Write">] let writeInt (i: int, stream: Stream) = ...
これで、C# 側からは Stream という名前でオーバーロードされて見え、F# 側からは別々の関数として見えるようになります。
ちなみに、関数名以外全く同一のシグネチャを持つ 2 つの関数に、CompiledName で同じ名前を指定すると、dll の書き込みに失敗するという、あまり見ないコンパイルエラーになります。
戻り値のみが異なる場合は後勝ちになるようです。
C# に暗黙の型変換を提供する
F# には暗黙の型変換は存在しませんが、C# 用に提供したい場合があります。
これは、op_Implicit を実装することで実現できます。
(* F# *) type Str = { Value: string } with static member op_Implicit(str) = str.Value
こうすることで、C# 上で以下のような記述が可能になります。
// C# var s = new Str("hoge"); string str = s;
しかし、この方法は型引数を持つ型に対してはなぜか使えません。
(* F# *) type Wrapper<'a> = { Value: 'a } with static member op_Implicit(wrapper) = wrapper.Value
// C# var w = new Wrapper<string>("hoge"); // 型 'Wrapper<string>' を型 'string' に暗黙的に変換できません。 string str = s;
これが出来たら、素敵なことができるんですが・・・
どうにかできる人がいたら連絡を!
拡張メソッド
F# には型拡張*1という仕組みがあり、これを用いることで型に関数などを後付けできるのですが、この方法で実装した拡張は C# 側からアクセスすることができません*2。
C# 側からもアクセスできる拡張メソッドを定義したい場合は、Extension 属性を使用します。
(* F# *) open System.Runtime.CompilerServices [<Extension>] module HogeExtensions = [<Extension>] [<CompiledName "Hoge">] let hoge(x) = ...
こっちでもいいです。
(* F# *) open System.Runtime.CompilerServices [<Extension>] type HogeExtensions = [<Extension>] static member Hoge(x) = ...
使い分けとしては、F# 側からはモジュールとして使いたいけど、C# 側からは拡張メソッドとして使いたい、という場合に前者を、どちらからも拡張メソッドとして使いたい(もしくは、F# 側からは使わない)、という場合は後者を使うといいでしょう。
前者の場合、モジュール名はもうちょっと考えたいところですが。
判別共用体を C# 側から使いたい
インターフェイスやクラスなど、C# 側にも対応する機能が存在するものは F# で定義しても C# 側からは簡単に使えます。
また、モジュールとレコードも、C# 側から自然に扱えます。
レコードにメソッドを定義できるのも、結構便利ですよね。
(* F# *) type User = { Name: string; Age: int } with override this.ToString() = sprintf "%A" this
しかし、判別共用体だけは簡単に C# から使うことができません*3。
例えば、成功と失敗を表す以下の判別共用体を定義したとします。
(* F# *) type Result<'TSuccess, 'TFailure> = Success of 'TSuccess | Failure of 'TFailure
これを C# 側から使おうとする場合・・・
// C# // 生成 var res = Result<int, string>.NewSuccess(42); // 分岐 switch (res.Tag) { case Result<int, string>.Tags.Success: int s = ((Result<int, string>)res).Item; // Successの時の処理 break; case Result<int, string>.Tags.Failure: string f = ((Result<int, string>)res).Item; // Failureの時の処理 break; }
これはひどい。
これでは使えたものではないので、これを操作するユーティリティを提供するといいでしょう。
F# からは関数をパイプライン演算子で繋いで使いたいので、モジュールを使うことにします。
しかし、C# 側からはそれでは面倒なので、拡張メソッドを使うことにします。
その前に、何にでも使える便利なメソッドを定義しておきましょう。
(* F# *) open System type Result<'TSuccess, 'TFailure> = Success of 'TSuccess | Failure of 'TFailure with member this.Match(ifSuccess: Func<_, _>, ifFailure: Func<_, _>) = match this with | Success x -> ifSuccess.Invoke(x) | Failure x -> ifFailure.Invoke(x) member this.Action(ifSuccess: Action<_>, ifFailure: Action<_>) = match this with | Success x -> ifSuccess.Invoke(x) | Failure x -> ifFailure.Invoke(x)
これだけでも、分岐処理を非常に簡単に記述することができるようになります。
// C# // 生成 var res = Result<int, string>.NewSuccess(42); // 分岐 var x = res.Match<string>( s => (s + 1).ToString(), f => "!" + f); // 分岐(副作用) res.Action( s => /* Successの時の処理 */, f => /* Failureの時の処理 */);
あとは、F# 用にモジュールを、C# 用に拡張メソッドを提供するだけです。
(* F# *) module Result = let fold f seed = function Success x -> f seed x | Failure _ -> seed let bind f = function Success x -> f x | Failure x -> Failure x let map f = function Success x -> Success(f x) | Failure x -> Failure x let count = function Success _ -> 1 | Failure _ -> 0 let exists f = function Success x -> f x | Failure _ -> false let forall f = function Success x -> f x | Failure _ -> true let iter f x = fold (fun _ x -> f x) () x let isFailure = function Success _ -> false | Failure _ -> true let isSuccess = function Success _ -> true | Failure _ -> false open System.Runtime.CompilerServices open System.ComponentModel [<EditorBrowsable(EditorBrowsableState.Never)>] [<Extension>] type public ResultExtensions = [<Extension>] static member Fold(x, seed, f: Func<_, _, _>) = Result.fold (fun a b -> f.Invoke(a, b)) seed x [<Extension>] static member Bind(x, f: Func<_, _>) = Result.bind f.Invoke x [<Extension>] static member Map(x, f: Func<_, _>) = Result.map f.Invoke x [<Extension>] static member Count(x) = Result.count x [<Extension>] static member Exists(x, f: Func<_, _>) = Result.exists f.Invoke x [<Extension>] static member Forall(x, f: Func<_, _>) = Result.forall f.Invoke x [<Extension>] static member Iter(x, f: Func<_, _>) = Result.iter f.Invoke x [<Extension>] static member IsFailure(x) = Result.isFailure x [<Extension>] static member IsSuccess(x) = Result.isSuccess x
EditorBrowsable 属性はなくてもいいんですが、付けておくと VS の IntelliSense を汚さないのでいい感じです。
コンピュテーション式に対応するクエリ式の提供
コンピュテーション式を提供する場合、C# 側にはクエリ式を提供することを考えましょう。
例えば先ほどの Result は、コンピュテーション式を提供すると便利です。
(* F# *) type ResultBuilder () = member this.Bind(x, f) = Result.bind f x member this.Return(x) = Success x member this.ReturnFrom(x: Result<_, _>) = x member this.Zero () = Failure() member this.Combine(e1, e2) = Result.bind (fun () -> e2) e1 let result = ResultBuilder()
result を提供することで、失敗の場合を気にせずに処理を書くことができるようになります。
(* F# *) let res = result { let! x = tryGetX() let! y = tryGetY() let! z = tryGetZ() let adjust = getAdjust() return adjust * double (x + y + z) }
C# 用には、Select と SelectMany を提供し、クエリ式が使えるようにします。
(* F# *) [<EditorBrowsable(EditorBrowsableState.Never)>] [<Extension>] type public ResultExtensions = (* 略 *) [<Extension>] static member Select(x, f: Func<_, _>) = Result.map f.Invoke x [<Extension>] static member SelectMany(x : Result<'a, 'TFailure>, f: Func<'a, Result<'b, 'TFailure>>, selector: Func<'a, 'b, 'c>) : Result<'c, 'TFailure> = x |> Result.bind (fun x -> f.Invoke x |> Result.bind (fun y -> Success (selector.Invoke(x, y))))
コンピュテーション式での let! が from in に、let が let に、return が select になったと思って使えます。
// C# var res = from x in TryGetX() from y in TryGetY() from z in TryGetZ() let adjust = GetAdjust() select adjust * (x + y + z);
ここまで考慮すれば、C# 側からもそれなりに使いやすいライブラリが提供できるようになります。
F# だけで閉じずに生きたいものですよね。
え? VB はどうしたって?
や、VB 分かりませんし・・・
次は・・・
次は、Gab_km さんの NaturalSpec なネタらしいです。楽しみですね!
あ、参加者の皆さんは、ネタかぶりを避けるためにも #FsAdventJP タグをつけて書く予定のネタを Twitter でつぶやいとくと良いかもです。
いげ太さんに reply を飛ばしておくとなおよし。
わかめのモナ化で色々やってきた
ハンズオンの前座として、Maybe モナドと State モナドの解説をしました。
発表も資料も割と好評なようで、「モナドの教え方」みたいなものの感覚もつかめたので発表してよかったです。
モナド則とかそういう、ふつうのプログラマにとって難しい言葉は使ってないので、気負わずに読めると思います。
ただやはりState モナドは難しかったようで、ハンズオンでも State の追加説明をしたりと、ここら辺はまだ改善の余地ありですね。
実は書きたかったことが書ききれてはいないので、どっかで書きたかったことを追加したバージョンも作りたいところです。
参加者の反応は上々なようです。よかったよかった。
皆様大変ありがとうございました!往復二万円の価値が十分にある勉強になりました!来年またよろしくお願いします! #わかめモナ化
@yoshihiro503 @bleis @mzp @keita44_f4 @joker1007 @vvakame @kyon_mm 今日はありがとうございました!本当にいい勉強会で、なんだかモナドが見えてきたような気がします
@zaki50 @bleis @keita44_f4 @kyon_mm @mzp @vvakame @yoshihiro503 楽しかったです!名古屋まで行った甲斐がありました。ありがとうございます。
2012-11-18 00:16:33 via YoruFukurou to @zaki50
みなさまありがとうございました!長野から駆けつけた価値が十分にありました!第2回もぜひ開いてください! #わかめモナ化
Functor型クラスのところ読み直し。昨日の特訓?が聞いたか、大分意味がわかるようになってきた!なごやすごい! 『すごいHaskellたのしく学ぼう!』 5分 5ページ URL #studyplus
あー、でも来年・・・あー、うー。