I have recently come across the Reflection module (in the standard library) below:
https://chapel-lang.org/docs/modules/standard/Reflection.html
I imagined that this module may be useful for creating a generic I/O library that reads in an input file (e.g. TOML) and automatically fills all the fields of class/record variables by scanning the field names and matching them with the contents of the input file. However, the Reflection module seems to provide only getField()
, which returns field values as rvalue. So currently, is it not possible to do something like setField()
to set field values via its name or index? FYI, my interest is whether something like below could be achieved via the Reflection module:
In the case of 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)
and in the case of Rust : https://docs.rs/toml/latest/toml/
A similar example may be the "namelist" feature in Fortran, which can read in a user-defined type variable directly from an input file, for example, as follows:
!! 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
I'll get to your question about Reflection in a moment, but I first wanted to bring your attention to the Serializer and Deserializer features provided by the IO module.
Here is a basic overview: https://chapel-lang.org/docs/modules/standard/IO.html#the-serialize-and-deserialize-methods
And here's a link to more in-depth documentation, suitable for writing your own [De]Serializer or adding custom [de]serialization to a type: https://chapel-lang.org/docs/technotes/ioSerializers.html
Here's an example using the JSON module:
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);
}
This program prints:
Wrote: {"x":5, "y":10}
Read: (x = 5, y = 10)
The Reflection module supports getFieldRef
(see https://chapel-lang.org/docs/main/modules/standard/Reflection.html#Reflection.getFieldRef), which returns a mutable reference. This allows you to assign values to the field. Do also note that getFieldRef
is currently unstable in the 2.0 release (most recent at the time of this comment).
The following is an example program demonstrating how you could use getFieldRef
to programmatically read generic types, in the event you or others find [De]Serializers to be insufficient.
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);
}
This program prints:
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")