スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#入門第36回 OOP(1)暗黙的クラス定義(1)

今回のお題は「OOP(1)暗黙的クラス定義(1)」です。
 
F#におけるクラスの定義方法は「明示的クラス定義」と「暗黙的クラス定義」の2種類あります。明示的クラス定義はc#やjava likeな定義方法で、暗黙的クラス定義はF#らしく、クラスを簡潔に定義できる方法です。
ここでは、まず暗黙的クラス定義に挑戦してみたいと思います。
クラスというのは、非常に大雑把にいえば、値と、その値に対して働く関数の寄せ集めです。例えばVec2Dというクラス名で2次元ベクトルを表すクラスを考えたいと思います。
クラスを定義するには、
type クラス名 初期化で使いたい変数のタプル =
    内容
という形になります。
例えば次のように2次元ベクトルのクラスを定義してみます。
type Vec2D (x : float,y:float) =
    member this.ShowContents
                = printfn "x = %f y = %f" x y
 
仮引数x,yのスコープが、内容全体になることに留意しておいてください。
 
このクラス定義でF#Interactiveでためしてみると、次のようになります。
type Vec2D =
  class
    new : x:float * y:float -> Vec2D
    member ShowContents : unit
  end
見ての通り、コンストラクタ(newキーワードと「保持する値の材料となる値のタプル」をつかってインスタンスを生成する関数で、引数は上の「保持する値の材料となる値のタプル」)が自動的に定義されます。
この自動的に定義されるコンストラクタをメインコンストラクタと呼びます。
 
また、class end というキーワードが増えてますが、自分で定義するときでも、このようにclass とendを追加してもよいのですが、ここでは、種推論機能にまかせて、省略しています。(種推論機能については、そのうち取り上げます。)
 
では実際にインスタンス化して、プロパティであるShowContentsを実行してみます。 
 
> let t = new Vec2D(2.0,3.0);;
val t : Vec2D
 
> t.ShowContents ;;
x = 2.000000 y = 3.000000
val it : unit = ()
 
さて次は、このベクトルのx成分を返すプロパティ、及びy成分を返すプロパティ、及び長さを返すプロパティを追加してみます。
 
type Vec2D (x : float,y:float) =
 
    member this.ShowContents
                = printfn "x = %f y = %f" x y
    member this.X = x
    member this.Y = y
    member this.Length =
            let sqr r = r * r
            sqrt <| sqr x + sqr y
 
F#Interactiveでの実行例
type Vec2D =
  class
    new : x:float * y:float -> Vec2D
    member X : float
    member Y : float
    member Length : float
    member ShowContents : unit
  end
  
> let t = new Vec2D(2.0,3.0);;
val t : Vec2D
 
> t.X;;
val it : float = 2.0
 
> t.Length;;
val it : float = 3.605551275  
 
this.Length の定義を少し変えてみたいと思います。
まず、最初に長さをlenという名前でlet束縛しておいて、これをthis.Lengthの定義で利用します。
次のようになります。
 
type Vec2D (x : float,y:float) =
 
    let len = 
        let  sqr r = r * r
        sqrt <| sqr x + sqr y
    
    member this.ShowContents
                = printfn "x = %f y = %f" x y
    member this.X = x
    member this.Y = y
    member this.Length = len
 
実行結果は上と同じになります。
 
実はこの、let len = ..が、書かれているブロックは非常に大切な部分で、F#はこの部分と、コンストラクタの引数となる「保持する値の材料となる値のタプル(この場合は(x : float,y:float))」を解析して、自動的に生成されるメインコンストラクタを定義したり、オブジェクト内に保持する値等を決定します。よって、この部分に、後に定義されるプロパティやメソッドに必要となる値(オブジェクト内に保持したい値等を含む)をlet束縛して定義しておきます。
 
また、doキーワードに続けてunit型を返す式を書くことができます。
例えば do printfn "初期化中"とかdo z <- z + 1 とか書くことができます。
 

 
type Vec2D (x : float,y:float) =
 
    do printfn "初期化を始めます"
    let len = 
            let  sqr r = r * r
            sqrt <| sqr x + sqr y
    let mutable q = x + y //意味もなくqを定義しているだけです
    do q <- q + 1.0 //意味もなくqの値を1.0増やしているだけです
    do printfn "初期化を終わります"
    
    member this.ShowContents
                = printfn "x = %f y = %f" x y 
                  
    member this.X = x
    member this.Y = y
    member this.Q = q
    member this.Length = len
  
実行例
 
> let t = new Vec2D(2.0,3.0);;
初期化を始めます
初期化を終わります
val t : Vec2D
 
> t.Q;;
val it : float = 6.0
 
では、次にVec2Dを引数にとり、自分自身との和を返すメソッドを追加してみます。
説明のために上で付け加えたq関連とdo printfn部分は取り除いてあります。
 
type Vec2D (x : float,y:float) =
 
    let len = 
        let  sqr r = r * r
        sqrt <| sqr x + sqr y
    
    member this.ShowContents
                = printfn "x = %f y = %f" x y
    member this.X = x
    member this.Y = y
    member this.Length = len
    member this.Add (t :Vec2D) =
            new Vec2D(x + t.X,y + t.Y)
            
F#Interactiveでの実行結果
 
type Vec2D =
  class
    new : x:float * y:float -> Vec2D
    member Add : t:Vec2D -> Vec2D
    member X : float
    member Y : float
    member Length : float
    member ShowContents : unit
  end
 
実行例
 
> let t = new Vec2D(2.0,3.0);;
val t : Vec2D
 
> let u = t.Add(new Vec2D(1.0,5.0));;
val u : Vec2D
 
> u.ShowContents;;
x = 3.000000 y = 8.000000
val it : unit = ()
 
それでは、ここで少しまとめてみます。
暗黙的にクラス定義するには、
type クラス名 仮引数(=初期化に使用する値のタプル) =
    初期化部分
    プロパティ、メソッドの定義
の形を用いる。
プロパティ、メソッドの定義で使える材料は、「初期化に使用する値のタプル」と、初期化部分でlet束縛して、定義した値である。
上の形で定義すると、F#が初期化部分を解析して、自動的にメインコンストラクタ(newをつかってインスタンスを生成する関数で、引数は上の「初期化に使用する値のタプル」)を定義してくれる。また同時に、インスタンス毎に保持するインスタンスフィールドも、自動的に決定される。
 
この初期化部分の一つ一つの式をPrimary Constructorと言います。
(全体でPrimary Constructorsです。)
Primary Constructors内の値には、オブジェクトが生成された後は、外部からは参照できません。(Primary Constructors内のlet束縛された値にアクセスできないということです。これらの値にアクセスするためには、プロパティやメソッドを経由する必要があります)
Primary Constructors内で、let束縛された値(関数以外)は、クラスのインスタンスフィールドになる可能性大ですが、そうなる絶対的保証はありません。
ということで、c#やjavaのクラス定義では、インスタンスフィールドを、まず考えるという面があるのですが、このF#の暗黙的クラス定義では、プロパティ、メソッドとして、何が必要かということが第一義で、インスタンスフィールドについては、「コンパイラにおまかせしておく」という感じです。(明示的にクラスのインスタンスフィールドを定義したい場合は、通常明示的クラス定義を使います。)
 
さて、それでは宿題です。上のクラスに,ベクトルの成分がともに0.0であるときtrueを返し、それ以外のときfalseを返すプロパティIsZeroと、float*float型の引数をとり、それぞれをx成分、y成分に足した、Vec2D型の値を返す、メソッドAdd2を追加してください。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

暗黙クラス定義で明示的フィールド定義

>明示的にクラスのインスタンスフィールドを定義したい場合は、明示的クラス定義を使う必要があります
試しに書いて見たらできました。

type Point(x:int, y:int) as p =
[<DefaultValue>] val mutable _x : int
[<DefaultValue>] val mutable _y : int
do p._x <- x; p._y <- y
;;

No title

ありゃーPrimary Constructorsでvalキーワードが使えたりするのですね。
ということで、ブログの該当部分を「明示的にクラスのインスタンスフィールドを定義したい場合は、通常明示的クラス定義を使います。」という表現に改めました。どーもありがとうございます。
プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。