D - Why isn't everything a template?
- Bill Cox (16/16) Feb 02 2003 Hi.
- Walter (4/8) Mar 08 2003 What you're describing is in essence a typeless programming language, su...
- Daniel Yokomiso (130/138) Mar 08 2003 Hi,
- Antti Sykari (14/25) Mar 08 2003 These two make assertions made me want the operator "implies"...
- Daniel Yokomiso (13/38) Mar 08 2003 It'll give us a problem, because implies is lazy. It must have the same
- Mike Wynn (37/50) Mar 08 2003 may seem a little picky but ....
- Daniel Yokomiso (10/63) Mar 08 2003 Yes, you're correct. I just wrote down the contract without trying to ma...
- Mike Wynn (52/114) Mar 09 2003 array
- Bill Cox (27/30) Mar 09 2003 Hi, Dan.
- Daniel Yokomiso (49/79) Mar 10 2003 Hi,
- Bill Cox (14/132) Mar 10 2003 This style of template framework looks good to me. I've not implemented...
- Daniel Yokomiso (13/26) Mar 11 2003 Probably it'll be as efficient as regular objects (they may be more opti...
- Bill Cox (64/75) Mar 12 2003 Hi, Daniel.
- Daniel Yokomiso (66/141) Mar 12 2003 Hi,
- Bill Cox (27/191) Mar 12 2003 Hi, Daniel.
- Jeroen van Bemmel (16/16) Mar 09 2003 As with many things in life, it's a matter of choice: what do you want/n...
- Bill Cox (18/37) Mar 09 2003 I agree. Since my original post, I've become convinced that templates
Hi. The problem I have with templates is that you have to ask your programmers to parameterise their code, and turn it into a template, or you don't get the awesome reusability. My question is this: Why isn't everything a template by default? Instead of parameterising code up front, you could reuse code that never got that upgrade. Basically, if a programmer has done his job and written a useful algorithm, why can't we just reuse it as if he'd made all the identifiers in his code template parameters. When trying to reuse code, you'd specify what identifiers to make parameters, and then instantiate it. Reguardless of the mechanism, I feel strongly that when a piece of code is finished and works, we shouldn't have to muck with it to make it reusable. A program is a mathematically precise definition of an algorithm. Why go through the process of parameterising it? Bill
Feb 02 2003
"Bill Cox" <bill viasic.com> wrote in message news:3E3D337D.6030501 viasic.com...Reguardless of the mechanism, I feel strongly that when a piece of code is finished and works, we shouldn't have to muck with it to make it reusable. A program is a mathematically precise definition of an algorithm. Why go through the process of parameterising it?What you're describing is in essence a typeless programming language, such as javascript.
Mar 08 2003
"Walter" <walter digitalmars.com> escreveu na mensagem news:b4dof2$pej$1 digitaldaemon.com..."Bill Cox" <bill viasic.com> wrote in message news:3E3D337D.6030501 viasic.com...Hi, There are several layers of parametrization possible when a language allows you to use higher-order functions. A simple recursive binary search looks like this: int binarySearch(int[] array, int item) { in { assert(isSorted(array)); } out(result) { assert(result >= -1); if (result >= 0) { assert(array[result] == item); } if (array.length == 0) { assert(result == -1); } } body { if (array.length == 0) { return -1; } int middle = array.length / 2; if (array[middle] == item) { return middle; } else if (array[middle] < item) { return binarySearch(array[middle + 1 .. array.length], item); } else { return binarySearch(array[0 .. middle], item); } } Hmmm, the obvious parametrization is at type, so we could use a template. This simple parametrization work for every type that provides a comparison operation, but some don't. If we parametrize over this we get: // type parametrization included // contracts removed for brevity public int binarySearch(T[] array, int function(T, T) order, T item) { if (array.length == 0) { return -1; } else { int middle = array.length / 2; if (order(item, array[middle]) == 0) { return middle; } else if (order(item, array[middle]) == -1) { return binarySearch(array[0 .. middle], order, item); } else if (order(item, array[middle]) == +1) { return binarySearch(array[middle + 1.. array.length], order, item); } else { assert(0); // here we protect ourselves against bad order functions return -1; // just pleasing the compiler } } } The original version looks like: public int binarySearch(T[] array, T item) { // using DTL ascending comparison function. return binarySearch(array, cmp.ascending, item); } Much better isn't it? Wait again, we assume that our user wants to compare againt a passed item. We can simulate this behaviour using a order function that encloses (closure-like) the search item. We also can parametrize the return of the function to be a generic function that'll be applied to the (index, value) pair that satisfies the ordering function: public int binaryApply(T[] array, int function(T) order, S function(int, T) apply) { if (array.length == 0) { return apply(-1, null); } else { int middle = array.length / 2; if (order(array[middle]) == 0) { return apply(middle, array[middle]); } else if (order(array[middle]) == -1) { return binaryApply(array[0 .. middle], order, apply); } else if (order(array[middle]) == +1) { return binaryApply(array[middle + 1.. array.length], order, apply); } else { assert(0); // here we protect ourselves against bad order functions return apply(-1, null); // just pleasing the compiler } } } The original version looks like: public int binarySearch(T[] array, int function(T, T) order, T item) { // using DTL ascending comparison function. return binaryApply(array, int function(T each) {return order(each, item);}, int function(int index, T each) {return index;}); } We could parametrize the slicing of the array and let the binaryApply operation work on any type that provides a threeWaySplit function, that given a ordering function returns the before and after slices and the middle element. We could also parametrize the algorithm we use to obtain the middle index, and get a more generic divideAndConquer function (it's not binary if we don't divide by two anymore ;-) ). Any algorithm can be parametrized over all operations it uses. Studying lambda calculus we see that all functions can be defined using two combinator s and k: k = \x.\y.x s =\x.\y.\z.(x z) (y z) \ means lambda. In D (without currying k and s parameters) they would look like: T k(T x, U, y) { return x; } T s((T function(S)) function(R) x, S function(R) y, R z) { return x(z)(y(z)); } That's as abstract as abstract can get. Well you curry k and s parameters in D too, but I leave this as an exercise to the reader. The point is that everything can be parametrized, loops, operations, ifs, gotos, etc. not just types and values. But if you parametrize everything you get something like unlambda. Templates are a nice way to parametrize algorithms, either types or functions. TANSTAFL as the tribe's elders used to say. Best regards, Daniel Yokomiso. P.S.: Every time someone equates "templates = generic parameter types for languages without function types" I feel a terrible urge to tell them to read something like STL sourcecode or the Haskell prelude. This usually enlightens them. "It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration." - Edsger W. Dijkstra --- Outgoing mail is certified Virus Free. Checked by AVG anti-virus system (http://www.grisoft.com). Version: 6.0.459 / Virus Database: 258 - Release Date: 25/2/2003Reguardless of the mechanism, I feel strongly that when a piece of code is finished and works, we shouldn't have to muck with it to make it reusable. A program is a mathematically precise definition of an algorithm. Why go through the process of parameterising it?What you're describing is in essence a typeless programming language, such as javascript.
Mar 08 2003
As a side note: "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:int binarySearch(int[] array, int item) { in { assert(isSorted(array)); } out(result) { assert(result >= -1); if (result >= 0) { assert(array[result] == item); } if (array.length == 0) { assert(result == -1); }These two make assertions made me want the operator "implies"... equivalent to: bit implies(bit lhs, bit rhs) { return !lhs || rhs; } but infix... assert(result >= 0 implies array[result] == item); assert(array.length == 0 implies result == -1); or even assert(result >= 0 => array[result] == item); assert(array.length == 0 => result == -1); Well, might not be that much readable anyway. But a nice convention perhaps. -Antti
Mar 08 2003
"Antti Sykari" <jsykari gamma.hut.fi> escreveu na mensagem news:87bs0lpe6a.fsf hoastest1-8c.hoasnet.inet.fi...As a side note: "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:It'll give us a problem, because implies is lazy. It must have the same semantics as a short-circuit "and" or "or". Perhaps using anonymous functions we can make it without compiler magic: assert(result >= 0 => bit function() {return array[result] == item;}); Ugh! In Smalltalk it works: assert: (result >= 0) => [(array at result) == item]. Some kind of block syntax would be necessary to make things non-intrusive. --- Outgoing mail is certified Virus Free. Checked by AVG anti-virus system (http://www.grisoft.com). Version: 6.0.459 / Virus Database: 258 - Release Date: 25/2/2003int binarySearch(int[] array, int item) { in { assert(isSorted(array)); } out(result) { assert(result >= -1); if (result >= 0) { assert(array[result] == item); } if (array.length == 0) { assert(result == -1); }These two make assertions made me want the operator "implies"... equivalent to: bit implies(bit lhs, bit rhs) { return !lhs || rhs; } but infix... assert(result >= 0 implies array[result] == item); assert(array.length == 0 implies result == -1); or even assert(result >= 0 => array[result] == item); assert(array.length == 0 => result == -1); Well, might not be that much readable anyway. But a nice convention perhaps. -Antti
Mar 08 2003
may seem a little picky but .... "Antti Sykari" <jsykari gamma.hut.fi> wrote in message news:87bs0lpe6a.fsf hoastest1-8c.hoasnet.inet.fi...As a side note: "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:shouldn't this be int binarySearch(int[] array, int item) { in { assert( array !== null ); // get the assert here rather than from isSorted. assert(isSorted(array)); } out(result) { assert(result >= -1); if (array.length == 0) { // check the length before getting array index exception assert(result == -1); } if (result >= 0) { assert( result < array.length ) // get an assert rather than an other possible array index exception from the assert. assert(array[result] == item); } if( result < 0 ) { for( int i =0; i < array.length; i++ ) { if (array[i] == item) { assert( false ) } } } } which reduces to } out(result) { assert( result < array.length ) // get an assert rather than an array index exception from the assert. if (result >= 0) { assert(array[result] == item); }else { assert(result == -1); for( int i =0; i < array.length; i++ ) { if (array[i] == item) { assert( false ) } } } }int binarySearch(int[] array, int item) { in { assert(isSorted(array)); } out(result) { assert(result >= -1); if (result >= 0) { assert(array[result] == item); } if (array.length == 0) { assert(result == -1); }
Mar 08 2003
"Mike Wynn" <mike.wynn l8night.co.uk> escreveu na mensagem news:b4e74v$vut$1 digitaldaemon.com...may seem a little picky but .... "Antti Sykari" <jsykari gamma.hut.fi> wrote in message news:87bs0lpe6a.fsf hoastest1-8c.hoasnet.inet.fi...Yes, you're correct. I just wrote down the contract without trying to make it less verbose. Your version is nicer than mine, as it makes explicit the relation between the valid results and the array length. Can I put your version in the contracts of binarySearch in DTL? --- Outgoing mail is certified Virus Free. Checked by AVG anti-virus system (http://www.grisoft.com). Version: 6.0.459 / Virus Database: 258 - Release Date: 25/2/2003As a side note: "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:shouldn't this be int binarySearch(int[] array, int item) { in { assert( array !== null ); // get the assert here rather than from isSorted. assert(isSorted(array)); } out(result) { assert(result >= -1); if (array.length == 0) { // check the length before getting array index exception assert(result == -1); } if (result >= 0) { assert( result < array.length ) // get an assert rather than an other possible array index exception from the assert. assert(array[result] == item); } if( result < 0 ) { for( int i =0; i < array.length; i++ ) { if (array[i] == item) { assert( false ) } } } } which reduces to } out(result) { assert( result < array.length ) // get an assert rather than an array index exception from the assert. if (result >= 0) { assert(array[result] == item); }else { assert(result == -1); for( int i =0; i < array.length; i++ ) { if (array[i] == item) { assert( false ) } } } }int binarySearch(int[] array, int item) { in { assert(isSorted(array)); } out(result) { assert(result >= -1); if (result >= 0) { assert(array[result] == item); } if (array.length == 0) { assert(result == -1); }
Mar 08 2003
"Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> wrote in message news:b4f83b$1gcc$1 digitaldaemon.com..."Mike Wynn" <mike.wynn l8night.co.uk> escreveu na mensagem news:b4e74v$vut$1 digitaldaemon.com...arraymay seem a little picky but .... "Antti Sykari" <jsykari gamma.hut.fi> wrote in message news:87bs0lpe6a.fsf hoastest1-8c.hoasnet.inet.fi...As a side note: "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:shouldn't this be int binarySearch(int[] array, int item) { in { assert( array !== null ); // get the assert here rather than from isSorted. assert(isSorted(array)); } out(result) { assert(result >= -1); if (array.length == 0) { // check the length before getting array index exception assert(result == -1); } if (result >= 0) { assert( result < array.length ) // get an assert rather than an other possible array index exception from the assert. assert(array[result] == item); } if( result < 0 ) { for( int i =0; i < array.length; i++ ) { if (array[i] == item) { assert( false ) } } } } which reduces to } out(result) { assert( result < array.length ) // get an assert rather than anint binarySearch(int[] array, int item) { in { assert(isSorted(array)); } out(result) { assert(result >= -1); if (result >= 0) { assert(array[result] == item); } if (array.length == 0) { assert(result == -1); }Yes, no problem, the point I was trying to make was your where missing failures and the contract had potential to cause exceptions, two things in my mind that should be avoided, having added those, all I did was reorder to remove the redundant checks. as an aside the reduced form does make explicit the relationship between valid results and array length which had I been thinking clearly I possible would have started with anyway as it is the most important check, I also think that checking failure is correct is also important it may be slow, it could be changed to }else { assert(result == -1); for( int i =0; i < array.length; i++ ) { assert( array[i] != item ); } } I was thinking that the isSorted check could be delayed until the end too } out(result) { bit found = false; assert( result < array.length ); if ( array.length > 0 ) { found = (array[0] == item); for( int i = 1; i < array.length; i++ ) { found |= (array[i] == item); assert( array[i-1] <= array[i] ); } } if (result >= 0) { assert( array[result] == item ); }else { assert((result == -1) && (!found)); } } although that's just a performance idea, as I don't know a way to perform the found check in the in contract and pass it to the out (the only Idea I had was a nested function ) int find( int[] ar, int i ) { bit found = false; int real_find( int[] ar, int i ) in { found = isFoundAssertOrdered(); } out { .... } body { ....... } return real_find( ar, i ); } as I feel that ordered is an input contract and item existing is an output contract I would not delay the checks (are two itterations through the array actually that expensive). anyway I'm jibbering must get some sleep today.index exception from the assert. if (result >= 0) { assert(array[result] == item); }else { assert(result == -1); for( int i =0; i < array.length; i++ ) { if (array[i] == item) { assert( false ) } } } }Yes, you're correct. I just wrote down the contract without trying to make it less verbose. Your version is nicer than mine, as it makes explicit the relation between the valid results and the array length. Can I put your version in the contracts of binarySearch in DTL?
Mar 09 2003
Daniel Yokomiso wrote:T k(T x, U, y) { return x; }Hi, Dan. This is pretty cool. I hadn't realized templates in D were quite that generic. Is "U, y" suppose to be "U y"? Why do we want to ignore y in the body? I'm confused as to why making a function that takes an ignored parameter is useful, but I'll take your word for it. BTW, if you have a good link that describes this, I'd like to read it. There's a subtle reason that D's templates don't give us power I was fishing for in my original post. You've convinced me that D's templates are fully generic, and that they're extremely cool. However, D currently limits how I get to use tempate generated classes. The problem is that I can't inherit functionality simultaneously from a framwork of classes. A simple example data structure that has this problem is a directed graph, because it has mutually recursive classes (Nodes and Edges). We can write great template parameterised graph packages. I can instantiate them. I just can't inherit their functionallity into my own classes in any clean way. This problem seems to have been identified by lots of people, and there are a variety of solutions. "Virtual classes", "template frameworks", and Sather's "include" all solve this problem. I think this is why we've had a lot of discussion lately about "virtual classes", which seem to overcome this limitation. I'm an advocate of Sather's "include" construct over "virtual classes", since using it takes less work for end-users, it offers slightly more power, and it leads to much simpler compilers. It's also easier to understand. Bill
Mar 09 2003
Hi, Comments embedded. In article <3E6B33FF.8060301 viasic.com>, Bill Cox says...Daniel Yokomiso wrote:I'm doing lots of typos recently. I should compile every snippet before posting, not just the most complex. If you google for combinators s k, you'll get some good links, like: http://undergraduate.cs.uwa.edu.au/courses/230.301/lectureNotes/13/15.pdf and http://undergraduate.cs.uwa.edu.au/courses/230.301/lectureNotes/13/16.pdf They seem to be pretty good. You can think of the K combinator as something similar to an if. An if has three parameters, a boolean value, a then part and an else part. Just one of the clauses of the if must be evaluated, the other can be ignored. It can be formulated as: if true then-part else-part = then-part if false then-part else-part = else-part Using lambda calculus a function "f x y" is curried, giving "(f x) y", and if "g = f x" we can use the equivalent "g y", so "if true" should give us something equivalent to the K combinator because it just returns the first parameter. There's a nice article about lambda calculus and perl (the perl part isn't important) at: http://perl.plover.com/lambda/T k(T x, U, y) { return x; }Hi, Dan. This is pretty cool. I hadn't realized templates in D were quite that generic. Is "U, y" suppose to be "U y"? Why do we want to ignore y in the body? I'm confused as to why making a function that takes an ignored parameter is useful, but I'll take your word for it. BTW, if you have a good link that describes this, I'd like to read it.There's a subtle reason that D's templates don't give us power I was fishing for in my original post. You've convinced me that D's templates are fully generic, and that they're extremely cool. However, D currently limits how I get to use tempate generated classes. The problem is that I can't inherit functionality simultaneously from a framwork of classes. A simple example data structure that has this problem is a directed graph, because it has mutually recursive classes (Nodes and Edges). We can write great template parameterised graph packages. I can instantiate them. I just can't inherit their functionallity into my own classes in any clean way. This problem seems to have been identified by lots of people, and there are a variety of solutions. "Virtual classes", "template frameworks", and Sather's "include" all solve this problem. I think this is why we've had a lot of discussion lately about "virtual classes", which seem to overcome this limitation. I'm an advocate of Sather's "include" construct over "virtual classes", since using it takes less work for end-users, it offers slightly more power, and it leads to much simpler compilers. It's also easier to understand.Maybe we should have something like template inheritance. if we allow abstract templates, we could use them for defining both frameworks and parametrically polimorphic structures. Something like: abstract template TGraph(T) { // T is the node content type abstract class Edge { // stuff here } abstract class Node { // stuff here } } template MyGraphFramework(T) : TGraph(T) { class Edge : TGraph.Edge { // overrides TGraph definitions } class Node : TGraph.Node { // overrides TGraph definitions } } alias instance MyGraphFramework(int) graph; graph.Node node = new graph.Node(); This looks like solves the problem using a syntax familiar to D programmers ;-) Also template inheritance is useful when you use templates as a module-like unit of abstraction. I wouldn't mind if modules and templates get merged, some time ago I voted for this.BillBest regards, Daniel Yokomiso.
Mar 10 2003
Daniel Yokomiso wrote:Hi, Comments embedded. In article <3E6B33FF.8060301 viasic.com>, Bill Cox says...This style of template framework looks good to me. I've not implemented templates in a language before, so I'm not very familiar with the hairy problems that arise. If this structure didn't foobar the compiler, I'd be for it. As you've said, the syntax is familiar to D developers, and that is important. I think there is still one significant limitation to this aproach, which this example demonstrates. We're using template inheritance to add graph functionality to classes in MyGraphFramework. Since D is a single-inheritance based language, like Java, that can lead to problems. If graph functionality is just something I want to add to my classes, rather than what defines my classes, I'd be left wanting multiple inheritance. BillDaniel Yokomiso wrote:I'm doing lots of typos recently. I should compile every snippet before posting, not just the most complex. If you google for combinators s k, you'll get some good links, like: http://undergraduate.cs.uwa.edu.au/courses/230.301/lectureNotes/13/15.pdf and http://undergraduate.cs.uwa.edu.au/courses/230.301/lectureNotes/13/16.pdf They seem to be pretty good. You can think of the K combinator as something similar to an if. An if has three parameters, a boolean value, a then part and an else part. Just one of the clauses of the if must be evaluated, the other can be ignored. It can be formulated as: if true then-part else-part = then-part if false then-part else-part = else-part Using lambda calculus a function "f x y" is curried, giving "(f x) y", and if "g = f x" we can use the equivalent "g y", so "if true" should give us something equivalent to the K combinator because it just returns the first parameter. There's a nice article about lambda calculus and perl (the perl part isn't important) at: http://perl.plover.com/lambda/T k(T x, U, y) { return x; }Hi, Dan. This is pretty cool. I hadn't realized templates in D were quite that generic. Is "U, y" suppose to be "U y"? Why do we want to ignore y in the body? I'm confused as to why making a function that takes an ignored parameter is useful, but I'll take your word for it. BTW, if you have a good link that describes this, I'd like to read it.There's a subtle reason that D's templates don't give us power I was fishing for in my original post. You've convinced me that D's templates are fully generic, and that they're extremely cool. However, D currently limits how I get to use tempate generated classes. The problem is that I can't inherit functionality simultaneously from a framwork of classes. A simple example data structure that has this problem is a directed graph, because it has mutually recursive classes (Nodes and Edges). We can write great template parameterised graph packages. I can instantiate them. I just can't inherit their functionallity into my own classes in any clean way. This problem seems to have been identified by lots of people, and there are a variety of solutions. "Virtual classes", "template frameworks", and Sather's "include" all solve this problem. I think this is why we've had a lot of discussion lately about "virtual classes", which seem to overcome this limitation. I'm an advocate of Sather's "include" construct over "virtual classes", since using it takes less work for end-users, it offers slightly more power, and it leads to much simpler compilers. It's also easier to understand.Maybe we should have something like template inheritance. if we allow abstract templates, we could use them for defining both frameworks and parametrically polimorphic structures. Something like: abstract template TGraph(T) { // T is the node content type abstract class Edge { // stuff here } abstract class Node { // stuff here } } template MyGraphFramework(T) : TGraph(T) { class Edge : TGraph.Edge { // overrides TGraph definitions } class Node : TGraph.Node { // overrides TGraph definitions } } alias instance MyGraphFramework(int) graph; graph.Node node = new graph.Node(); This looks like solves the problem using a syntax familiar to D programmers ;-) Also template inheritance is useful when you use templates as a module-like unit of abstraction. I wouldn't mind if modules and templates get merged, some time ago I voted for this.BillBest regards, Daniel Yokomiso.
Mar 10 2003
In article <3E6CA73E.7000103 viasic.com>, Bill Cox says... [snip]This style of template framework looks good to me. I've not implemented templates in a language before, so I'm not very familiar with the hairy problems that arise. If this structure didn't foobar the compiler, I'd be for it. As you've said, the syntax is familiar to D developers, and that is important.Probably it'll be as efficient as regular objects (they may be more optimizable, since templates are supposed to be resolved at compile-time).I think there is still one significant limitation to this aproach, which this example demonstrates. We're using template inheritance to add graph functionality to classes in MyGraphFramework. Since D is a single-inheritance based language, like Java, that can lead to problems. If graph functionality is just something I want to add to my classes, rather than what defines my classes, I'd be left wanting multiple inheritance. BillWalter decided against multiple inheritance due to the mess C++ does. Eiffel has a different model, but has its problems too. AFAIK there are no statically-typed imperative languages with a simple, safe, MI model. Sather is safe, but not simple, with all the abstract, partial, generic classes. But I don't know if there are significant problems in the real world, using extensible templates like these. Can you propose a problem that this solution can't solve, or leads to a awkward solution (e.g. duplicated code, additional levels of indirection)? I'm just trying to "test" this model, not try to say that MI is bad (I like MI, but I admit that it's a hard problem to solve).
Mar 11 2003
Daniel Yokomiso wrote:Walter decided against multiple inheritance due to the mess C++ does. Eiffel has a different model, but has its problems too. AFAIK there are no statically-typed imperative languages with a simple, safe, MI model. Sather is safe, but not simple, with all the abstract, partial, generic classes. But I don't know if there are significant problems in the real world, using extensible templates like these. Can you propose a problem that this solution can't solve, or leads to a awkward solution (e.g. duplicated code, additional levels of indirection)? I'm just trying to "test" this model, not try to say that MI is bad (I like MI, but I admit that it's a hard problem to solve).Hi, Daniel. A simple example would be inheriting linked list functionality into my classes. A traditional linked list adds fields and methods to both parent and child classes, so inheriting these as a template framework of some sort makes sense. Clearly, I wouldn't want to waste my one real inheritance on a simple linked list capability. To make the example more solid, let's imagine I'm trying to implement a simple model of a company organization. I might want a Department class to have linked lists of Employees and Computers: module linkedList; class Parent { Child first; add(Child child) { child.next = first; first = child; } } class Child { Child next; } module sillyCompanyModel; class Department { string name; ... } class Employee { string name; int employeeNumber; ... } class Computer { string osType; ... } inherit linkedList { Parent -> Department; Parent.first -> Department.firstEmployee; Parent.add -> Departement.addEmployee; Child -> Employee; Child.next -> Employee.nextEmployee; } inherit linkedList { Parent -> Department; Parent.first -> Department.firstComputer; Parent.add -> Departement.addComputer; Child -> Computer; Child.next -> Employee.nextComputer; } I used Ocaml's "inherit" keyword, rather than Sather's "include" in this example. You only need multiple inheritance in the sense that Ocaml does it: simple parse tree copying. Also, while Sather has complex inheritance mechanisms like partial classes, it's "include" feature is more like Ocaml's "inherit" in that it is based on simple copying of parse trees. A nice property of this style of module level inheritance is that it puts the mapping in one place. Spreading out the inheritance mapping over a bunch of classes could make it hard to see what's going on if I make extensive use of renameing. In general, I think it makes a lot of sense to specify a mapping when inheriting a module's functionality. It's also important to be able to rename stuff. If we build the mechanism on simple syntax tree copying, we can do multiple inheritance easily. Bill
Mar 12 2003
In article <3E6EFA11.2080903 viasic.com>, Bill Cox says...Daniel Yokomiso wrote:Hi, You can achieve what you want in Eiffel using non-conforming inheritance (a feature of ETL3). You can inherit from some class without forming a subtype relationship (the syntax may be incorrect but the idea is here): class Department is inherit expanded LinkedList(Employee) rename firstElement as firstEmployee addElement as addEmployee end expanded LinkedList(Computer) rename firstElement as firstComputer addElement as addComputer end end class Computer is inherit Link(Computer) rename next as nextComputer end end class Employee is inherit Link(Employee) rename next as nextEmployee end end class LinkedList(T -> Link) is feature firstElement: T addElement(link: T) is do link.next(firstElement); firstElement := link; end end class Link(T) is feature next: T end You can read more about it at http://www.inf.ethz.ch/~meyer/ongoing/. Just a couple of questions. Why don't use a generic LinkedList template and make it an attribute of Department, instead of merging linked list functionality with your classes? IIRC you questioned this because memory locality and additional levels of indirection hurt the performance. Isn't this model flawed? Also, I don't think that everyone besides department should be able to see nextComputer and nextEmployee, so we need some kind of access control feature, like Eiffel export clauses or C++ friendship. There's a third concern about this kind of solution: what will happen when two classes, let them be Department and TechSupport, need to maintain distinct lists of Computer (e.g. department resources and broken computers). With this model we'll force every Computer instance to have n member variables, where n is the number of lists needed, even if they aren't in the lists most of the time. In this example I don't think that include-like functionality is necessary or a good design decision. Otherwise if your problem requires that your classes have next references (e.g. modelling chains) then this kind of solution is good. I'm looking forward an example where include mechanism (i.e. code inheritance without subtyping relationship) is necessary, not just an implementation decision. Best regards, Daniel Yokomiso. "I'm not a lawyer, but I'm pedantic and that's just as good." - D. Gary GradyWalter decided against multiple inheritance due to the mess C++ does. Eiffel has a different model, but has its problems too. AFAIK there are no statically-typed imperative languages with a simple, safe, MI model. Sather is safe, but not simple, with all the abstract, partial, generic classes. But I don't know if there are significant problems in the real world, using extensible templates like these. Can you propose a problem that this solution can't solve, or leads to a awkward solution (e.g. duplicated code, additional levels of indirection)? I'm just trying to "test" this model, not try to say that MI is bad (I like MI, but I admit that it's a hard problem to solve).Hi, Daniel. A simple example would be inheriting linked list functionality into my classes. A traditional linked list adds fields and methods to both parent and child classes, so inheriting these as a template framework of some sort makes sense. Clearly, I wouldn't want to waste my one real inheritance on a simple linked list capability. To make the example more solid, let's imagine I'm trying to implement a simple model of a company organization. I might want a Department class to have linked lists of Employees and Computers: module linkedList; class Parent { Child first; add(Child child) { child.next = first; first = child; } } class Child { Child next; } module sillyCompanyModel; class Department { string name; ... } class Employee { string name; int employeeNumber; ... } class Computer { string osType; ... } inherit linkedList { Parent -> Department; Parent.first -> Department.firstEmployee; Parent.add -> Departement.addEmployee; Child -> Employee; Child.next -> Employee.nextEmployee; } inherit linkedList { Parent -> Department; Parent.first -> Department.firstComputer; Parent.add -> Departement.addComputer; Child -> Computer; Child.next -> Employee.nextComputer; } I used Ocaml's "inherit" keyword, rather than Sather's "include" in this example. You only need multiple inheritance in the sense that Ocaml does it: simple parse tree copying. Also, while Sather has complex inheritance mechanisms like partial classes, it's "include" feature is more like Ocaml's "inherit" in that it is based on simple copying of parse trees. A nice property of this style of module level inheritance is that it puts the mapping in one place. Spreading out the inheritance mapping over a bunch of classes could make it hard to see what's going on if I make extensive use of renameing. In general, I think it makes a lot of sense to specify a mapping when inheriting a module's functionality. It's also important to be able to rename stuff. If we build the mechanism on simple syntax tree copying, we can do multiple inheritance easily. Bill
Mar 12 2003
Daniel Yokomiso wrote:In article <3E6EFA11.2080903 viasic.com>, Bill Cox says...Hi, Daniel. This would do the trick for this example. Eiffel's 'expanded' construct seems to be like Ocaml's "inherit", with the addition of a renaming capability. I'm not recommending that people implement linked lists in this way, although it is sometimes the right solution. It's just a simple example that gives C++ fits. In general, inheriting module funcitonality requires a detailed mapping of constructs. Eiffel's 'expand' mechanism works, but spreads that mapping over multiple classes. It also requires careful retyping of references to objects in the base module, which is error-prone and hard work. Sather's "include" construct does virtually the same thing, just with the entire mapping in one place, and without the need for retyping references. I really think Sather got module (or framework) level inheritance right with the "include" construct. Add template frameworks, and you've really got something powerful. As for a more compelling example that benifits from this capability, any data structures that have embedded directed graphs would be good. I counted several of them in the application I'm currently working on. For example, wires on PC boards are connected with vias using a data structure isomorphic to directed graphs. Template frameworks for the graphs are great, but I still need a powerful framework inheritance mechanism, and one with a renaming capability. BillDaniel Yokomiso wrote:Hi, You can achieve what you want in Eiffel using non-conforming inheritance (a feature of ETL3). You can inherit from some class without forming a subtype relationship (the syntax may be incorrect but the idea is here): class Department is inherit expanded LinkedList(Employee) rename firstElement as firstEmployee addElement as addEmployee end expanded LinkedList(Computer) rename firstElement as firstComputer addElement as addComputer end end class Computer is inherit Link(Computer) rename next as nextComputer end end class Employee is inherit Link(Employee) rename next as nextEmployee end end class LinkedList(T -> Link) is feature firstElement: T addElement(link: T) is do link.next(firstElement); firstElement := link; end end class Link(T) is feature next: T end You can read more about it at http://www.inf.ethz.ch/~meyer/ongoing/. Just a couple of questions. Why don't use a generic LinkedList template and make it an attribute of Department, instead of merging linked list functionality with your classes? IIRC you questioned this because memory locality and additional levels of indirection hurt the performance. Isn't this model flawed? Also, I don't think that everyone besides department should be able to see nextComputer and nextEmployee, so we need some kind of access control feature, like Eiffel export clauses or C++ friendship. There's a third concern about this kind of solution: what will happen when two classes, let them be Department and TechSupport, need to maintain distinct lists of Computer (e.g. department resources and broken computers). With this model we'll force every Computer instance to have n member variables, where n is the number of lists needed, even if they aren't in the lists most of the time. In this example I don't think that include-like functionality is necessary or a good design decision. Otherwise if your problem requires that your classes have next references (e.g. modelling chains) then this kind of solution is good. I'm looking forward an example where include mechanism (i.e. code inheritance without subtyping relationship) is necessary, not just an implementation decision.Walter decided against multiple inheritance due to the mess C++ does. Eiffel has a different model, but has its problems too. AFAIK there are no statically-typed imperative languages with a simple, safe, MI model. Sather is safe, but not simple, with all the abstract, partial, generic classes. But I don't know if there are significant problems in the real world, using extensible templates like these. Can you propose a problem that this solution can't solve, or leads to a awkward solution (e.g. duplicated code, additional levels of indirection)? I'm just trying to "test" this model, not try to say that MI is bad (I like MI, but I admit that it's a hard problem to solve).Hi, Daniel. A simple example would be inheriting linked list functionality into my classes. A traditional linked list adds fields and methods to both parent and child classes, so inheriting these as a template framework of some sort makes sense. Clearly, I wouldn't want to waste my one real inheritance on a simple linked list capability. To make the example more solid, let's imagine I'm trying to implement a simple model of a company organization. I might want a Department class to have linked lists of Employees and Computers: module linkedList; class Parent { Child first; add(Child child) { child.next = first; first = child; } } class Child { Child next; } module sillyCompanyModel; class Department { string name; ... } class Employee { string name; int employeeNumber; ... } class Computer { string osType; ... } inherit linkedList { Parent -> Department; Parent.first -> Department.firstEmployee; Parent.add -> Departement.addEmployee; Child -> Employee; Child.next -> Employee.nextEmployee; } inherit linkedList { Parent -> Department; Parent.first -> Department.firstComputer; Parent.add -> Departement.addComputer; Child -> Computer; Child.next -> Employee.nextComputer; } I used Ocaml's "inherit" keyword, rather than Sather's "include" in this example. You only need multiple inheritance in the sense that Ocaml does it: simple parse tree copying. Also, while Sather has complex inheritance mechanisms like partial classes, it's "include" feature is more like Ocaml's "inherit" in that it is based on simple copying of parse trees. A nice property of this style of module level inheritance is that it puts the mapping in one place. Spreading out the inheritance mapping over a bunch of classes could make it hard to see what's going on if I make extensive use of renameing. In general, I think it makes a lot of sense to specify a mapping when inheriting a module's functionality. It's also important to be able to rename stuff. If we build the mechanism on simple syntax tree copying, we can do multiple inheritance easily. Bill
Mar 12 2003
As with many things in life, it's a matter of choice: what do you want/need to be flexible, and what can be fixed A programming language is in itself a very generic mechanism, because it can be used to implement any (well, almost) program. So the language itself is the most generic template. However, because it is so generic parameterizing this mechanism ("programming") takes a lot of effort (effort measured in amount of typing and thinking). Fixing all parameters reduces the effort needed to reuse a piece of code to almost zero, but at the same time limits the applicability of that code to one specific situation. Hence we have a spectrum from ( everything fixed (no parameters) => low effort to reuse but limited applicability) to ( everything flexible => high effort to reuse but wide applicability ). The low end of the spectrum is e.g. a completed program for which you have no sources, the high end is the language itself So basically everything _is_ a template in a programming language, just the granularity of templatization ("templatizability?") differs
Mar 09 2003
Jeroen van Bemmel wrote:As with many things in life, it's a matter of choice: what do you want/need to be flexible, and what can be fixed A programming language is in itself a very generic mechanism, because it can be used to implement any (well, almost) program. So the language itself is the most generic template. However, because it is so generic parameterizing this mechanism ("programming") takes a lot of effort (effort measured in amount of typing and thinking). Fixing all parameters reduces the effort needed to reuse a piece of code to almost zero, but at the same time limits the applicability of that code to one specific situation. Hence we have a spectrum from ( everything fixed (no parameters) => low effort to reuse but limited applicability) to ( everything flexible => high effort to reuse but wide applicability ). The low end of the spectrum is e.g. a completed program for which you have no sources, the high end is the language itself So basically everything _is_ a template in a programming language, just the granularity of templatization ("templatizability?") differsI agree. Since my original post, I've become convinced that templates are great, and needed in the language, and that putting in the template parameters isn't such a bad thing. The thing that was originally bugging me was my inability to reuse so much code easily. This doesn't seem to be limitation in templates. Patric Down posted a solution he called "template frameworks", and "class frameworks" that solve the problem. It looked like the difference was minor, in that both solved the problem, which lead me to wonder if everything could be thought of as a template, and thus the original post. I now believe that templates fine as they are, and that my original problem, the difficulty reusing code with multiple recursive classes, is solved in various was with "template frameworks", "class frameworks", "virtual classes", Sather's "include" construct, and other mechanisms described in this news group. Inheriting functionality is different than declaring new types with templates. Bill
Mar 09 2003