www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - class design question (inner classes)

reply coxalan <coxalan web.de> writes:
Hello,

I come from mathematics and I want to create some classes for _fast_
computations in certain algebraic structures (groups, fields, rings, ...).

My hope is that specialization at compile time will give me an advantage over
universal computer algebra systems.

I want to have:
1) Objects for the algebraic base structures
2) Some type (class, struct, whatever) for the elements of this structure

For example:
1) a class SymmetricGroup and 2) a class Permutation.

Each instance of type 2) "belongs" to exactly one object of class 1).
But this link should not be stored for each instance of 2). Instead, It should
be given statically by the type.

Furthermore, for convenience I really want to have overloaded operators for the
objects of 2).


Up to now I tried these two examples:

*** 1 ***
import std.stdio;

class SymmetricGroup {
    const uint degree;

    this(int degreeIn) {
        degree = degreeIn;
    }

    struct Permutation {
        uint data[];
    }

    Permutation createPermutation(uint[] dataIn) {
        Permutation result;
        result.data = dataIn;
        return result;
    }

    Permutation mul(Permutation a, Permutation b) {
        Permutation result;
        result.data.length = degree;
        foreach(int i, uint val; b.data) {
            result.data[i] = a.data[val];
        }
        return result;
    }
}

void main() {
    SymmetricGroup s = new SymmetricGroup(3);
    s.Permutation a = s.createPermutation([0,2,1]);
    s.Permutation b = s.createPermutation([1,2,0]);
    s.Permutation c = s.mul(a,b);
    writefln("%d",c.data);
}
********

The problem with this is that I don't know how to overload the *-Operator such
that I can write
s.Permutation c = a * b;
instead of
s.Permutation c = s.mul(a,b);

I cannot add a method "opMul" within the struct "Permutation", because to call
the method "mul" I would need a pointer to the outer class "SymmetricGroup",
which I don't have.

[Comment: Yes, I could write

        Permutation opMul(Permutation x) {
            Permutation result;
            result.data.length = data.length;
            foreach(int i, uint val; x.data) {
                result.data[i] = data[val];
            }
            return result;
        }

in this particular example, but in general I need information stored in the
outer class 1).]


*** 2 ***
import std.stdio;

class SymmetricGroup {
    const uint degree;

    this(int degreeIn) {
        degree = degreeIn;
    }

    class Permutation {
        uint data[];

        this() {
            data.length = degree;
        }

        this(uint[] dataIn) {
            data = dataIn;
        }

        Permutation opMul(Permutation x) {
            return mul(this, x);
        }
    }

    Permutation mul(Permutation a, Permutation b) {
        Permutation result = new Permutation();
        foreach(int i, uint val; b.data) {
            result.data[i] = a.data[val];
        }
        return result;
    }
}

void main() {
    SymmetricGroup s = new SymmetricGroup(3);
    s.Permutation a = s.new Permutation([0,2,1]);
    s.Permutation b = s.new Permutation([1,2,0]);
    s.Permutation c = a * b;
    writefln("%d",c.data);
}
********

This is definitely more elegant than the first example. But here, every object
of type "s.Permutation" stores a reference to the object "s", which I want to
avoid.

If it was possible to instantiate the object "s" at compile time
(something like
static const SymmetricGroup s = new SymmetricGroup(3);
),
the above mentioned references would not be needed. But unfortunately, It is
not possible to instantiate objects at compile time (what's actually the reason
for this?).

[A final comment:
I know that I could go without the class SymmetricGroup, and have only objects
of type Permutation. But I really want these outer classes, to do constructions
like direct products of groups, etc.]


I believe that within the D template toolkit, there must be a solution for my
problem, which I simply don't see.
I will appreciate any hint or suggestion.
Sep 11 2007
next sibling parent reply Regan Heath <regan netmail.co.nz> writes:
coxalan wrote:
 Hello,
 
 I come from mathematics and I want to create some classes for _fast_
computations in certain algebraic structures (groups, fields, rings, ...).
 
 My hope is that specialization at compile time will give me an advantage over
universal computer algebra systems.
 
 I want to have:
 1) Objects for the algebraic base structures
 2) Some type (class, struct, whatever) for the elements of this structure
 
 For example:
 1) a class SymmetricGroup and 2) a class Permutation.
 
 Each instance of type 2) "belongs" to exactly one object of class 1).
 But this link should not be stored for each instance of 2). Instead, It should
be given statically by the type.
 
 Furthermore, for convenience I really want to have overloaded operators for
the objects of 2).
 
 
 Up to now I tried these two examples:
 
 *** 1 ***
 import std.stdio;
 
 class SymmetricGroup {
     const uint degree;
 
     this(int degreeIn) {
         degree = degreeIn;
     }
 
     struct Permutation {
         uint data[];
     }
 
     Permutation createPermutation(uint[] dataIn) {
         Permutation result;
         result.data = dataIn;
         return result;
     }
 
     Permutation mul(Permutation a, Permutation b) {
         Permutation result;
         result.data.length = degree;
         foreach(int i, uint val; b.data) {
             result.data[i] = a.data[val];
         }
         return result;
     }
 }
 
 void main() {
     SymmetricGroup s = new SymmetricGroup(3);
     s.Permutation a = s.createPermutation([0,2,1]);
     s.Permutation b = s.createPermutation([1,2,0]);
     s.Permutation c = s.mul(a,b);
     writefln("%d",c.data);
 }
 ********
 
 The problem with this is that I don't know how to overload the *-Operator such
that I can write
 s.Permutation c = a * b;
 instead of
 s.Permutation c = s.mul(a,b);
 
 I cannot add a method "opMul" within the struct "Permutation", because to call
the method "mul" I would need a pointer to the outer class "SymmetricGroup",
which I don't have.
 
 [Comment: Yes, I could write
 
         Permutation opMul(Permutation x) {
             Permutation result;
             result.data.length = data.length;
             foreach(int i, uint val; x.data) {
                 result.data[i] = data[val];
             }
             return result;
         }
 
 in this particular example, but in general I need information stored in the
outer class 1).]
 
 
 *** 2 ***
 import std.stdio;
 
 class SymmetricGroup {
     const uint degree;
 
     this(int degreeIn) {
         degree = degreeIn;
     }
 
     class Permutation {
         uint data[];
 
         this() {
             data.length = degree;
         }
 
         this(uint[] dataIn) {
             data = dataIn;
         }
 
         Permutation opMul(Permutation x) {
             return mul(this, x);
         }
     }
 
     Permutation mul(Permutation a, Permutation b) {
         Permutation result = new Permutation();
         foreach(int i, uint val; b.data) {
             result.data[i] = a.data[val];
         }
         return result;
     }
 }
 
 void main() {
     SymmetricGroup s = new SymmetricGroup(3);
     s.Permutation a = s.new Permutation([0,2,1]);
     s.Permutation b = s.new Permutation([1,2,0]);
     s.Permutation c = a * b;
     writefln("%d",c.data);
 }
 ********
 
 This is definitely more elegant than the first example. But here, every object
of type "s.Permutation" stores a reference to the object "s", which I want to
avoid.
 
 If it was possible to instantiate the object "s" at compile time
 (something like
 static const SymmetricGroup s = new SymmetricGroup(3);
 ),
 the above mentioned references would not be needed. But unfortunately, It is
not possible to instantiate objects at compile time (what's actually the reason
for this?).
 
 [A final comment:
 I know that I could go without the class SymmetricGroup, and have only objects
of type Permutation. But I really want these outer classes, to do constructions
like direct products of groups, etc.]
 
 
 I believe that within the D template toolkit, there must be a solution for my
problem, which I simply don't see.
 I will appreciate any hint or suggestion.

Maybe template bolt-ins? (I'm not 100% sure I know what you want to achieve so this may be waaaay off base) class SymmetricGroup { uint degree; this(int degreeIn) { degree = degreeIn; } } class Permutation(T, int D) : T { uint data[]; this() { super(D); data.length = degree; } this(uint[] dataIn) { super(D); data = dataIn; } Permutation opMul(Permutation b) { auto result = new Permutation; foreach(int i, uint val; b.data) { result.data[i] = data[val] * b.data[val]; } return result; } } alias Permutation!(SymmetricGroup, 3) SGP; void main() { SGP a = new SGP(); SGP b = new SGP(); SGP c = a * b; } In the above you don't actually need SymmetricGroup at all, your template could just take "int D" and use that for degree, however I suspect you want to add more to SymmetricGroup? Regan
Sep 11 2007
parent reply coxalan <coxalan web.de> writes:
Regan Heath Wrote:

 Maybe template bolt-ins?  (I'm not 100% sure I know what you want to 
 achieve so this may be waaaay off base)
 
 class SymmetricGroup
 {
       uint degree;
 
       this(int degreeIn) {
           degree = degreeIn;
       }
 }
 
 class Permutation(T, int D) : T
 {
           uint data[];
 
           this() {
               super(D);
               data.length = degree;
           }
 
           this(uint[] dataIn) {
               super(D);
               data = dataIn;
           }
 
           Permutation opMul(Permutation b) {
 		auto result = new Permutation;
 		foreach(int i, uint val; b.data) {
 		    result.data[i] = data[val] * b.data[val];
 		}
 		return result;
          }
 }
 
 alias Permutation!(SymmetricGroup, 3) SGP;
 
 void main()
 {
 	SGP a = new SGP();
 	SGP b = new SGP();
 	SGP c = a * b;
 }

With your code, the uint-value "degree" gets stored once for every instance of SGP. This is not what I want. The class SymmetricGroup should collect everything that all of its elements (the object of the class Permutation) have in common. Just imagine the same for a class TableGroup, where the multiplication-values are stored in an huge array. This table definitely should not be stored for each single element of the group.
 In the above you don't actually need SymmetricGroup at all, your 
 template could just take "int D" and use that for degree, however I 
 suspect you want to add more to SymmetricGroup?

Yes, of course this is only an example which I made as short as possible. There are two reasons for this class [these are the classes of kind 1) in my first posting]: * I want to implement more kinds of groups, for example also matrix groups. All the groups should have a common interface which will be used in templated group algorithms. * There are constructions which combine two or more groups into a new one. For the implementation of such combined groups, It will be very convenient to have classes of this kind 1). coxalan
Sep 11 2007
parent reply Regan Heath <regan netmail.co.nz> writes:
 With your code, the uint-value "degree" gets stored once for every
 instance of SGP. This is not what I want. 

Make it "static" or, make it a template parameter and just use that, eg. class SymmetricGroup { } class Permutation(T, int D) : T { uint data[]; this() { data.length = D; } this(uint[] dataIn) { data = dataIn; } Permutation opMul(Permutation b) { auto result = new Permutation; foreach(int i, uint val; b.data) { result.data[i] = data[val] * b.data[val]; } return result; } } alias Permutation!(SymmetricGroup, 3) SGP; void main() { SGP a = new SGP(); SGP b = new SGP(); SGP c = a * b; }
 The class SymmetricGroup
 should collect everything that all of its elements (the object of the
 class Permutation) have in common.

Like .. (list please). Are they all data members or methods as well. Have you considered using mixins to mix a SymmetricGroup template containing methods/data members into Permutation.
 In the above you don't actually need SymmetricGroup at all, your 
 template could just take "int D" and use that for degree, however I
  suspect you want to add more to SymmetricGroup?

Yes, of course this is only an example which I made as short as possible.

Sure, but a short example can make it hard (for us/me) to see the whole picture. :) Especially when my formal maths education stopped at high school level.
 There are two reasons for this class [these are the classes of kind
 1) in my first posting]:
 
 * I want to implement more kinds of groups, for example also matrix
 groups. All the groups should have a common interface which will be
 used in templated group algorithms. * There are constructions which
 combine two or more groups into a new one. For the implementation of
 such combined groups, It will be very convenient to have classes of
 this kind 1).

Ok. I think I have reached the limit of my usefulness here :) Someone else is bound to have some ideas. Regan
Sep 11 2007
parent reply coxalan <coxalan web.de> writes:
Regan Heath Wrote:

  > The class SymmetricGroup
 should collect everything that all of its elements (the object of the
 class Permutation) have in common.

Like .. (list please). Are they all data members or methods as well.

Definitely methods, like uint order() which returns the number of the elements of the group. For the SymmetricGroup there are no other data members, but for other Groups there could be arbitrary data members (like a big array where the multiplication results are stored).
 [...]
 Ok.  I think I have reached the limit of my usefulness here :)
 
 Someone else is bound to have some ideas.

It was my fear that I put too much technical mathematics stuff into my posting. I already put this question (in C++ form) into a mathematical internet forum, but then the OOP-design part of the discussion got stuck at a very basic level... :-) Now I rethought my question and how I can put it on a more abstract level to get out the math part. I guess my problem comes down to this point: class Outer { class Inner { [...] } [...] } main() { A a = new A(); [... initialize many a.Inner objects here ...] } Once that "a" is initialized, te reference "a" stays constant all the time. So all further references/pointers to "a" are redundant. Especially, the references to "a" stored in "a.Inner" inner objects are redundant. So if there was a way to tell the compiler that the reference a will never change, the compiler could do the optimization and skip all these inner-class-references. Now the question is: Is there currently a way to achieve a satisfying, equivalent result? If not: Should I make this a feature request? coxalan
Sep 11 2007
next sibling parent reply Regan Heath <regan netmail.co.nz> writes:
coxalan wrote:
 Regan Heath Wrote:
 
 The class SymmetricGroup should collect everything that all of
 its elements (the object of the class Permutation) have in
 common.

well.

Definitely methods, like uint order() which returns the number of the elements of the group. For the SymmetricGroup there are no other data members, but for other Groups there could be arbitrary data members (like a big array where the multiplication results are stored).
 [...] Ok.  I think I have reached the limit of my usefulness here
 :)
 
 Someone else is bound to have some ideas.

It was my fear that I put too much technical mathematics stuff into my posting. I already put this question (in C++ form) into a mathematical internet forum, but then the OOP-design part of the discussion got stuck at a very basic level... :-) Now I rethought my question and how I can put it on a more abstract level to get out the math part. I guess my problem comes down to this point: class Outer { class Inner { [...] } [...] } main() { A a = new A(); [... initialize many a.Inner objects here ...] } Once that "a" is initialized, te reference "a" stays constant all the time. So all further references/pointers to "a" are redundant. Especially, the references to "a" stored in "a.Inner" inner objects are redundant. So if there was a way to tell the compiler that the reference a will never change, the compiler could do the optimization and skip all these inner-class-references. Now the question is: Is there currently a way to achieve a satisfying, equivalent result? If not: Should I make this a feature request? coxalan

What about doing it manually/explicitly... import std.stdio; import std.string; import std.c.stdlib; class Outer { Inner[] m_Data; uint m_Degree; this(uint d) { m_Degree = d; } Inner allocate(uint[] data) { m_Data ~= new Inner(this, data); return m_Data[$-1]; } new(uint size) { writefln("Outer new %d", size); return malloc(size); } delete(void *p) { free(p); } } class Inner { static Outer m_Outer; uint m_Data[]; this(Outer o, uint[] data = null) { m_Outer = o; m_Data.length = m_Outer.m_Degree; if (data !is null) { m_Data[0..$] = data[0..m_Data.length]; } } Inner opMul(Inner b) { Inner res = new Inner(m_Outer); foreach(int i, uint val; b.m_Data) { res.m_Data[i] = m_Data[i] * b.m_Data[i]; } return res; } new(uint size) { writefln("Inner new %d", size); return malloc(size); } delete(void *p) { free(p); } string toString() { return format("%s", m_Data); } } void main() { Outer o = new Outer(3); Inner a = o.allocate([0,1,2]); Inner b = o.allocate([1,2,3]); Inner c = a * b; writefln(c); } Of course, if you use Inner with another Outer class it will overwrite the static m_Outer and fail horribly... Regan
Sep 11 2007
parent Regan Heath <regan netmail.co.nz> writes:
Regan Heath wrote:
 Of course, if you use Inner with another Outer class it will overwrite
 the static m_Outer and fail horribly...

But maybe we can get round this by using templates... class Inner(T) { static T m_Outer; } That way, if you use say.. Inner!(Outer) a; Inner!(Outer) b; Inner!(Outer) c; Inner!(Bob) d; Inner!(Bob) e; Inner!(Fred) f; You will get a static m_Outer for each combination, thus 3 for the code above. Regan
Sep 11 2007
prev sibling next sibling parent reply Downs <default_357-line yahoo.de> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

coxalan wrote:
 Regan Heath Wrote:
 
  > The class SymmetricGroup
 should collect everything that all of its elements (the object of the
 class Permutation) have in common.


Definitely methods, like uint order() which returns the number of the elements of the group. For the SymmetricGroup there are no other data members, but for other Groups there could be arbitrary data members (like a big array where the multiplication results are stored).
 [...]
 Ok.  I think I have reached the limit of my usefulness here :)

 Someone else is bound to have some ideas.

It was my fear that I put too much technical mathematics stuff into my posting. I already put this question (in C++ form) into a mathematical internet forum, but then the OOP-design part of the discussion got stuck at a very basic level... :-) Now I rethought my question and how I can put it on a more abstract level to get out the math part. I guess my problem comes down to this point: class Outer { class Inner { [...] } [...] } main() { A a = new A(); [... initialize many a.Inner objects here ...] } Once that "a" is initialized, te reference "a" stays constant all the time. So all further references/pointers to "a" are redundant. Especially, the references to "a" stored in "a.Inner" inner objects are redundant. So if there was a way to tell the compiler that the reference a will never change, the compiler could do the optimization and skip all these inner-class-references. Now the question is: Is there currently a way to achieve a satisfying, equivalent result? If not: Should I make this a feature request? coxalan

I'm surprised nobody brought this up before. Yes, of course you can do this. class Outer { class Inner { [...] } } void main() { Outer foo=new Outer; auto bar=foo.new Inner; auto baz=foo.new Inner; } That what you want? :) --downs -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFG5vsZpEPJRr05fBERAtpiAJ47qu9tffXEr47OsNR3+v89i4JLEwCfU15R rUPiTTNTUCH/o/lv52Cae2M= =UmNC -----END PGP SIGNATURE-----
Sep 11 2007
parent reply Downs <default_357-line yahoo.de> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I'm sorry.
I didn't read/understand your requirements precisely.
Of course, this _will_ entail a reference to the outer class. As they
say, "oops".
Sorry again.
 --downs
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFG5vvDpEPJRr05fBERApk6AJ4mQwqMcVCiUNAp4q8bSY3O8vTK4wCgnGSD
fAzOol+zuD48RTxx2aRFH+E=
=5Sm8
-----END PGP SIGNATURE-----
Sep 11 2007
parent coxalan <coxalan web.de> writes:
Downs Wrote:

 I'm sorry.
 I didn't read/understand your requirements precisely.
 Of course, this _will_ entail a reference to the outer class. As they
 say, "oops".
 Sorry again.

No problem and thanks for your answer. As you can see in my postings, it took me some time to see what really the matter is, too: I know that in my case the reference to the outer class object will never change. So I know that all references stored in the inner class objects will be the same and are not necessary. coxalan
Sep 11 2007
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
coxalan wrote:
 Regan Heath Wrote:
 [...]
 Ok.  I think I have reached the limit of my usefulness here :)

 Someone else is bound to have some ideas.

It was my fear that I put too much technical mathematics stuff into my posting. I already put this question (in C++ form) into a mathematical internet forum, but then the OOP-design part of the discussion got stuck at a very basic level... :-) Now I rethought my question and how I can put it on a more abstract level to get out the math part. I guess my problem comes down to this point: class Outer { class Inner { [...] } [...] } main() { A a = new A(); [... initialize many a.Inner objects here ...] } Once that "a" is initialized, te reference "a" stays constant all the time. So all further references/pointers to "a" are redundant. Especially, the references to "a" stored in "a.Inner" inner objects are redundant. So if there was a way to tell the compiler that the reference a will never change, the compiler could do the optimization and skip all these inner-class-references. Now the question is: Is there currently a way to achieve a satisfying, equivalent result? If not: Should I make this a feature request? coxalan

I assume correctly that A == Outer in that example above? Some questions: is the total number of instances of Outer constant and known at compile time? If not, I think it's impossible to do what you want. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 12 2007
prev sibling next sibling parent Christian Kamm <kamm.incasoftware shift-at-left-and-remove-this.de> writes:
 If it was possible to instantiate the object "s" at compile time
 (something like
 static const SymmetricGroup s = new SymmetricGroup(3);
 ),
 the above mentioned references would not be needed. But unfortunately, It
 is not possible to instantiate objects at compile time (what's actually
 the reason for this?).

If SymmetricGroup contains only compile time constants, you could do: struct SymmetricGroup(int degreeIn) { const int degree = degreeIn; } struct Permutation(Group) { alias Permutation!(Group) thistype; thistype opMul(ref thistype a) { // do something with Group.degree } } void main() { alias SymmetricGroup!(3) s; Permutation!(s) a, b, c; c = a * b; }
Sep 11 2007
prev sibling parent reply Roberto Mariottini <rmariottini mail.com> writes:
coxalan wrote:
[...]
 This is definitely more elegant than the first example. But here, every object
of 

to avoid. You can simply declare the inner class as static. This way it won't store a reference to its container class. Ciao
Sep 11 2007
parent coxalan <coxalan web.de> writes:
Roberto Mariottini Wrote:

 coxalan wrote:
 [...]
 This is definitely more elegant than the first example. But here, every object
of 

to avoid. You can simply declare the inner class as static. This way it won't store a reference to its container class. Ciao

A static outer class will not work, because within the inner class I need access to the class members of the outer class. But since the outer class will stay constant, there is no need to store this reference again and again for each inner object.
Sep 11 2007