F# メモ XNA Framework

F#でも XNA が使用できます。
ただし .NET Framework 4.0 ではビルドできませんでした。3.5 なら問題なくビルドできます。
おそらく、 XNA Game Studio 4.0 がリリースされれば .NET Framework 4.0 でもビルドできるようになると思われます。

BasicComponents.fs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
namespace Siki

open System
open System.Collections.Generic
open Microsoft.Xna.Framework

module BasicComponents =
   
    /// 秒を表す単位
    [<Measure>]
    type sec =
        static member Unit v = v * 1.0<sec>
        static member Unit v = v * 1<sec>
           
    /// フレームを表す単位
    [<Measure>]
    type frame =
        /// 1フレームを表す
        static member Unit v = v * 1.0<frame>
        static member Unit v = v * 1<frame>

    /// 1秒あたりの描画フレーム数を計算するコンポーネント
    type FpsComponent(game, syncv, fixedStep, targetFps) =
        inherit DrawableGameComponent(game)
        // フィールド
        let         m_interval   = 1.0<sec>
        let mutable m_fps        = 0.0<frame/sec>
        let mutable m_sec        = 0.0<sec>
        let mutable m_framecount = 0.0<frame>
        let mutable m_lastupdate = 0.0<sec>

        // メインコンストラクタ
        do
            let graphics =
                game.Services.GetService(typeof<IGraphicsDeviceManager>)
                :?> GraphicsDeviceManager
           
            graphics.SynchronizeWithVerticalRetrace <- syncv
            game.IsFixedTimeStep <- fixedStep
            game.TargetElapsedTime <- TimeSpan.FromSeconds(1.0 / targetFps)

        /// カスタムコンストラクタ
        new(game) =
            new FpsComponent(game, false, false, 1.0 / game.TargetElapsedTime.TotalSeconds)

        new(game, syncv) =
            new FpsComponent(game, syncv, false,  1.0 / game.TargetElapsedTime.TotalSeconds)

        new(game, targetFps) =
            new FpsComponent(game, true, true, targetFps)
       
        /// 現在のFPS値を取得する
        member x.Fps
            with get() = m_fps

        /// 現在のFPSを整数に丸めた値を取得する
        member x.RoundFps
            with get() = x.Fps |> float |> Math.Round |> int

        /// 初期化メソッド
        override x.Initialize() =
            base.Initialize()

        /// 更新メソッド
        override x.Update(gametime) =
            base.Update(gametime)

        /// 描画メソッド
        override x.Draw(gametime) =
            let elapsed = gametime.ElapsedGameTime.TotalSeconds |> sec.Unit
            m_framecount <- m_framecount + 1.0<frame>
            m_sec <- m_sec + elapsed
            if m_sec > m_interval then
                m_fps <- m_framecount / m_sec
                x.Game.Window.Title <- sprintf "%d fps" x.RoundFps
                m_framecount <- 0.0<frame> // カウンタリセット
                m_sec <- m_sec - m_interval
            base.Draw(gametime)

Program.fs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
namespace Siki

open System
open System.Collections.Generic
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics

open Siki.BasicComponents

module Main =
    /// ゲームクラス
    type Game1() as x =
        inherit Game(IsMouseVisible=true,
                     IsFixedTimeStep=false)
       
        // フィールド
        let m_rand = new Random()
        let m_graphics = new GraphicsDeviceManager(x)
        let m_fpscomponent = new FpsComponent(x)
        let mutable m_num = 0
        let mutable m_bgc = new Color(80uy, 80uy, 80uy)
        let mutable m_rd = -1
        let mutable m_gd = -1
        let mutable m_bd = -1
        // メインコンストラクタ
        do
            x.Window.Title <- "FPSを表示します"
        /// 初期化メソッド
        override x.Initialize() =
            x.Components.Add(m_fpscomponent)
            base.Initialize()

        /// 更新メソッド
        override x.Update gametime =
            m_num <- m_num + 1
            base.Update gametime

        /// 描画メソッド
        override x.Draw gametime =
            match m_rand.Next(0, 5) with
            | 0 ->
                m_bgc.R <- m_bgc.R + (m_rd |> byte)
                if (m_bgc.R = 0x00uy || m_bgc.R = 0xffuy) then m_rd <- m_rd * -1
            | 1|2 ->
                m_bgc.G <- m_bgc.G + (m_gd |> byte)
                if (m_bgc.G = 0x00uy || m_bgc.G = 0xffuy) then m_gd <- m_gd * -1
            | 3|4|5 ->
                m_bgc.B <- m_bgc.B + (m_bd |> byte)
                if (m_bgc.B = 0x00uy || m_bgc.B = 0xffuy) then m_bd <- m_bd * -1
            | _ -> ()
            m_graphics.GraphicsDevice.Clear(
                m_bgc
            )
            base.Draw gametime
   
    /// プログラムのエントリポイント
    [<EntryPoint>]
    let main(args:string[]) =
        use game = new Game1()
        game.Run()
        0

FPSを計算するだけの簡単なサンプルです。
Update()、Draw()が呼ばれていることを確認するために、少しずつ背景色を変えています。

F# メモ Option

Option型

C#でいうNullable型のようなもので、値を持たないことも表せるようになる。

引数 x が自然数なら x を、負の数なら None (値を持たない) を返す。

> let natural x =
    if x >= 0 then Some x
    else None;;

val natural : int -> int option

Option.get
‘a option -> ‘a

Option型 は Some (値を持っている) か None (値を持たない) のいずれかを表し、
Some の場合は値を取得できる。
None に対し Option.get を行うと例外が発生する。

> let a = natural 100
let b = natural -100
let c = Option.get a;;

val a : int option = Some 100
val b : int option = None
val c : int = 100

Option.isSome
‘a option -> bool

引数が Some なら true を、None なら false を返す。


Option.isNone
‘a option -> bool

引数が Some なら true を、None なら false を返す。

F# メモ List (2)

リスト関数

関数
関数の型
簡単な説明
List.length
‘a list -> int
要素の数を取得する
List.head
‘a list -> ‘a
先頭の要素を取得する
List.tail
‘a list -> ‘a list
先頭の要素を除いたリストを取得する
List.exists
(‘a -> bool) -> ‘a list -> bool
条件に合う要素が存在するか調べる
List.rev
‘a list -> ‘a list
順番を逆にしたリストを取得する
List.tryFind
(‘a -> bool) -> ‘a list -> ‘a option
条件に合う要素を取得する
存在しない場合はNoneを返す
List.zip
‘a list -> ‘b list -> (‘a * ‘b) list
2つのリストの要素を組み合わせ、
タプルのリストを作成する
List.filter
(‘a -> bool) ‘a list -> ‘a list
条件に合う要素のみを含むリストを作成する
List.partition
(‘a -> bool) -> ‘a list -> (‘a list * ‘a list)
条件に合う要素のみを含むリストと
それ以外のリストのタプルを作成する

他にも多数あります。msdn


集約関数 (Aggregate Operators)

リストなどのコレクションには個々の要素に対して何らかの操作を行う関数が多数用意されている。

List.map
(‘a -> ‘b) -> ‘a list -> ‘b list

‘a -> ‘b 変換関数をすべての要素に適用し、’b list を作成する。

例:整数のリストを基に、文字列のリストを作成する。

> let tostr x = sprintf "%d" x
let a = [1..10]
let b = List.map tostr a;;

val tostr : int -> string
val a : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
val b : string list = ["1"; "2"; "3"; "4"; "5"; "6"; "7"; "8"; "9"; "10"]

List.reduce
(‘a -> ‘a -> ‘a) ->’a list -> ‘a

リストの要素を辿りながら ‘a 型のアキュムレータを構築していく関数。
(‘a -> ‘a -> ‘a) の部分は現在のアキュムレータとリストの要素を引数とし、新たなアキュムレータを作成する関数。

例:1から5までの整数の積を求める。(ついでに、アキュムレータの確認を行う)

> let multiple a b =
    printfn "// %3d = %2d * %d" (a*b) a b
    a * b
let b = List.reduce multiple [1..5];;
//   2 =  1 * 2
//   6 =  2 * 3
//  24 =  6 * 4
// 120 = 24 * 5

val multiple : int -> int -> int
val b : int = 120

上の例のように、リストの先頭要素がアキュムレータの初期値となる。


List.fold
(‘acc -> ‘b -> ‘acc) -> ‘acc -> ‘b list -> ‘acc

リストの要素を辿りながら任意の型のアキュムレータを構築していく関数。
List.reduceを汎用的にしたもの。
アキュムレータの初期値を与える必要がある。

例:リスト中の文字数の合計を取得する

> let Count acc (str:string) = acc + str.Length
let dow = [
    "Sunday";
    "Monday";
    "Tuesday";
    "Wednesday"
]
let total = List.fold Count 0 dow;;

val Count : int -> string -> int
val dow : string list = ["Sunday"; "Monday"; "Tuesday"; "Wednesday"]
val total : int = 28

List.iter
(‘a -> unit) -> ‘a list -> unit

指定された関数を、リストの各要素に対して順次実行する関数。

F# メモ List (1)

基本


リストの作成。
[] で囲み、要素は ; で区切る。

> let odds = [1;3;5;7;9]
let evens = [2;4;6;8;10];;

val odds : int list = [1; 3; 5; 7; 9]
val evens : int list = [2; 4; 6; 8; 10]

リストの追加。
@ はリストの結合を行う。

> let appended = odds @ evens;;

val appended : int list = [1; 3; 5; 7; 9; 2; 4; 6; 8; 10]

要素の追加。
:: はリストの先頭に要素を追加する。

> 0 :: appended;;
val it : int list = [0; 1; 3; 5; 7; 9; 2; 4; 6; 8; 10]

範囲を指定する。
1から10までの整数のリストを作成する。

> let x = [1..10];;

val x : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

範囲と間隔を指定する。
1から10までの奇数のリストを作成する。

> let x = [1..2..10];;

val x : int list = [1; 3; 5; 7; 9]

要素を持たないリスト。

> let emptylist = [];;

val emptylist : 'a list

yield

リストの要素を何らかの手続きで決定させることができる。

> let near x =
    [
        yield x - 1
        yield x
        yield x + 1
    ]
let nearTen = near 10;;

val near : int -> int list
val nearTen : int list = [9; 10; 11]

引数の値とその前後の値を要素とするリストを返す関数 near を作成し、10を与えている。
上から順に評価されるので、10-1=9、10、10+1=11が要素となる。

[] の中には条件式やループ等も使用できる。
30以下の素数のリストを作成する例。

> let primes =
    [
        let factors n =
            [
                for i in 2..n-1 do
                    if n % i = 0 then
                        yield i
            ]
               
        for i in 2..30 do
            if List.length (factors i) = 0 then
                yield i
    ];;

val primes : int list = [2; 3; 5; 7; 11; 13; 17; 19; 23; 29]

F# メモ コメント

行コメント

C++と同じで // から行末までがコメントとして扱われる。


ブロックコメント

(* と *) で囲まれた部分がコメントとなる。

このブロックコメントはネストすることができるので、

1
2
3
4
5
6
(*
    outer comment
    (*
        inner comment
    *)
*)

は正しい。しかし、

1
2
3
4
5
6
(*
    outer comment
    (*
        inner comment
   
*)

はコンパイルエラーとなる。

F# メモ タプル

タプルは複数の値を一つの組として扱う機能。
int と string のタプルの型は int * string と表される。
コンマで区切ることでタプルの作成や要素の取得ができる。

> let t = (1234, "1234");;

val t : int * string = (1234, "1234")

> let d, s = t;;

val s : string = "1234"
val d : int = 1234

タプルを関数の引数にするには以下のように括弧で括る必要がある。

> let sum (a, b, c) = a + b + c

let x = sum (12, 23, 34);;

val sum : int * int * int -> int
val x : int = 69
TOP

INFORMATION

未来の自分のためのメモ
管理者:rei