好的,这里有个奇怪的问题。我正在使用FSharp.Data.SqlClient
从我们的数据库中获取记录。它推断的记录有几个字段,它们是选项类型。我需要过滤掉任何选项类型为None的记录,并创建已知字段的新记录。以下是我所谈论的一个例子。为了解决这个问题,我创建了一个过滤函数recordFilter
,它在所有类型的Option<'T>
包含值时返回我想要的类型,而在None
不包含Option<'T>
时返回Option<'T>
。
我的问题是,是否可以创建一个仅自动检查记录中的所有match...with
字段以获取值的函数。我猜这需要某种反射来迭代记录的字段。我猜这是不可能的,但我想把它扔出去,万一我错了。
如果这种方法是惯用的方式,那么我很乐意听到这一点。我只是想确保我没有错过一些更优雅的解决方案。 F#的可能性让我感到惊讶。
我的动机是我正在处理包含type OptionRecord = {
Id: int
Attr1: int option
Attr2: int option
Attr3: int option
Attr4: int option
Attr5: int option
Attr6: int option
}
type FilteredRecord = {
Id: int
Attr1: int
Attr2: int
Attr3: int
Attr4: int
Attr5: int
Attr6: int
}
let optionRecords = [for i in 1..5 ->
{
OptionRecord.Id = i
Attr1 = Some i
Attr2 =
match i % 2 = 0 with
| true -> Some i
| false -> None
Attr3 = Some i
Attr4 = Some i
Attr5 = Some i
Attr6 = Some i
}]
let recordFilter (x:OptionRecord) =
match x.Attr1, x.Attr2, x.Attr3, x.Attr4, x.Attr5, x.Attr6 with
| Some attr1, Some attr2, Some attr3, Some attr4, Some attr5, Some attr6 ->
Some {
FilteredRecord.Id = x.Id
Attr1 = attr1
Attr2 = attr2
Attr3 = attr3
Attr4 = attr4
Attr5 = attr5
Attr6 = attr6
}
| _, _, _, _, _, _ -> None
let filteredRecords =
optionRecords
|> List.choose recordFilter
类型的数十个字段的记录。像我在这个例子中那样写出一个巨大的//Convert bitmap to byte array
Bitmap bitmap = mResultsBitmap;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
byte[] bitmapdata = bos.toByteArray();
//write the bytes in file
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
fos.write(bitmapdata);
} catch (IOException e) {
e.printStackTrace();
}
try {
fos.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
final File pFIle2 = f;
//TODO: clarifai stuff
//TODO: clarifai stuff
Log.e("this:"," this is running 0");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Log.e("this:", " this is running 1");
client = new ClarifaiBuilder("mykeyhere1234}").buildSync();
Log.e("this:", " this is running 2");
Thread th = new Thread(new Runnable() {
@Override
public void run() {
Log.e("this:", " this is running 3");
Log.e("this", client.getDefaultModels().generalModel().predict()
.withInputs(
ClarifaiInput.forImage(ClarifaiImage.of(pFIle2))
)
.executeSync().rawBody());
Log.e("this:", " this is running 4");
}
});
}
});
语句是很烦人的。当它只有几个字段是好的,当它是30多个字段时,它很烦人。
{{1}}
答案 0 :(得分:4)
这确实可以用反射来完成。命名空间FSharp.Reflection
包含一些方便的帮助程序,专门用于F#类型,而不是一般的.NET。需要考虑的关键点是:
FSharpType.GetRecordFields
返回每个记录字段的PropertyInfo
个对象列表。option
进行比较来判断该属性是否为typedefof<option>
。None
在运行时表示为null
。FSharpValue.GetUnionFields
和FSharpValue.GetRecordFields
分别返回联合或记录字段值的列表。FSharpValue.MakeRecord
根据字段值列表创建新记录。以下是代码:
open FSharp.Reflection
/// Record with Option-typed fields
type RM = { a: int option; b: string option; c: bool option }
/// Record with same fields, but non-optional
type R = { a: int; b: string; c: bool }
/// Determines if the given property is of type option<_>
let isOption (f: System.Reflection.PropertyInfo) =
f.PropertyType.IsGenericType && f.PropertyType.GetGenericTypeDefinition() = typedefof<option<_>>
/// Returns an array of pairs (propertyInfo, value) for every field of the given record.
let fieldsWithValues (r: 'a) =
Array.zip (FSharpType.GetRecordFields typeof<'a>) (FSharpValue.GetRecordFields r)
/// Determines if the given record has any option-type fields whose value is None.
let anyNones (r: 'a) =
fieldsWithValues r |> Seq.exists (fun (f, value) -> isOption f && isNull value)
/// Given two records, 'a and 'b, where 'a is expected to contain some option-typed
/// fields, and 'b is expected to contain their non-option namesakes, creates a new
/// record 'b with all non-None option values copied from 'a.
let copyOptionFields (from: 'a) (to': 'b) : 'b =
let bFields = FSharpValue.GetRecordFields to'
let aFields = Array.zip (FSharpType.GetRecordFields typeof<'a>) (FSharpValue.GetRecordFields from)
for idx, (f, value) in aFields |> Array.indexed do
if isOption f && not (isNull value) then
let _, values = FSharpValue.GetUnionFields( value, f.PropertyType )
bFields.[idx] <- values.[0] // We know that this is a `Some` case, and it has only one value
FSharpValue.MakeRecord( typeof<'b>, bFields ) :?> 'b
用法:
> anyNones {RM.a = Some 42; b = Some "abc"; c = Some true}
val it : bool = false
> anyNones {RM.a = Some 42; b = Some "abc"; c = None}
val it : bool = true
> let emptyR = {R.a = 0; b = ""; c = false}
> copyOptionFields {RM.a = Some 42; b = Some "abc"; c = Some true} emptyR
val it : R = {a = 42; b = "abc"; c = true;}
> copyOptionFields {RM.a = None; b = Some "abc"; c = None} emptyR
val it : R = {a = 0; b = "abc"; c = false;}
注意:上面的代码不执行任何健全性检查(例如,'a
和'b
确实是记录,或者他们的字段确实是同名的并且在同一个订单等)。我将此作为练习留给读者: - )
注意2 :小心表现。由于这是反射,因此速度较慢,无法在编译时进行优化。