1

次のコードを想定します。

type Large = { v1: int; v2: int }
type Small = { v1: int }

let fn (value: Small) = printfn "%d" value.v1
// or
let fn (value: {v1: int}) = printfn "%d" value.v1

let large = { v1 = 5; v2 = 6 }
fn large

Smallは基本的に のサブレコードですLargefn largeエラーをスローします。それを機能させる「いい」方法はありますか?

バックグラウンド:

多くの場合、大量のデータがある状況があります。そのデータを、ユーザーからパラメーターとして提供された未知の関数に渡したいとします。あなたが知っている唯一のことは、関数がデータの一部を必要とするということです. しかし、データのどの部分であるかはわかりませんし、Small型についてもわかりません。型しか知らないLargelarge1 つのオプションは、すべてのデータをレコードの形式で未知の関数に送信することです。しかし、関数がデータのごく一部しか使用しない場合、fn関数はデータを受け入れるべきではないと私は信じています。データは、関数の読み取り、理解、および単体テストをより困難にするだけLargeです。私の意見では、関数は必要なものだけを受け入れる必要がありますが、前もってどの部分がLargefnfnLargefn関数が必要とするタイプ。

4

4 に答える 4

1

データが大きいと、 fn 関数の読み取り、理解、単体テストが難しくなります。

問題を正しく理解できるかどうかはわかりません。しかし、主な問題が読みやすさとテスト能力に関するものである場合は、変換関数を使用するだけかもしれません

let forgetful (large : Large) = ({v1=large.v1} : Small)

次に、関数を記述します

let fn (value: Small) = printfn "%d" value.v1

これは読み取り可能/テスト可能であり、規定することにより欲求関数 f を取得します

let f x = fn (forgetful x)

もうテストは必要ありません..

于 2013-04-16T08:03:44.723 に答える
1

以下に基づいています。

F# レコードの奇妙な動作:

F# は構造型付けを使用しないことを理解することが重要です (つまり、フィールドが少ないレコードを受け取る関数の引数として、フィールドが多いレコードを使用することはできません)。これは便利な機能かもしれませんが、.NET 型システムにはうまく適合しません。これは基本的に、あまりにも凝ったものを期待してはならないことを意味します。引数は、よく知られた名前付きレコード タイプのレコードでなければなりません。

ニコラス:

Large fn のどの部分が使用されるかを事前に本当に知らない場合は、すべてを指定する必要があります。これは意味的に正しい唯一の選択です。

私は2つの可能な解決策を思いつきました:

解決策 1

基本的にユーザー関数を 2 つに分割します。良いことは、reducedPostprocessFn必要なものだけを受け入れることです。したがって、この関数は簡単に推論して単体テストを行うことができます。postprocessFnは非常に短いため、何をするかを簡単に確認できます。このソリューションは、によって提示されるアクティブなパターンに似ていると思いますPhillip Trelford。アクティブパターンの利点は何ですか?

(* simulation *)

type Large = {v1: int; v2: int}

let simulation postprocessFn =
  let large = {v1 = 1; v2 = 2}
  postprocessFn large

(* user *)

let reducedPostprocessFn (v1: int) =
  printfn "%d" v1

let postprocessFn (large: Large) =
  reducedPostprocessFn large.v1

simulation postprocessFn

解決策 2

このソリューションではダックタイピングを使用しますが、次のことを行います。

http://msdn.microsoft.com/en-us/library/dd233203.aspx

明示的なメンバー制約: ... 一般的な使用を意図していません。

F# とダックタイピング

これはレコードタイプでは機能しないことを思い出しました。with member .... を使用してメンバーで修正できますが、技術的にはそれらのメンバーはフィールドです。

そこで、レコードの代わりに通常のクラスを使用しました。今では 2 つではなく 1 つの関数しか使用していませんが、正直なところ、F# でのダック タイピングは見苦しいものです。

(* simulation *)

type Large(v1, v2) =
  member o.v1 = v1
  member o.v2 = v2

let simulation postprocessFn =
  let large = Large(1, 2)
  postprocessFn large

(* user 2 *)

let inline postprocessFn small =
  let v = (^a: (member v1: int) small)
  printfn "%d" v

simulation postprocessFn
于 2013-04-16T07:27:49.540 に答える
1

Large fn のどの部分が使用されるかを事前に本当に知らない場合は、すべてを指定する必要があります。これは意味的に正しい唯一の選択です。

fn はその種類の関数のため、fn はそのすべてを必要としないと感じ、そのクラスの関数に付ける名前が欠落しているだけなので、困惑するかもしれません。

このクラスに名前を付けると、Large をアクセスする必要がある部分だけに縮小したり、ジェネリックな部分で fn を分割したり、そのクラスの関数の型に関連付けられた別の部分を分割したりできます。

type Large = { v1: int; v2: int }
   with x.AdaptToPrintable() =  x.v1.ToString()

fn x = printfn "%A" x

PS : 問題がセマンティックにあるのか、それとも値を指定する操作上の側面にあるのか、私は混乱していると思います。後者の場合、フィルの答えが最善のようです..

于 2013-04-15T20:23:24.653 に答える