I agree with Keno that a macro is the best way to tackle this problem, I just didn't realise that the entire body of the code could easily be inside the macro, and in the end core language features are handled in a similar way. So if you are happy with the @with var (name1, name2) syntax I think it would be a good start by adding that to base, since this will be a useful addition when the number of names is small.
Now if I have a bunch of different functions that process on these named fields I would have something like:
@with state (v1, v5, v7, v2, v3, v4, v9, v12, v15, v18, v19, v20, v21, v8, v22, v23, v24, i1, i2, i3, i4, i5, i6, i7, ic1eq, ic2eq, ic3eq, ic4eq, ic5eq, ic6eq, ic7eq, ic8eq, sr, srinv, pi, gmin, is1, nvtf1, is2, nvtf2, nvtinvf1, vcritf1, nvtinvf2, vcritf2, gc1, gr1, gr2, itxr2, gc2, gc3, gc4, gc5, gr7, gc6, gr3, itxr3, gc7, gr4, gc8, gr5, vpos, vneg, gin, gininv, vposcap, vnegcap, ginotacore, ginotares, ginotacoreinv, ginotaresinv, vc3lo, vc3hi, a4a4c, a5a5c, a6a6c, a14a14c, a16a16c, a17a17c, a17a17nrmc, a12a17c, a16a16nrmc, a15a16c, a14a14nrmc, a13a14c, a6a14c, a13a6c, a5a5nrmc, a4a5c, a2a5c, a2a4c, a4a4nrmc, a1a4c, v5c, v7c)
process2 (state::CircuitModel, input::Float64)
@with state (v1, v5, v7, v2, v3, v4, v9, v12, v15, v18, v19, v20, v21, v8, v22, v23, v24, i1, i2, i3, i4, i5, i6, i7, ic1eq, ic2eq, ic3eq, ic4eq, ic5eq, ic6eq, ic7eq, ic8eq, sr, srinv, pi, gmin, is1, nvtf1, is2, nvtf2, nvtinvf1, vcritf1, nvtinvf2, vcritf2, gc1, gr1, gr2, itxr2, gc2, gc3, gc4, gc5, gr7, gc6, gr3, itxr3, gc7, gr4, gc8, gr5, vpos, vneg, gin, gininv, vposcap, vnegcap, ginotacore, ginotares, ginotacoreinv, ginotaresinv, vc3lo, vc3hi, a4a4c, a5a5c, a6a6c, a14a14c, a16a16c, a17a17c, a17a17nrmc, a12a17c, a16a16nrmc, a15a16c, a14a14nrmc, a13a14c, a6a14c, a13a6c, a5a5nrmc, a4a5c, a2a5c, a2a4c, a4a4nrmc, a1a4c, v5c, v7c)
# some in depth numerical processing on state and input that updates the state
end
process3 / process 4 etc similarly defined
If I add a single new name (variable) into the CircuitModel type I will have to go and find everywhere I use @with and insert that variable into the right place inside the list, which to me seems pretty brittle and needlessly repetitive. Is this the best way to handle this?
Ok, so there may be problems working out which variable is being referred to, but this is all because of the convenience of the language in other aspects, eg:
a = 2 # could possibly add a new local variable, or update an existing local or global variable
So the problems you are pointing out seem mostly to be with ambiguity of scope, but in this situation I know exactly what I want access to and I'm happy to completely specify it since otherwise the code becomes horrible, so how about a new "locked down scope" block that specifically stops the introduction of implicit locals and also blocks access to globals or anything else other than what is specifically stated at the start of the block? This look something like:
process5 (state::CircuitModel, input::Float)
@withonly state::CircuitModel, input begin
v1 = 1 # fine since "v1" is a name of "CircuitModel"
v5 = input # fine since "v5" is a name of "CircuitModel", and "input" is allowed as well
a = 2 # error, "a" is not not a name of "CircuitModel" and is not "input"
local b = 3 # ok, since this is specifically a new local variable "b"
gr5 = b # ok since "b" is the new local variable and "gr5" is a name of "CircuitModel"
end
end
So this addresses your points about not being able to reason locally about what is going on, since any variable must be from one of the blocks arguments (a name clash would be a compiler error). I would even be happy being able to limit use so that only the first argument could be a composite type, so if the variable isn't listed specifically, or defined as a local you know it must be a member of the composite type or you get a compiler error. This would allow all the c++ hacks like me to have their "this" in a safe, unambiguous manner and not have to obfuscate our code with typing "this" everywhere.
So here is an example of part of one of the process blocks if you have to put a "state." in front of everything, which is wonderfully explicit, but also completely useless since I can't read the code!
process6 (state::CircuitModel, input::Float)
# skipped a large chunk of code, this is actually a section of an inner loop
state.v1 = input;
state.v2 = state.z3-(state.v1*state.a1a4c+state.v5c*state.a2a4c);
state.v3 = state.z4-(state.v5c*state.a2a5c+state.v2*state.a4a5c);
state.v4 = state.z5-state.v1*state.a1a6-(state.v7c*state.a3a6+state.v3*state.a5a6);
state.v9 = state.z6+state.v3*state.a6a7-state.v4*state.a6a7;
state.v12 = state.z7-state.v9*state.a7a8;
state.v15 = state.z8-state.v12*state.a8a9;
state.v18 = state.z9-state.v15*state.a9a10;
state.v19 = state.v18;
state.v20 = state.ia1eq-state.v19*state.ga1;
state.v21 = state.v20*state.k-state.v1*state.k;
state.v8 = state.z13-(state.v4*state.a6a14c+state.v21*state.a13a14c);
state.v22 = -(state.v12*2);
state.v23 = state.z15-state.v22*state.a15a16c;
state.v24 = state.z16-state.v20*state.a12a17c;
state.i1 = state.v2*state.gr1-state.v1*state.gr1;
state.i2 = state.z1+state.v3*state.gr2+state.v4*state.gr3-(state.v5c*state.gr2+state.v7c*state.gr3);
state.i3 = state.itr3+state.v7c*state.gr3-state.v4*state.gr3;
state.i5 = state.v24*state.gc8-(state.ic8eq+state.v20*state.gc8);
state.i6 = state.v8*state.gr7-state.v21*state.gr7;
state.i7 = state.v23*state.gc7-(state.ic7eq+state.v22*state.gc7);
end
And here is the a snippet of using a "locked down" block, the code isn't easy to understand, but I can spot possible errors and fix it much more easily since I see the variable names more easily:
process7 (state::CircuitModel, input::Float)
@withonly state::CircuitModel, input begin
# skipped a large chunk of code, this is actually a section of an inner loop
v1 = input;
v2 = z3-(v1*a1a4c+v5c*a2a4c);
v3 = z4-(v5c*a2a5c+v2*a4a5c);
v4 = z5-v1*a1a6-(v7c*a3a6+v3*a5a6);
v9 = z6+v3*a6a7-v4*a6a7;
v12 = z7-v9*a7a8;
v15 = z8-v12*a8a9;
v18 = z9-v15*a9a10;
v19 = v18;
v20 = ia1eq-v19*ga1;
v21 = v20*k-v1*k;
v8 = z13-(v4*a6a14c+v21*a13a14c);
v22 = -(v12*2);
v23 = z15-v22*a15a16c;
v24 = z16-v20*a12a17c;
i1 = v2*gr1-v1*gr1;
i2 = z1+v3*gr2+v4*gr3-(v5c*gr2+v7c*gr3);
i3 = itr3+v7c*gr3-v4*gr3;
i5 = v24*gc8-(ic8eq+v20*gc8);
i6 = v8*gr7-v21*gr7;
i7 = v23*gc7-(ic7eq+v22*gc7);
end
end