From: rubyhacker@... Date: 2005-09-10T05:31:31+09:00 Subject: Re: KirbyBase Well, I didn't get to make this post last night. :-/ Things such as work, food, and sleep really interfere with coding and posting. I'll make a few comments now and flesh them out later. As I said, I'm an Object Guy and Jamey is a Database Guy. What does that mean? I was working on an app and using KirbyBase. Because of the work Jamey had done recently, creating objects from the database records was much easier. But I noticed something. My fields were all simple types. (After all, that's how databases work.) But I was taking my smaller objects and using them to build bigger objects. In most cases, I was storing a key into another table. So I would look up a record, and four of its fields would actually be keys into other tables. I'd then look up those four. I'd then embed my four little objects inside an object corresponding to the original row. I had a feeling that I was doing too much work, that some of this could be done for me. My thoughts were like this: OK, we have primitive types such as integer and string. From OOP, I have a habit of thinking of classes as abstract types (see Bertrand Meyer -- I think). So if I want an object to have a member which is (e.g.) a Person, why can't I treat Person just like String and Integer? In Ruby, we don't think of variables or attributes as being typed. I make an exception in the case of retrieving objects from databases, because I am very used to the idea of fields having types. (Although someday I will comment on that also.) After all, even in Ruby, we often think to ourselves, "This member will be a string" -- although if we are not careful, we might assign any other thing to it. But I won't go off on too much of a tangent here. So here I am. I'm thinking: Treat simple types (simple fields) the way we already do. We store a type for each field. Let's store the complex types the same way... but in the actual table, just store a key into another table. That requires the table name and key field name to be specified somehow, of course. My take: Let's get slightly railsy and assume the class name lowercased is the table name (e.g. Person => person.tbl). That means we only have to specify the field name explicitly (and I've been thinking of a way to eliminate that also, by optionally establishing a known "key field" for a table). I was thinking "composite objects." I was thinking "embedding simpler objects inside more complex ones." I was thinking "this is how I can easily and transparently marshal my objects." When I mentioned this to Jamey, he said in effect, "Oh, you want one- to-one links between database tables." That's Mr. Database talking to Mr. Object. He was quite right, of course, but it confused me to think that way. It was as if my mechanic said, "Oh, you're trying to use this solenoid/starter assembly to initiate sequential explosions inside your cylinders with the aid of these spark plugs." To which I would say, "Uh, I just want to start my car." (As it happens, I really don't know much about cars or engines. That's a hardware problem.) So Mr. Database says, "Let's implement one-to-many also." (And he may have done others also, I don't have a KB install in front of me.) And my reaction is, well, OK, fine. But first of all, I don't personally see a need for it. (Of course, I might discover a need next week.) Second of all, I am not sure what analogy that would have in object terms. I suppose it would in effect be embedding an array where all the objects are objects derived from the rows of the child class. This bothers me a *little* because all the elements of the array would be of the same type. If you ever assign something else to that array, you'll get an error when you try to insert. Well, there's already some clash between Ruby and the database concept, because db fields have types, and attributes (as variables) don't have types. But this seemed like going a step farther to me -- heightening the clash by creating an array that has to be homogeneous, like a Pascal array. Third of all, if you start to have "one-to-one" and "one-to-many" links and such, you start having to distinguish between them. This makes table instantiation, even in the default case, just a tiny bit more difficult. It's creeping complexity. It's very small, but it adds up (and sometimes multiplies if you're not careful, the way probabilities multiply). See, there is a sort of "conservation of complexity" in any system. If I knew more information theory, I could express it better. Take data compression as an example. I can write a simple, dumb compression program that will compress most text by a factor of 2 or better. But that's not very good. An extremely complex algorithm (a much better one, that is) might compress by a factor of 10 or more. But the net information is the same, don't you see? We have moved the information from the data into the program. We have a very high-entropy resultant data file and a very low-entropy program. I have increased the data's entropy at the cost of increasing the entropy of the software. (My freezer keeps things very cold, but there is hot air coming out the back of the fridge.) I can compress the Declaration of Independence into a single byte with a sophisticated enough program, like the one shown here in part: if text == 'a' puts "When in the course of human events, it becomes necessary" # lines omitted puts "our lives, our fortunes, and our sacred honor." end You think I'm kidding, and I am. But I'm also not kidding at all. If I write a C program in 600 lines, I can probably write it in Ruby in 100 lines. Where did the complexity go? It left my program and went into the interpreter. That is where it belongs -- under the hood. Information hiding is how humans manage complexity. The concept of the black box is a greater human invention than the discovery of fire or the wheel. Now, suppose I specify a table with KirbyBase that has two simple fields, an integer and a string. We do it something like this (I probably am forgetting the method name): create_table(:mytablename, :alpha, :Integer, :beta, :String) Fine and dandy, nice and simple. Now suppose we add another field -- I'll abandon the Greek alphabet and call it "boss" which will be a Person object. In an absolutely perfect world, this would be "just another type." The software would read my mind and do exactly the right thing, and all I would type is: create_table(:mytablename, :alpha, :Integer, :beta, :String, :boss, :Person) But this leaves some unanswered questions. Here are the questions and my answers -- pardon me for personifying the database software: 1. "What table will I get this 'person' stuff from?" (Just derive the table name from the class name.) 2. "What field in the child table will I use as a key?" (Hmm, maybe I'll have to tell you this one. It might be cool to be able to designate a key field, though.) 3. "What type is that key field?" (Given the name, you can find it in the child table's information.) 4. "What if Person itself is another complex object?" (Relax, just apply the same algorithm recursively. Worst that can happen is there is cyclic data, you'll go into an infinite loop, and I'll have to kill you.) Now, Jamey's first attempt at this had me writing code in the MYtablename#kb_create method, calling a method named one_to_one_link or some such. It felt very manual to me, like I was hotwiring my car. "What's the big deal?" says the mechanic. "All you do is, you grab these two wires, not these two, and touch them together for a little while, not too long, and..." And I reply: "I don't want to reach for the wires and grab them, I don't want to memorize the colors, I don't want to estimate the time interval, and I don't want to see wires dangling. Keep that under the hood." In one iteration, perhaps not the present one, a one-to-many relationship was stored inside its parent object as a KBResultSet. Ugly to me. If we must have one-to-many, let it be just an array. I don't want my choice of database to intrude into my objects any more than necessary. Given the right glue, *any* database should be usable for my objects. KirbyBase is implementing this glue pretty well so far, INSIDE the db software where it belongs. In the one-to-one case (present iteration), the type information is specified as an array. This was my suggestion, and in general I like it. But it is too complex. We may not reach the ideal shown above, but let's strive toward it. When I have Jamey's email in front of me, I'll tell you exactly what I mean. I was also disturbed a little to see that the class had to inherit from a KB class (I forget which one). It's hard to articulate why this bothers me. It's one more thing to remember, one more thing to do, and it's not a totally clean separation. Also, a word or two about the kb_create method (called automatically when a row is retrieved, to turn it into an object). In a perfect world, we should "normally" not have to define one at all. I'm thinking of ways to make it usually unnecessary. (In the case of "calculated fields," this might be the very best place to put them, however.) Hope this helps clarify things a little. And I'm glad to see this discussion happening in public, just in case I give Jamey some really stupid advice. :) I'd hate to persuade him into a bad design. Cheers, Hal