2017-04-24 3 views
10

型を定義し、その型で動作する関数(インスタンスメソッドではなく)を記述する機能的アプローチをとった場合、コードをどのように整理すればよいですか?F#:型とモジュールを構成するためのベストプラクティス

私は、一般的に、3つの方法のいずれかを行く:

(1):

module MyType = 
    type MyType (x : int) = 
     member val X = x with get 

    let myFunction myType y = myType.X + y 

(2):

type MyType (x : int) = 
    member val X = x with get 

[<RequireQualifiedAccess>] 
module MyTypeOps = 
    let myFunction myType y = myType.X + y 

(3):

type MyType = 
    { 
     x : int 
    } 

    static member MyFunction myType y = myType.x + y 

プロof(1)は、関数がモジュール内で定義されていることです。 (1)の短所は、タイプがモジュールでも定義されているため、open MyTypeをその冗長性の回避策として許可したい場合は、MyType.MyTypeのインスタンス化時の冗長性との冗長性が得られません。

(2)の長所は、モジュールで定義された関数であり、モジュールタイプの冗長性はありません。 Conは、モジュールがその型と同じ名前になることはできません。

Pro of(3)は、メソッドが静的で、モジュールタイプの冗長性がないということです。この方法では、いくつかの型(非メソッド値やアクティブなパターンなど)を定義することはできません。

通常、私は(2)を好んでいますが、モジュールに名前を付けるのは嫌ですが、記述的で直感的ではないものがあります。 (2)は私がそれを行う必要があるまれなケースではtype ... and ...を使用して相互に依存する型を作成することも可能にしますが、アプローチ(1)のような別々のモジュールで定義された型では不可能です。

私の仲間のF#プログラマーはどのようなアプローチを取っていますか?私は何か明白な(またはそれほど明白でない)ものを見落としているのだろうか、もしそうでなければ、モジュールに対応する型と同じ名前を内部に持たせることができないというコンベンションがあるかどうか同じ名前空間。

答えて

9

リストにない4つ目の方法があります。これは、タイプとモジュールの名前を同じにすることです。あなたはそれができないと思っていますが、実際には非常に一般的な方法です。モジュールの[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]属性を使用するだけで(4.1以前のF#バージョンでは)簡単に行うことができます。どこにでも書くのはちょっと醜いので、モジュールと同じ名前の型を定義すると、F# 4.1 made this the default behaviorとなります。どのように処理されたかを確認するには、FSharpx.Collectionsコード(他の多くのプロジェクトの中で)を見てください。あなたのコードは、同じ名前のタイプとモジュール、Fの#で、この方法を整理すると

https://github.com/fsprojects/FSharpx.Collections/blob/master/src/FSharpx.Collections/Queue.fs

namespace FSharpx.Collections 

type Queue<'T> (front : list<'T>, rBack : list<'T>) = 
    // ... 
    member this.Conj x = 
     match front, x::rBack with 
     | [], r -> Queue((List.rev r), []) 
     | f, r -> Queue(f, r) 
    // ... 

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] 
module Queue = 
    // ... 
    let inline conj (x : 'T) (q : Queue<'T>) = (q.Conj x) 
    // ... 

:ここでは良い例になりすぎて恐ろしく大きくない一つのファイルには、ですコンパイラは、PascalCaseスタイルで名前が付けられているが、camelCaseスタイルで名前が付けられたモジュールに関数を持つメンバーメソッドを持つ、標準の命名規則に従えば、事態をまっすぐに保つことができます。 C#コンパイラは混乱しますが、CompilationRepresentationという属性がそれを処理し、他の.Net言語ではモジュール名がQueueModuleであることが保証されます。だから、F#2から:

let q = new Queue([1;2;3], []) 
let moreItems = q |> Queue.conj 4 
let stillMoreItems = moreItems.Conj 5 

のC#から:

Queue<int> q = new Queue<int>({1,2,3}, {}); // Not valid list syntax, but whatever 
Queue<int> moreItems = QueueModule.Conj(4, q); 
Queue<int> stillMoreItems = moreItems.Conj(5); 

Queue.conj機能は、F#から使用することがより自然に見える、とメンバーの方法moreItems.Conjは、C#から使用するより自然に見えますが、両方ともにご利用いただけます両方の言語。

+1

[F#標準ライブラリのコレクションの種類](https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/FSharp.Core/list.fs)も同じモジュール(たとえ型に等価なメソッドを持たなくても)型のパターンとして名前を指定します。 –

+0

興味深い選択は、そのCompilationRepresentation属性を使用しています(これまでに見たことはありませんでした。私はC#の相互運用性が懸念されている(これは私の問題ですが)、私の質問に対する素晴らしい応答であるので、この答えのチェックマークを与えています。これは、リストにある3つの方法のうちの2番目と基本的に同じです。さらに、私はメンバメソッドに機能をコード化する必要はありません。私は対応するモジュールでそれを行うのが好きです。なぜなら、自分の型を単にプロパティの宣言に制限したいからです。 – MiloDC

+0

ちょうど好奇心から、QueueがC#の慣用句を作成したいレコードタイプの場合(私は自分の関数を自分の型から離してしまうので、レコードをたくさん使います)、次に使用するアクティブなパターンをどのように定義しますか?メンバーメソッド? – MiloDC

関連する問題