我最近遇到了下面的 Reflection 模块(在标准库中):
https://chapel-lang.org/docs/modules/standard/Reflection.html
我想这个模块对于创建一个通用 I/O 库可能很有用,该库读取输入文件(例如 TOML),并通过扫描字段名称并将其与以下内容匹配来自动填充类/记录变量的所有字段输入文件。然而,Reflection 模块似乎只提供
getField()
,它将字段值作为右值返回。那么目前,是否无法执行类似 setField()
的操作来通过其名称或索引设置字段值?仅供参考,我感兴趣的是是否可以通过反射模块实现如下所示的功能:
对于 Swift:https://github.com/LebJe/TOMLKit
let toml = """
string = "Hello, World!"
int = 523053
double = 3250.34
"""
struct Test: Codable {
let string: String
let int: Int
let double: Double
}
...
let test = Test(string: "Goodbye, World!", int: 24598, double: 18.247)
let encodedTest: TOMLTable = try TOMLEncoder().encode(test)
let decodedTest = try TOMLDecoder().decode(Test.self, from: encodedTest)
print("Encoded Test: \n\(encodedTest)\n")
print("Decoded Test: \(decodedTest)")
--> Result:
Encoded Test:
double = 18.247
int = 24598
string = 'Goodbye, World!'
Decoded Test: Test(string: "Goodbye, World!", int: 24598, double: 18.247)
对于 Rust 来说:https://docs.rs/toml/latest/toml/
类似的例子可能是 Fortran 中的“namelist”功能,它可以直接从输入文件中读入用户定义的类型变量,例如如下:
!! test.f90
program main
implicit none
type Foo
integer :: num
real :: val
endtype
type(Foo) :: f
namelist /myinp/ f
open( 10, file="test.inp", status="old" )
read( 10, nml=myinp )
close( 10 )
print *, "f % num = ", f % num
print *, "f % val = ", f % val
end
!! test.inp
&myinp
f%num = 100
f%val = 1.23
/
$ gfortran test.f90 && ./a.out
f % num = 100
f % val = 1.23000002
我稍后会回答您有关反射的问题,但我首先想让您注意 IO 模块提供的序列化器和反序列化器功能。
以下是基本概述:https://chapel-lang.org/docs/modules/standard/IO.html#the-serialize-and-deserialize-methods
这里有一个更深入文档的链接,适合编写您自己的[反]序列化器或向类型添加自定义[反]序列化:https://chapel-lang.org/docs/technotes/ioSerializers.html
这是使用 JSON mdodule 的示例:
use IO, JSON;
record Point {
var x : int;
var y : int;
}
proc main() {
var temp = openTempFile();
var orig = new Point(5, 10);
// Write 'orig' in JSON to our in-memory file
{
var wr = temp.writer(locking=false, serializer=new jsonSerializer());
wr.write(orig);
}
// Read the temp file's raw data to prove it wrote JSON
{
const data = temp.reader(locking=false).readAll(string);
writeln("Wrote: ", data);
}
// Create a 'fileReader' configured to read JSON, and read the Point back in
var rd = temp.reader(locking=false, deserializer=new jsonDeserializer());
var pt = rd.read(Point);
writeln("Read: ", pt);
}
该程序打印:
Wrote: {"x":5, "y":10}
Read: (x = 5, y = 10)
Reflection 模块支持
getFieldRef
(请参阅 https://chapel-lang.org/docs/main/modules/standard/Reflection.html#Reflection.getFieldRef),它返回可变引用。这允许您为该字段分配值。另请注意,getFieldRef
目前在 2.0 版本中不稳定(在发表此评论时是最新版本)。
以下是一个示例程序,演示了如何使用
getFieldRef
以编程方式读取泛型类型,以防您或其他人发现 [De]Serializers 不够。
use IO, Reflection;
// for 'isParam', 'isType'
use Types;
record A {
var x : int;
var y : string;
}
record B {
var i : int;
var j : int;
var k : int;
}
record C {
type T;
var x : T;
}
proc readGeneric(reader : fileReader(?), ref arg: ?) {
param numFields = getNumFields(arg.type);
for param i in 0..<numFields {
// 'type' and 'param' fields must be known at compilation time
if !(isParam(getField(arg, i)) || isType(getField(arg, i))) {
param name : string = getFieldName(arg.type, i);
ref fieldRef = getFieldRef(arg, i);
// %? = read according to type of 'fieldRef'
reader.readf(name + ": %?\n", fieldRef);
}
}
}
proc test(type T, data: string) {
var temp = openTempFile();
{
// In brackets to force temporary writer to flush at end of scope
temp.writer(locking=false).write(data);
}
var r = temp.reader(locking=false);
var val : T;
readGeneric(r, val);
writeln("Read type '" + T:string + "': ", val);
}
proc main() {
const AData =
'''x: 5
y: "hello"''';
test(A, AData);
const BData =
'''i: 1
j: 2
k: 3''';
test(B, BData);
// Examples of generic types
const CData_int = '''x: 5''';
test(C(int), CData_int);
const CData_string = '''x: "five"''';
test(C(string), CData_string);
}
该程序打印:
Read type 'A': (x = 5, y = "hello")
Read type 'B': (i = 1, j = 2, k = 3)
Read type 'C(int(64))': (x = 5)
Read type 'C(string)': (x = "five")