Advertisement

True Variant Datatype

Started by September 29, 2011 02:15 PM
7 comments, last by _orm_ 13 years, 1 month ago
I mentioned in another thread that I was working on a Variant datatype and I think I am on to something. At the moment, it is extremely buggy and to be honest, I am a little embarassed by my ham-handed implementation. It is based off of a bit-stream class I found on code-project a while ago, a link to which I do not have at the moment. It has worked fairly well. The bit stream allows me to represent both primitive datatypes and strings , and then given whatever it is based off of, can run the proper conversions. Of course, any variant type will cause a hit to performance, but I think that a true variant type is a worthwile thing to have in any scripting language, as there are a multitude of uses for them.

Please bear in mind that this currently has a few things in it that are specific to my game engine, but they are mostly utility functions.

What currently works:

  • String concantenation when both vars represent strings.
  • Arithmetic operations between variants regardless whether they represent a string or a primitive.
  • All assignment operators.What does not work:

    • Arithmetic assignment operators
    • The string constructor (for who knows what reason).Some sample code from my test-case in my game engine.

      void ConsCmd_VarTest( Console@ c, const string@[]@ args )
      {
      if( args.length() < 2 )
      {
      c.Print( "Not enough arguments. Need at leasst two numbers. Note that they can have decimals in them.");
      return;
      }

      c.Print("Testing the 'var' datatype.");

      var arg1 = args[0],
      arg2 = args[1];
      c.Print(arg1.asString()+" + "+arg2.asString()+" = "+var(arg1+arg2).asString());
      c.Print(arg1.asString()+" - "+arg2.asString()+" = "+var(arg1-arg2).asString());
      c.Print(arg1.asString()+" * "+arg2.asString()+" = "+var(arg1*arg2).asString());
      c.Print(arg1.asString()+" / "+arg2.asString()+" = "+var(arg1/arg2).asString());

      var arg3 = arg1;

      var testconstructor( 10 );
      arg3 += var("10");
      c.Print(arg1.asString()+" += 10 -> "+arg3.asString() );
      }


I don't think variants are useful at all. I prefer my scripting languages to be implicitly and strongly typed. Instead of doing all the work to create a variant type that could contain anything at any given moment, just have your scripting language create the proper variable type the first time it's assigned a value. To make things easier, you could implicitly convert values of one type to another when using them in expressions, but, to be honest, I find this behavior to be a good way to introduce subtle bugs.



// Creation of a numeric variable
var a = 10;

// Creation of a string variable
var b = "20";

// This can't work because the compiler can't determine what its datatype should be.
var c;

// You could implicitly use type conversion here to convert b into a numeric for the arithmetic
// operation, but how does the compiler actually know that this expression is intended
// to be an arithmetic expression? If your language uses the plus sign for string
// concatenation, then it's just as feasible that the script author wanted the string "1020" back
// instead of the numeric value 30. This is why implicit type conversion can be problematic
// and I recommend against it.
var c = a + b;

Advertisement
That is actually a good point on implicit conversion. Perhaps I should just return an invalid value in those cases? The thing is though that concantenation will only occur when both vars represent a string. Then again, there really is no real usage for adding a string representing a double to a double or an int.
Here's a situation where you might wind up doing arithmetic on a string and a numeric value by accident:


var moneyInMyBankAccount = 1000;

// Variants are tricksie bastards
var howMany = PromptForUserInput("Please enter how many foos you'd like to sell.");
var costPerFoo = 10.5;

var total = costPerFoo * howMany;

moneyInMyBankAccount += total;

// moneyInMyBankAccount now = "100021"! Yea!

That is actually a good point on implicit conversion. Perhaps I should just return an invalid value in those cases? The thing is though that concantenation will only occur when both vars represent a string. Then again, there really is no real usage for adding a string representing a double to a double or an int.


Errors are better. An invalid value can go unnoticed and bubble up in your code where it becomes difficult to determine what the source of the invalid data is. It's much harder to ignore an error.
Errors would be better. I'll do that.
Advertisement
Really though, variants are more trouble than they're worth. It is almost always a bug when you change a variable's type. There really is no good reason that a variable should be a string value on line 10, but then the same variable is used to hold a numeric value on line 20. A strongly typed system with inferred typing based on first assignment gains you all the convenience of variants with none of the pitfalls of being able to change a variable's type at any time. Those bugs are so hard to track down that the programmer eventually starts explicitly converting using "asXYZType()" every time they access the variant, which completely undermines the entire reason why variants were created in the first place. It makes your code look ugly, potentially slower, and forces you to acknowledge its type everywhere you use it. You'll start using the dreaded Hungarian notation when naming variables just so you can remember what type they were supposed to be. Then anyone who ever looks at your code later will hate you and possibly contemplate removing you from the gene pool to prevent contamination of the future of the human race.
However, I have found variants to be extremely useful when it comes to object factories. In my current setup, I have factories that take arrays of variants and then use them as paramaters for constructing game objects.

However, I guess a better way to do this would be to define a constructor interface that gets passed to a concrete factory and provides construction instructions.



class Factory
{
Body@ Construct(IFactoryConstructor@ cons)
{
return cons.Construct();
}
};

interface IFactoryConstructor
{
Body@ Construct();
};



class Brute_Constructor : IFactoryConstructor
{
Brute_Constructor( int strength, string@ resource )
{
this.strength = strength;
@this.resource = resource;
}
Body@ Construct()
{
return Body_Brute( strength, resource );
}
private int strength;
private string@ resource;
};



I get the feeling that there is an infinitely better way to do this.

I feel that variants are useful for generic containers where you don't know exactly what will be put in them at the design time, e.g. the session object in web development, or data base access objects, or even generic properties in a game engine.

I would have to agree that variants as a variable type is not really that useful though, for the reasons given by smr. Still, I would very much like to be able to allow the application developer to properly register their own variant types, just as _orm_ has done.

I'll have a closer look at your implementation _orm_. Anything in particular that you feel is missing in the AngelScript library to let you accomplish what you want?

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I can't really think of anything, and I actually realized that variants are actually pretty silly and unnecessary in the scheme of things.

This topic is closed to new replies.

Advertisement