New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type checked units in zig by run-time operator-overloading and comptime book keeping #3002
Comments
There have been proposals for operator overloading before, so I will set that aside. How exactly do you think the comptime bookkeeping should work? (I'm going to simplify to a much smaller setting than you propose: compile time checking that const std = @import("std");
const assert = std.debug.assert;
const Measure = struct {
amount: f64,
comptime unit: []const u8,
pub fn add(comptime unit: []const u8, a: Measure, b: Measure) Measure {
comptime assert(std.mem.eql(u8, unit, a.unit));
comptime assert(std.mem.eql(u8, unit, b.unit));
return Measure{ .amount = a.amount + b.amount, .unit = unit };
}
};
test "inch + inch" {
var a = Measure{ .amount = 1, .unit = "inch"[0..] };
var b = Measure{ .amount = 2, .unit = "inch"[0..] };
var c = Measure.add("inch", a, b);
} Zig doesn't currently allow you to mark fields comptime; this code marks the But how could this actually be done? If we have a non- What if we want to return a slice In the limit, something like this seems like it should be allowed: fn stripes(comptime units: []const []const u8, n: usize, allocator: *std.mem.Allocator) ![]Measure {
var out = try allocator.alloc(Measure, n);
for (out) |*v, i| {
out[i] = Measure{
.amount = @intToFloat(f64, i),
.unit = units[i % units.len],
};
}
} however the "type" of the result of This proposal is very scant on details, so I can't know if I've taken this in completely the wrong direction. |
const Measure = struct{
value: f64,
exponents: [7]i8, // this "mask" is used to check and update Measure instances before and after operations
//notice it's an array of signed integers, not unsigned.
}; It might indeed not be possible to keep track of an instance field at compile time, a necessity for this proposal. If some form of subtyping was possible, then
This proposal is basically just a spin-off from the discussion in issue #1595, especially regarding a comment on how operations can sometimes yield new units. I hope everything can become more clear with this additional example: Purely runtime version of this type checking concept in a gist with runnable code (zig test). Excerpt: // units are represented by exponents.
// simple units, index corresponds to SI system.
const exponentsMeter= [7]i8{0,1,0,0,0,0,0}; // [m]
const exponentsSecond = [7]i8{1,0,0,0,0,0,0}; // [s]
const exponentsKg = [7]i8{0,0,1,0,0,0,0}; // [kg]
//..etc
// more complex units
const exponentsSpeed = [7]i8{-1,1,0,0,0,0,0}; // [m/s]
const exponentsNewton = [7]i8{-2,1,1,0,0,0,0}; // [kg*m/s^2]
const exponentsWatt = [7]i8{-3,2,1,0,0,0,0}; // [kg*m^2/s^3]
const exponentsTesla = [7]i8{-2,2,1,-2,0,0,0}; // [kg*m^2/(s^2*A^2)]
// ....
test "example" {
var distance = UnitMeter.init(10).toMeasure();
var time = UnitS.init(2).toMeasure();
UnitOps.div(&distance,time);
var tmp = distance;
assert(std.mem.eql(i8,tmp.exponents,exponentsSpeed));
// var kg = UnitKg.initFromMeasure(tmp); // runtime error
}
|
This proposal is probably not suitable for zig (very scenario specific type checking earned at the cost of more language complexity), but I will leave it for reference.
Start with a basic type (struct/primitive hybrid)
BaseUnit
, that holds amutable value
(f64) and a "comptime"mutable dictionary
of unit-names (enum value) to exponents (signed int).Then, let
BaseUnit
be supported by binary operators (acting on the f64 field) at runtime, while at comptime also the exponent-dictionary is updated and checked.E.g
{2.1, [0,3,1]} * {0.5, [0,-3,2] }
becomes{1.05, [0,0,3] }
, and{3.0, [1,0,2] } / {2.0, [-1,0,2] }
becomes{1.5, [2,0,0] }
. Addition and subtraction leaves theexp-dict
unchanged if theexp-dicts
of the two values are the same. If they don't match, you'll get a compile error.For type checks in code, now introduce named types e.g
UnitMeter
,UnitSecond
, that are the same asBaseUnit
except with a fixed exponent-dictionary.Usage in code, getting some type safety for units:
It's possible to create something like this with normal structs, but the interesting thing here is the idea of having comptime checks on a subset of instance variables for a given type. In this example, the exponent-dictionaries are fully redundant at runtime, so there is no reason to have any performance impact compared to using primitive f64.
Related: #1595 #2953
The text was updated successfully, but these errors were encountered: