Variables in mathematical sense in code?

Programming, for all ages and all languages.
Post Reply
User avatar
Candy
Member
Member
Posts: 3882
Joined: Tue Oct 17, 2006 11:33 pm
Location: Eindhoven

Variables in mathematical sense in code?

Post by Candy »

I've been hacking on a library idea that makes dynamic variables, which you can use in arithmetic expressions and update them later on. When one of the original variables changes, the calculated variables are updated as well.

I'm looking for comments on whether it would be useful, whether it won't be overly confusing (as the example can show, you can make a loop that contains an apparently pointless assignment that does change the rest) and whether people will consider it interesting. I'm not releasing the code for the dyn<> class yet, I'm going to hack it a bit more before I do. One of the problems (probably quite apparent from the outputs and the expressions) is that it fails in spectacularly unclear ways as well and that it can't combine expressions easily yet.

The output from the example:

Code: Select all

candy@blackbox:~/dyn$ g++ -o test test.cc
candy@blackbox:~/dyn$ ./test
0 0 0 -2
0.000000 0.000000 0.000000 -3.141593
25 0 25 23
25.000000 0.000000 25.000000 21.858407
25 0 25 23
25.000000 0.000000 25.000000 21.858407
25 29568 29593 29591
25.000000 29568.000000 29593.000000 29589.858407
25 14784 14809 14807
25.000000 14784.000000 14809.000000 14805.858407
25 9856 9881 9879
25.000000 9856.000000 9881.000000 9877.858407
25 7392 7417 7415
25.000000 7392.000000 7417.000000 7413.858407
25 5913 5938 5936
25.000000 5913.600000 5938.600000 5935.458407
25 4928 4953 4951
25.000000 4928.000000 4953.000000 4949.858407
25 4224 4249 4247
25.000000 4224.000000 4249.000000 4245.858407
25 3696 3721 3719
25.000000 3696.000000 3721.000000 3717.858407
25 3285 3310 3308
25.000000 3285.333333 3310.333333 3307.191741
25 2956 2981 2979
25.000000 2956.800000 2981.800000 2978.658407
25 2688 2713 2711
25.000000 2688.000000 2713.000000 2709.858407
candy@blackbox:~/dyn$                                              
and the example code:

Code: Select all

#include <stdio.h>
#include "dynamic"

int main() {
        dyn<int> w, x, y(29568);
        dyn<int> a = w * w,
                 b = y / x,
                 z = a + b,
                 c = z - dyn<int>(2);
//      dyn<double> d = w;
//      dyn<double> e = x;
//      dyn<double> f = y;
        dyn<double> d, e, f(29568);
        dyn<double> g = d * d,
                    h = f / e,
                    i = g + h,
                    j = i - dyn<double>(3.141592653589793);
        printf("%d %d %d %d\n", (int)a, (int)b, (int)z, (int)c);
        printf("%f %f %f %f\n", (double)g, (double)h, (double)i, (double)j);
        w = 5;
        d = 5;
        printf("%d %d %d %d\n", (int)a, (int)b, (int)z, (int)c);
        printf("%f %f %f %f\n", (double)g, (double)h, (double)i, (double)j);
        for (int l = 0; l<12; l++) {
                x = l;
                e = l;
                printf("%d %d %d %d\n", (int)a, (int)b, (int)z, (int)c);
                printf("%f %f %f %f\n", (double)g, (double)h, (double)i, (double)j);
        }
}
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Post by Combuster »

Creative idea, it most certainly is.

I can think of optimizing usages for this kind of thing - it allows one to use a simple syntax to compute the variables at set time rather than read time, as well as allowing for a clean way to do this.

As far as the readability is concerned, that for the larger part how you name things, write things and document things. If you'd use some naming convention where the type is stored in the name, you could more easily distinguish dynamic variables from hard ones.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
Candy
Member
Member
Posts: 3882
Joined: Tue Oct 17, 2006 11:33 pm
Location: Eindhoven

Post by Candy »

Combuster wrote:Creative idea, it most certainly is.

I can think of optimizing usages for this kind of thing - it allows one to use a simple syntax to compute the variables at set time rather than read time, as well as allowing for a clean way to do this.

As far as the readability is concerned, that for the larger part how you name things, write things and document things. If you'd use some naming convention where the type is stored in the name, you could more easily distinguish dynamic variables from hard ones.
Thank you for the first answer in a full day :)

I got the hard bits from the previous example ironed out, but I'm left with a choice between the current C++ or the theoretical next c++0x. The former works for everybody and gives usable code, but is limited in at least two ways, both causing me to write repetitive code with minor adjustments. The latter can be used, but is very unfamiliar territory for me.

The readability is good then. I'm just worried about programmers being surprised the first time they touch this, just like a mathematician is surprised the first time he programs and stuff isn't updated automatically.

Second design item I've been tripping over is type promotion.

Code: Select all

dyn<double> var = 400;
As common as this example will probably be, it's a cast from an int to a double. Even though it's commonly considered an upcast, that can't be said from most types. I'm going to treat these items as uncompilable, since it's not for me to decide which operation you want done first, type conversion or construction of dynamic variable. No matter that both will eventually have the same function, the choice isn't supposed to be heuristically decided.

Arbitrary operations should also be possible but I'm not sure how you'll apply them. I think some form of

Code: Select all

dyn<int> a, b, c;
dyn<int> v = dyn<int>::apply(&algorithm::mix, a, b, c);
should be readable enough, although it's a shame that you can't just make a direct connection.

I'm also wondering what the best way is to add in ternary operator support, since the ternary operator can't be overridden. I'm thinking of

Code: Select all

dyn<bool> override = false;
dyn<int> v = 12;
v.set_if(override, 16);
override = true;
but that looks a bit ugly and doesn't feel intuitive. I can't beat the set_if with some normal ternary operator, like the following would do

Code: Select all

dyn<int> v = (override ? 16 : 12);
which I would like to do. This is kind of nice for disjunct mathematical functions that have one definition for one part of the domain and another for another part.

I'm also stuck with how to implement self-stabilizing functions in this, since they require an estimate value that you can't calculate until you know the rest of the function, which depends on its own estimate.

I'm considering adding in derived limits, where the limits on the input values are decided by the later operations on it. That way, invalid assignments are forbidden and unsolvable functions become runtime errors.

What type of naming scheme are you suggesting? Something similar to the Hungarian notation?

Code: Select all

class Window {
    public:
        dyn<int> dynX, dynY, dynW, dynH;
};
User avatar
Candy
Member
Member
Posts: 3882
Joined: Tue Oct 17, 2006 11:33 pm
Location: Eindhoven

Post by Candy »

I've been doubting the release of this code, but what the heck. Here's the small code file that makes the example compile & work.

It's pure C++ code, templatized and a b*tch to debug, but she does give nice debugging results. Last time I've tried it she accepted compound expressions.

A few notes:

- When you define a variable and later make use of it, references are made in the list of the variable whose content you need to update it. If you assign a variable to itself, it might work but I'd have to think it though what it would do in terms of object construction, and reading out its value would result in an obvious infinite loop.
- There are in fact two types of object with a common parent. One type is the true variable, which is a sort of handle for another variable, which contains a list of listeners. The second is a true variable (of any sort, a place in memory, a function of zero or more other variables) and to this one can only be listened by one variable. Any time you wish to reuse anything (common subexpression elimination comes to mind) you're going to have to code it explicitly.
- The foregoing implies that assigning to a variable with a name (first type) will not update it but replace it, and more specifically, will replace the second half of it. Same goes for +=, -= etc.
- I've not bothered with adding all types of operator, you can figure that out yourself. One I'd really like to add that would be very useful is prohibited by the C++ language, namely ?:. I'm considering adding a bit of override_if(dyn<bool>, dyn<T>) to the variables to still allow optional automatic overrides.
- The class set is intended to be pretty orthogonal to use, as in, they should behave like the normal numbers as much as possible. This leads to 8 overrides per operator, as you can put either a const dyn_c<T> &, a dyn_c<T> & or a const T & in place of either operand (and the 9th possible combination is just applying the operator).
- Casts are always forced. Since the classes don't know about up or down, you're going to have to specify the cast you want. Yes, that's going to give an error when you try to assign an int to a dyn<double>.
- I'm mainly hoping for ideas about improvement of the design, the concept or things I could add that would add fundamental functionality to it. I'm also looking for reviews on what could change to make it fit for any other application, since I'm mainly aiming at small-scale control linkage. That would be, assigning the width of one frame element as a function of another to truly be that function instead of a value assignment. I'm not sure whether this'll be intuitive or helpful but I hope it will.
- When you mess around with the code, expect a bunch of very weird compile errors and runtime errors. I've had a few errors I didn't know the compiler could give.
- Using C++0x features, specifically right-hand references, would bring the overload count down to 3 (I think, at least). I haven't done this yet since requiring a compiler that doesn't really exist yet might limit the usability.
- Debugging notes have been left in. These symbolize mostly construction and destruction. When trying to create value-type-ish objects with some reference counting it's very common to lose a few along the way, so this was a very helpful way of seeing why it segfaulted or called terminate without exception and without call to terminate.
- Licensing: I hereby place the code in the public domain. Good luck in using it and if you find it useful, leave me a note.

That's it for the notes. I work with GCC 3.4.6 as host compiler and 4.1.1 as target compiler. I'm certain it works with 3.4.6, not too certain on 4.1.1. Compiling with -pedantic -Wall -Wextra -std=gnu++98 should give no warnings, but I'm pretty sure since it's a template class with loads of overloads that I've missed a lot of occasions that it didn't match up. You will likely get a load of warnings about anything. Tell me please :)

Good luck :)
Attachments
dynamic.h
Dynamic variables, full source for header
(13.25 KiB) Downloaded 63 times

[The extension cc has been deactivated and can no longer be displayed.]

Post Reply