らくがき

2008/04/09

[F#] .NETライブラリから返される「null値」はどのように扱えばよい?

boxing 機能を用いることで、「null値」と比較できます。
box (value) <> null
パターンマッチで判断することもできます。
match x.ReadLine() with 
| null -> None 
| line -> Some(line)
.NETライブラリで最終的に null値 を返す関数を扱うときの多相的関数(ジェネリック関数)
#light

/// Usage: "SeqGivenFuncTillNull<string>(sr.ReadLine)"
let SeqGivenFuncTillNull<'a when 'a:not struct> (f:unit->'a) :seq<'a> =
  seq {
        let v = ref (f()) in
        while box (!v) <> null do
          yield !v
          do v := f()
      }
StringReader を例にすると、以下のようになります。
let str = "testeseテスト!\ndeoweiru!ewp!";;
let sr = new StringReader(str);;

let lineSeq = SeqGivenFuncTillNull<string>(sr.ReadLine);;

let main =
  for line in lineSeq do
    printfn "%s" line
  done
StringReader は TextReader を継承しているので、 TextReader を拡張し、"SeqGivenFuncTillNull"を意識しないようにもできます。
/// Extend IO.TextReader.
type System.IO.TextReader with
  member it.to_string_seq = SeqGivenFuncTillNull<string>(it.ReadLine)

// Usage:
let s = "first line\nsecond line\nthird line\n"
let sr2 = new System.IO.StringReader(s)
for line in sr2.to_string_seq do   // Life is good :)
  printfn "%s" line;;
また、F#で扱いやすいように Option で包むことも可能です。
type System.IO.TextReader with 
    member x.TryReadLine() = 
        match x.ReadLine() with 
        | null -> None 
        | line -> Some(line)

let SeqGivenFuncTillNull2 (f:unit->'a option) :seq<'a> =
  seq {
        let v = ref (f())
        while (!v) <> None do
          yield Option.get (!v)
          do v := f()
      }

Labels:

2008/04/06

[F#] F#でのSTM(Software Transactional Memory)

Software Transactional Memory for F# http://cs.hubfs.net/blogs/hell_is_other_languages/archive/2008/01/16/4565.aspx wikiによると、STMとは 「データベーストランザクションに似た並行性制御機構であり、並列計算を行う際の共有メモリへのアクセス法である。」 とある。 つまり、複数のスレッドから読み書きされるデータの整合性を保証する役目ってことかな。 従来のロックベースの代替案として注目を集めているみたい。 そんなわけで、リンク先にあるのは、「STMをF#で使えるようにしてみましたー!」的なライブラリ (コア部分はC#で書かれてるんだって) おまけとして「サンタクロース問題」のソース付  

Labels:

2008/04/02

[F#] 二分木(binary tree)専用のmap関数

Memo : Node(左の木, このノードの値, 右の木)
type 'a tree = Nil | Node of ('a tree) * 'a * ('a tree)
再帰的: (通りがけ順, 中置記法)
let rec map0 f t =
  match t with
    Nil -> Nil
  | Node(l,v,r) -> let v' = f v in Node(map0 f l, v', map0 f r)
末尾再帰的: (帰りがけ順, 逆ポーランド記法)
let map3 f tree =
   let rec map' subtree build =
       match subtree with
           | Nil -> build Nil
           | Node(l, v, r) ->
               let v' = f v in
               map' l (fun l' -> map' r (fun r' -> Node(l', v', r') |> build)) in
   map' tree (fun x -> x)
テスト用コード:
// 関数のリスト
let maps : (string * (('a->'b) -> 'a tree -> 'b tree)) list = 
   [
    "map0 (non-tail-recursive):", map0;
    "map3 (Vladimir's functional):", map3;  
   ]

// 木の表示
let rec print_tree t = match t with
  Nil -> printf ""
| Node(l,v,r) ->
    printf "(";
    print_tree l; 
    printf "%d" v;
    print_tree r;
    printf ")"
;;

// テスト用の木の用意と、関数のリストに登録されている関数の実行、そして木の表示
let _ =
  let tree = Node(Node(Node(Nil,1,Nil),2,Node(Nil,3,Nil)),5,Node(Node(Nil,6,Nil),7,Node(Nil,8,Nil))) in
  print_tree tree; printf "\n";
  List.iter (fun (s,map) -> print_tree (map (fun x -> x+1) tree); printf "\n") maps
;;
テスト用の木構造 結果:
(((1)2(3))5((6)7(8)))  -- テスト用の木
(((2)3(4))6((7)8(9)))  -- 関数適用後の木 -- map0
(((2)3(4))6((7)8(9)))  -- 関数適用後の木 -- map3
再帰的な定義は、非常に簡潔でわかりやすく、実行速度も速い。しかし、メモリ消費量が大 末尾再帰的な定義は、簡潔ではなく、実行速度も遅い。しかし、メモリ消費量が小  

Labels:

2008/03/30

[F#] seqリストで ”指定した条件の直前まで” 抜き出すにはどうするの?

コード:
let takeUntil p (s : #seq<_>) =
  seq {
        let enum = s.GetEnumerator() in
        while enum.MoveNext() && not (p enum.Current) do
            yield enum.Current 
        done
      }
使用例:
let even_choose i = Seq.choose (fun x -> if (x &&& 1) = 0 then Some(x) else None) i

{1..10} |> even_choose |> takeUntil (fun x -> x > 6) |> Seq.fold ( + ) 0;;
結果:
val it : int = 12
{1..10} -> [1;2;3;4;5;6;7;8;9;10]  // リスト作成
even_choose -> [2;4;6;8;10]        // 偶数のみ選択
takeUntil -> [2;4;6]               // "6より大きい数が現れるまで" のリストをGet
Seq.fold -> 12                     // リストを合計 シチャイマス
 

Labels:

2008/03/26

[F#] 開発者との距離が近い

hubFSとかでのF#開発陣との距離の近さに改めて感心・感動 いい感じだゼ  

Labels:

2008/03/23

[F#] 約log2n回の再帰呼び出しでべき乗(累乗)を計算する

29の場合は、次のように表すことができる これを下から上へ辿ると 内側から計算すると n が偶数のときには以下が成り立つ (res : 計算結果) n が奇数のときには以下が成り立つ n=9 を2進数で表すと 1001 2進数の場合は、最下位ビットが1だと奇数なので、それを調べて処理分けしています。
let pow7 (x:bignum) n =
  let rec poww d = 
    if d = 1 then
      x
    else
      let res = poww (d >>> 1) in
      if (d &&& 1) = 0 then
        (res * res)
      else
        (res * res * x)
  in
  poww n
実行結果:
> pow7 2N 9;;
val it : bignum = 512N
 

Labels:

2008/03/22

[F#] 動的に数値型Interfaceを対応付けて関数を動かす

GetNumericAssociation なるもので動的に数値型Interfaceと対応付けられるみたい (http://cs.hubfs.net/forums/thread/3129.aspx) コード:
#light
open Math
open Math.GlobalAssociations

let Add (n1:'a) (n2:'a) =
    let tn : INumeric<'a> = GetNumericAssociation<'a>()
    tn.Add(n1, n2)
結果は次のように色々な数値型で動作させることが可能
Add 1 2
> val it : int = 3

Add 1. 2.
> val it : float = 3.0

Add 1L 2L
> val it : int64 = 3L

Add 1.f 2.f
> val it : float32 = 3.0f
デフォルトで float, int32, int64, bigint, float32, Complex, bignum が使えて、 他の型も登録することで選択肢に加えることができるんだって。 Add を他の関数で使うとこれまた同じ効果を得られる
let MyGenFunc (n1:'a) (n2:'a) (n3:'a) =
    (Add (Add (Add n1 n2) (Add n1 n3)) (Add n2 n3))
結果:
MyGenFunc 1 2 3
> val it : int = 12

MyGenFunc 1. 2. 3.
> val it : float = 12.0
パフォーマンスを気にする場合は、ひとつの関数定義の中にすべてを含めてしまい、
let MyGenFunc (n1:'a) (n2:'a) (n3:'a) =
    let tn : INumeric<'a> = GetNumericAssociation()
    let inline (+) (n1:'a) (n2:'a) = tn.Add(n1, n2)
    ((n1 + n2) + (n1 + n3) + (n2 + n3))
演算子を適切に定義することで可読性を高めることもできる らしい! 他の例としてはこんなの(http://cs.hubfs.net/forums/thread/3823.aspx
let sumList (data:'a list) =
  // get the F# 'INumeric' interface implementation for a type 'a
  let num = GetNumericAssociation<'a>()
  data |> List.fold_left (fun a b -> num.Add(a,b)) num.Zero;;

// sumList [1; 2; 3; 4; 5] ... returns 15
// sumList [1.1; 2.2; 3.3; 4.4; 5.5] ... returns 16.5
 

Labels:

2008/03/19

[F#] 重複を持たない組合せ(nCm)

こんな感じかな? 数学音痴なので基本式まんまです(^-^; コード:
let Combination n m =
  let rec CC n m =
    if m = 0 then
      1
    else 
      let i = CC (n - 1) (m - 1) in
        (n * i / m)
  in
  let i = CC n m in
    i;;
10 C 5 の場合:
Combination 10 5
  CC 10 5
    CC 9 4
      CC 8 3
        CC 7 2
          CC 6 1
            CC 5 0
            = 1 <-- i
          = 6 * 1 / 1 = 6
        = 7 * 6 / 2 = 21
      = 8 * 21 / 3 = 56
    = 9 * 56 / 4 = 126
  = 10 * 126 / 5 = 252
= 252
必ず割り切れるという性質を利用して、毎回割っています。
2008/03/19 - 末尾再帰的に書くとこうだ!
コード:
let Combination (n, m) =
  let rec CC (i, j, res) =
    if j > m then
      res
    else
      CC ((i + 1), (j + 1), (i * res / j))
  in
  CC ((n - m + 1), 1, 1);;
まちがいない!  

Labels:

2008/03/17

hubFS

F# の活発なコミュニティ たぶん、ここで質問すればほとんどの答えを得られるんじゃないかな  

Labels:

[F#] Unicode文字を関数名や引数に使用可能!

C# と同じようにUnicode文字を関数名や引数に使えるみたい コード:
let ほげほげ あふん = printfn "%s" あふん

ほげほげ "表示"
結果:
val ほげほげ : string -> unit

表示
日本語で書けると便利なときもあるよね  

Labels:

[F#] ネストしたリストをフラットにする

ネストしたリストをフラットにするコード
type 'a tree =
   | E of 'a   // Element
   | N of 'a tree list // Node

let rec flattenTree (t:'a tree) : 'a list =
   match t with
   | E e -> [e]
   | N n -> List.flatten(List.map flattenTree n)
使用例:
let t1 = N [ E 1; N [E 2; E 3]; E 4 ]
flattenTree t1
flattenTree (N [ E "a"; N [ E "b"; E "c"]; E "d" ] )
結果:
> val it : int tree = N [E 1; N [E 2; E 3]; E 4]
> val it : int list = [1; 2; 3; 4]
> val it : string list = ["a"; "b"; "c"; "d"]
ふむふむ・・・なんとなくおk!  

Labels:

2008/03/12

#light は要注意

いろんなサンプルに「#light」って記述されているけど、これ、かなりのクセモノかも。 「#light」ありの場合は、変数の同時束縛ができないみたい
let x = 1 and y = 2   // Syntax error
「#light」なしの場合は、普通にOK
let x = 1 and y = 2
"in" とか書かなくていいからよさげ?と思っていたのも束の間 見事にはまりました 玄人から言わせれば 「同時束縛なんぞ 使う機会ない」 ってことかな? それか、別の記述方法があるのか… ナゾナゾ
2008/03/13 - #light 慣れれば素敵かも?
別の記述方法があった!
let x = expr
and y = expr
expr
こんな感じに let と並べないとダメなんだって。
let x = 1
and y = 2
x + y
とすると、#light なしでこう書けるものは
let gg =
  let x = 2 in 
  let y = 3 in
  (let y = x and x = y + 2 in x * y) + y
#light ありだとこんな感じ?
#light

let gg = 
  let x = 2  
  let y = 3 
  (let y = x
   and x = y + 2 
   x * y) + y
同時束縛のときは”並べないとダメ!”とか決められると誰が書いてもこの書式になるわけで ソースを見るときは楽かもしれない! 他にも結構決められてるみたいだから、「書き方おかしいぜ」って言われたらマニュアル要チェックや  

Labels:

2008/03/11

F# は UTF-8 推奨だった

どうやら、F#は UTF-8 推奨みたい ソースファイルの文字コードを UTF-8 にしてコンパイルすると、Shift-jis では文字化けしていた文字列が普通に表示される。
○ let str = "表示する"
だけど、ソースファイル内での以下の表現は正しく処理されない
× let strb = "表示する"B   // この時点で正常なバイトデータが得られない
× 'あ'   // syntax error
とはいえ、次の表現は正しく処理される
○ let utf_strb = Encoding.UTF8.GetBytes(str)     // 正常なUTF-8のバイトデータが得られる
○ printf "%c" str.[1]   // 「示」が表示される
F# 内部では System.Char を使っているようなので、Unicode系は得意中の得意 そのため、上の printf ではちゃんと表示されるものと思われる(実データは3バイトもあるからね) 正しく処理されない表現は、F# のソースから System.Char に渡すまでのどこかで拒否られているっぽい 直せるものなら直してほしいけど、ダメならダメで特に困ることはないかも?w ささ、ソースファイルは UTF-8 で保存してコンパイルしよう! VS2008 の場合は、標準が Shift-jis なので注意ダ  

Labels: