BBS
リストの途中まで処理したい場合 - いげ太
2009/03/09 (Mon) 00:03:20
たとえば、要素値が 0 であるところまでのリストがほしいという場合、単に Seq.take_while ((<>) 0) ではダメ(0 が現れる手前までになる)なので、次のような関数を用意するとよいかと。
let take_find p (inp: seq<_>) =
seq { use e = inp.GetEnumerator()
let latest = ref (Unchecked.defaultof<_>)
while e.MoveNext() && (latest := e.Current; not (p !latest)) do
yield !latest
if p !latest then
yield !latest }
[3..(-1)..(-1000)]
|> take_find ((=) 0)
|> Seq.fold (*) 1
|> printfn "%d"
Re: リストの途中まで処理したい場合
- 管理人(deko_pon)
2009/03/09 (Mon) 23:00:13
例外の箇所へのコメントありがとうございます。
私は正格評価好き派で
ほとんどSeqを使ったことがないので上記コードの理解するのに結構時間かかってしまいました
とりあえず思ったのは
「回数が定まっているループを途中で抜けたい」っていう欲求が
Lazyな世界ではそこまでないというか
勝手に必要なとこだけ計算されるし
「必要なところまでループする」って感じですね。
breakやらを必要としない理由が何となくですが納得出来ました。
whileにyieldが出てくる構文なんて知らないぞ、と思って調べたら
Sequence Expressionっていうやつなんですね、これは知らなかった
・参考
http://blogs.msdn.com/dsyme/archive/2007/09/22/some-details-on-f-computation-expressions-aka-monadic-or-workflow-syntax.aspx
そもそもyieldはあまり理解してなかったんですが
これっていわゆるコルーチンですよね。
schemeのambみたいなのも頑張ったら書けるのかな
ところで、このBBSは勝手にソースコードをfold_leftしてしまう><ので
頂いたコードを少し整理して#light "off"で書き直してみました。
let take_find p (inp: seq<_>) =
seq {use e = inp.GetEnumerator() in
while e.MoveNext() && (p e.Current) do
yield e.Current;
done;
yield e.Current
} in
[1;2;0;4;5]
|> take_find ((<>)0)
|> print_any;;
最後に、このケースなら、こんな感じでお茶を濁してしまうかも
let take_till p a lst = Seq.append (Seq.take_while p lst) [a];;
take_till ((<>)0) 0 [1;2;0;4;5];;
Re: リストの途中まで処理したい場合 - いげ太
2009/03/12 (Thu) 10:56:53
僕の版の take_find と、deko_pon さんの版の take_find とで、以下のケースについてテストしてみるとおもしろいですよ。
-- 空のリストを渡した場合
-- すべてのリスト要素が条件に合致“しない”場合(deko_pon さん版のは条件式が逆になるので、「合致“する”場合」と言うべきかな)
-- print などの副作用を含むリストを渡した場合
僕は Seq.take_while のライブラリ ソースをもとに take_find を作ったのですが、なぜそういうコードになっているのか、このテストを行うことで理解できると思います。
Re: リストの途中まで処理したい場合
- 管理人(deko_pon)
2009/03/13 (Fri) 00:09:58
テストしてみましたが、
自分が作ったやつは空リストが返ってくる時はエラーが出てしまいますね
Unchecked.defaultofがわからなかったので調べてみたところ
・C#の世界では、参照にはnullが代入可
・F#の世界では、そうなっていない箇所もある(リスト、タプルなど)
→この違いを吸収するもの
参考
http://blogs.msdn.com/dsyme/archive/2008/05/02/full-release-notes-for-f-1-9-4.aspx
自分はこんな所まで考えたことが無かったのですが
それが上記コードの違いになったみたいです。難しい・・
テストケース
(自分が作った方はtake_find2に改名)
take_find ((<>)0) [] |> print_any;;
seq []
take_find2 ((=)0) [] |> print_any;;
Error: Enumeration already finished.
take_find ((<>)0) [0;0;0] |> print_any;;
seq [0; 0; 0]
take_find2 ((=)0) [0;0;0] |> print_any;;
Error: Enumeration already finished.
副作用
take_find ((=)0) [1;2;3;(printfn "%d" 4;4);5;(printfn "%d" 6;6);7] |> print_any;;
let g:unit->int=let a = ref 10 in fun b -> a:=!a-1;printfn "%d" !a;!a;;
take_find ((=)0) [g();g();g();0;g()] |> print_any;;
Re: リストの途中まで処理したい場合 - いげ太
2009/03/13 (Fri) 10:46:46
> print などの副作用を含むリストを渡した場合
あーっと、すいません。ここはリスト(list)じゃなくてシークエンス(seq)と言わないといけないところでした。
let xs = seq { for n in 0..9 -> printf "%d " n; n };;
take_find ((=) 3) xs;;
// 0 1 2 3 val it : seq<int> = seq [0; 1; 2; 3]
take_find2 ((<>) 3) xs;;
// 0 0 1 1 2 2 3 3 val it : seq<int> = seq [0; 1; 2; 3]
> Unchecked.defaultof
C# でいうところの default キーワードですね。その名の通りですが、型パラメタとして指定された型のゼロ初期化値を返すものですね。
(ご参考)
http://msdn.microsoft.com/ja-jp/library/xwth0h0d(VS.80).aspx
http://lorgonblog.spaces.live.com/Blog/cns!701679AD17B6D310!725.entry
「例外」について - いげ太
2009/02/19 (Thu) 13:47:09
例外のガイドラインについては、以下の記事が参考になるかと。
[引用]
.NET Framework における例外の分類は主に二つにわけられる。
* ランタイムが送出する例外
* アプリケーション定義の例外
.NET においては、ランタイムが出す例外はほぼきれいに SystemException から継承されたものに統一されている。
ほぼと書いたのは一部例外があるからだ。
これは Microsoft の設計ミスであり、そのため .NET 1.x と .NET 2.0 ではアプリケーション定義の例外に関する方針が変わっている。
.NET 1.x ではアプリケーション定義の例外は ApplicationException から派生させるというガイドラインが出ていたが、 .NET 2.0 においては Exception から直接派生するようにされている。
「プログラミング .NET Framework 」によれば、 SystemException と ApplicationException を各例外の基底型とすることにはあまり意味を感じないと書かれており、むしろ混乱するとも書かれている。
そこで、Microsoft も方針を切り替えたようだ。
http://shinshu.fm/MHz/95.83/archives/0000127004.html
[/引用]
いちおう MSDN でも言及されてます。
[引用]
アプリケーションを設計するときに固有の例外の作成を必要とする場合は、カスタム例外を Exception クラスから派生させることをお勧めします。当初は、カスタム例外を ApplicationException クラスから派生させる必要があると考えられていましたが、実際にはあまり有用ではありませんでした。詳細については、「例外処理の実施」を参照してください。
http://msdn.microsoft.com/ja-jp/library/system.applicationexception.aspx
[/引用]
[引用]
ほとんどのアプリケーションでは、Exception クラスからカスタム例外を派生します。本来、カスタム例外は ApplicationException クラスから派生しなければならないと考えられていましたが、実際には、それによって大きな価値が付加されたことはないようです。
http://msdn.microsoft.com/ja-jp/library/seyhszts.aspx
[/引用]
じゃあ、exception による定義で OK かというとそうでもなく。いまのところ OK ではありますが、Draft の「9.4 Exception Definitions」の項にも「Exception values may also be generated by defining and using classes that extend System.Exception.」という注意書きがあるように、今後、仕様(推奨される記法)が変わる可能性は大いにあります。
あと余談ですが、failwithf なんて便利なものもあります。
let less_than_10 n =
if n < 10 then
n
else
raise (failwithf "'n' = %d: It should be less than 10." n)
2009/02/19 (Thu) 22:34:44
おわ、ApplicationExceptionクラスの箇所に確かに書いてますね、やってしまった・・ご指摘感謝です。
これ、.Net2.0からってことは・・.Netのプログラム経験があまりないのがばれてしまいます(笑
また、記事については後日修正したいと思います。
それと、例外の使い方については、少なくとももう一つは書く予定でいて、そのうちの一つはProgramming in OCamlに載っていたちょっと例外っぽくない使い方についてです。(忘れっぽいのでもう忘れたのですが・・また調べる予定)
failwithf
ご紹介、ありがとうございます。
実は、記事を書く際に調べたのでこれの存在は知っていた(Expert F# p85)のですが、例外の記事の量が結構多くなってしまっていたのと、入門レベルのサイトということでとりあえず2個だけに絞って載せました。
こういうのって悩ましくて、記事全体の量が増えれば重要な所は相対的に量が少なくなるので・・。
他にもraise End_of_fileなどいくつか載ってたので、Appendixに分離してみようかなと思います。
ところで、お世話になっているので、もしよろしければ、いげ太さんのブログへリンク張っても良いでしょうか?
Re: 「例外」について - いげ太
2009/02/20 (Fri) 21:36:26
> failwithf
なるほど。あえて書かなかったと。網羅的に書くのはリファレンスの役目で、入門サイトとしては取捨選択して提示することが肝要、ですね。
> ブログへリンク
どうぞです。リンクフリーですー。
2009/02/15 (Sun) 21:01:46
はじめまして!
僕がF#の勉強始めたときにこんなサイトがあれば良かったのに!(笑)とすごく思いました。
情報も整理されててすごく見やすいです!
機会がありましたら、F#関連のことで交流させていただきたいです。 よろしくお願いします。
それでは、失礼いたします。
2009/02/17 (Tue) 01:15:58
net-kさん、はじめまして。
すみません、今さきほど書き込みに気づいて、お返事が遅くなりました。
お褒めいただきありがとうございます。ホームページにきてもらって、しかもこうして反響があると作った自分としては嬉しい限りです。
ほんと、ちょっとずつ作っていた甲斐がありました(^^)
情報の分類については、今になって見直してみると、直したいところが色々あるのですが(特に前半あたり)、今の基本的な方向としてはとりあえず追加&文法事項の網羅を目指しています。
>機会がありましたら、F#関連のことで交流させていただきたいです。 よろしくお願いします
こちらこそ、是非よろしくお願いします。
はじめまして - いげ太
2009/02/06 (Fri) 19:02:25
楽しく拝見させてもらってます。すごいですね。現状、ここまでまとまった F# の情報が日本語で読めるとこはほかにないんじゃないかと思います。
ところで、ちょっと気になったところもあったので、よければご参考ください。
* [Basic] - [条件分岐] に elif(else if の略記)についての記載がない
* BBS から「サイトへ戻る」リンクが欲しい
Re: はじめまして - 管理人(deko_pon)
2009/02/07 (Sat) 19:10:34
いげ太さん、はじめまして。
記事についてのご指摘ありがとうございます。
elifについてですが、この構文初めて知りました。
BBSとあわせて、該当記事は後ほど修正したいと思います。
構文定義を確認したところ、1.9.4と1.9.6で変更されているみたいですね
1.9.4
expr :=
| if expr then expr else expr -- conditional
| if expr then expr -- conditional statement
1.9.6
expr :=
| if expr then expr [elif expr then expr]* [else expr] -- conditional expression
ただ、アンインストールしてしまったので確認してないのですが
バージョン1.9.4でもlexyacc.htmlにはサンプルがのっているので
おそらく昔からelifはあったのだろうと推測します。
ところで、F#のブログ書かれているいげ太さんですよね?
毎回新しい発見があって、文も楽しいので
よく記事を読ませてもらってます。
こうして利用してもらえるととても作った甲斐があります。
マイペースなサイトですが、よろしくお願いいたします。