HomeForumSourceResearchGuide
Sign in to reply to forum posts.
Printing doesn't capture field values

Hello,

I'm doing some academic work with Dana and I'm trying to build an extensible and data type agnostic extension to the CSV functionality in the current version of the library. However, debugging is a challenge due to the following issue:

In "out.print()" statements certain data types do not get printed when the field is selected with its integer representation.

Code:

    Person person = new Person("Sava", 22)
    out.println("Value before type field manipulation = $(person.name), $(person.age)")
    out.println("Value before type field manipulation = $(person:.0), $(person:.1)")
The above code prints:
   Value before type field manipulation = Sava, 22
   Value before type field manipulation = Sava,
The interesting thing is that it somehow doesn't work for numbers, but works for arrays (char arrays e.g.)

Code:

    // Type Construction.
    Type newType = new Type()
    newType.class = Type.DATA
    newType.fields = new Field[2]
    newType.fields[0] = new Field(new Type(Type.INTEGER, 0, 8), "alpha")
    newType.fields[1] = new Field(new Type(Type.DECIMAL, 0, 16), "beta")
    Data newData = new Data() from newType
    // Again, doesn't compile.
    // out.println("newData: $(newData.alpha), $(newData.beta)")
    out.println("newData: $(newData:.0), $(newData:.1)\n")
    newData:.0 = 10
    newData:.1 = -1.2
    out.println("newData: $(newData:.0), $(newData:.1)\n")
The above code prints:

    newData: ,
    newData: 
    ,

I have tried many things like direct type field assignment and getting the data from different sources, but this makes it exceedingly hard to debug code that aims to be dynamic.

I'm open to suggestions, thanks!

Sava

Hi Sava :-)

It sounds like you're making a neat little utility, maybe you can share when you're done.

I think you're stuck because of how Dana uses associative functions in the compiler for string construction. This makes it look like Dana is doing some dynamic type inference when it's unpacking string variables, but it really isn't.

Whenever I'm doing dynamic type stuff I've found it useful to keep reminding myself that Dana is a statically typed language. The dynamic type manipulation primitives are pretty powerful, and for me can easily make you think that Dana has general dynamic type support at a language level when it really doesn't.

I'll start with a quick reminder on how string variables work because I think it helps to explain your issue.

Let's say we have this program:

component provides App requires io.Output out {
	
	int App:main(AppParam params[])
		{
		char str[] = "jess"

		out.println("my name is: $str")
		
		return 0
		}
	
	}

What the compiler is really doing is converting the output line string to:

new char[]("my name is: ", str)

(I'm assuming here you know how Dana array concatenation works)

From this you can more clearly see that your string variables must be of type char[] (or a compatible type) for this to work at all.

If you need a print something that isn't a character array, you must have access to a function which can convert it to a character array.

For an integer, you need do to e.g.:

component provides App requires io.Output out, data.IntUtil iu {
	
	int App:main(AppParam params[])
		{
		char str[] = "jess"
		int age = 3

		out.println("my name is: $str and my age is $(iu.makeString(age))")
		
		return 0
		}
	
	}

The IntUtil.makeString() function converts to a char[] so this is OK.

Now, Dana has a feature it calls associative functions. This is where you can call a function on a primitive type or data type, if you have access to an object which has a function with a single parameter of that type.

So within the above code it would be legal to do:

char strB[] = age.makeString()

So here the compiler searches for the first named required interface which has a function called makeString() taking a single parameter of type int.

The compiler uses this technique to make string building easier. We can do this:

component provides App requires io.Output out, data.IntUtil iu {
	
	int App:main(AppParam params[])
		{
		char str[] = "jess"
		int age = 3

		out.println("my name is: $str and my age is $age")
		
		return 0
		}
	
	}

And the compiler is going to search for a function called makeString() on a named required interface which takes a single int parameter. The compiler is then converting this to:

new char[]("my name is: ", str, " and my age is: ", iu.makeString(age))

Again, everything here is a char[] so this is OK. This associative function behaviour for string variables can easily make a programmer think that Dana is doing some kind of runtime type inference, but it isn't - it's a statically-typed language. A few of my colleagues made this bad assumption before :-)

If you give a dynamically-accessed field as a string variable, Dana has no way to understand at compile-time what it's supposed to do with that field in order to turn it into a char[], so as far as I understand the compiler does nothing at all here (and couldn't do anything even if it wanted to) on the assumption the programmer knows what they're doing and the dynamic field access will already give a value of char[] type.

So, to get Dana to print out a dynamically-accessed field you have to first put it into a statically typed variable of an appropriate type, or otherwise create some way to convert that field to a char[] before giving it as a string variable.

In all of your above examples the Dana compiler will be assuming your variables are already of type char[], and the Dana runtime will be trying to interpret them as a char[], and is probably just interpreting them as a null / empty string.

Phew, what a long answer, I hope it makes a bit of sense!

jess

Hello again, jess,

Whoa, thank you so much for taking the time, this makes perfect sense! I would've never thought to separate compile time knowledge from the fact that dynamic typing is dynamic (big surprise there :) )

One slight issue there or rather clarification needed. Dana has the type information at compile time, which is the following:

 data Person
 {
       char name[]
       int age
  }
Now why can't Dana deduce that person.1 is of type "int"?

I mean it has done it perfectly fine with this line of code:

   out.println("Value before typefield manipulation = $(person.name), $(person.age)")

Output:

   "Value before typefield manipulation = Sava, 22"

But it hasn't done it with this line of code:

    out.println("Value before typefield manipulation = $(person:.0), $(person:.1)")

Output:

   "Value before typefield manipulation = Sava, "
After all, doesn't "person:.age" get translated to "person:.1" anyways?

I think my confusion might be coming from here:

"Note that TypeField is a type synonym for int, so you can apply any arithmetic operators to a TypeField or otherwise treat them as integers for all purposes. You can also access fields using a literal integer, for example p:.0."

So, am I correct to assume that "age" is not the same as person.[age] - which is actually the integer, i.e. the type field.

Many thanks,

Sava

Hi Sava :-)

I'm not a total expert here, but thinking about general language and compiler theory I imagine this comes down to consistency and the unsolvability of static analysis.

In your example, it looks like the compiler should be able to resolve:

person:.1

...to an appropriate type, but in many cases this would be impossible and so you'd get inconsistent behaviour from the :. operator. I personally think this would be undesirable.

I'll try to dig into why this true. First let's note that person.age and person:.1 are clearly two different operators. The dot operator is a static type member access. Here the compiler is resolving a named field against the type which is possible because the compiler knows what type that variable is. The :. operator is a dynamic field access, which is delegated to the runtime to resolve.

I don't know exactly how this works, but we can imagine that the :. operator in Dana, as far as the compiler is concerned, has a return type of unknown or something similar. Because its return type is unknown, the compiler cannot therefore do the smart associative function logic which allows it to convert string variables to makeString() calls. The compiler wouldn't known which makeString() function to use (the int one? the dec one? etc.).

So your question, I think, is "why can't the compiler resolve unknown into a type?".

If I work from your simple example, it looks like maybe it should be able to:

data Person
{
	char name[]
	int age
}

component provides App requires io.Output out, data.IntUtil iu {
	
	int App:main(AppParam params[]) {
		Person p = new Person("jj", 5)
		out.println("$(p:.1)")

		return 0
	}
	
}

...from a quick visual inspection here we can tell that we're using a constant of 1 for our :.1 access, and we're doing it on a variable of type Person, so in this case the compiler probably could figure this out. But to do this the compiler is going to have to already use some special casing.

Here's a very similar situation in which the compiler definitely cannot resolve the type:

data Person
{
	char name[]
	int age
}

component provides App requires io.Output out, data.IntUtil iu, util.Random, time.Calendar cal {
	
	int App:main(AppParam params[]) {
		Random rand = new Random()
		rand.setSeed(cal.getMS())
		Person p = new Person("jj", 5)
		out.println("$(p:.(rand.getInt(2)))")

		return 0
	}
	
}

...in this case even if the compiler were to try to run the code, which is more or less what happens in advanced static analysis, the result is not deterministic, so it's not always going to be the same return type. Even without the randomness we can create similar scenarios by including conditional branch statements, putting our variable into a generic Data variable, or passing that generic Data value into a function in a different component with complex loop/branch logic, etc.

Because the compiler can't resolve types in many cases for the :. operator, it seems to take the approach of never trying to do so even in the edge cases where it's possible. I think that consistency of logic is probably what most programmers would want, or maybe that's just me :)

jess

[edited by forum mods to fix a username association error, thanks]

Hello jess,

That makes perfect sense. Once again, thank you for taking the time to explain!

Sava