Newbie questions about Pliant

Newbie questions about Pliant

Can't seem to use Int32

'Failed to compile Int32 ()' in simple program
Message posted by naasking on 2005/04/09 14:22:24
Once again using my simple fibonacci function:

function fibo n -> r
  arg Int32 n r
  if n < 2
    return 1
  else
    return (fibo n-2)   (fibo n-1)

console "out: "   ('convert to string' (fibo 40))   "[lf]"

This fails with "Failed to compile Int32   ()". I'm taking this to mean that 
pliant doesn't know what Int32 is, correct? Is there something missing
from the above, some module I must include?
Message posted by hubert.tonneau on 2005/04/09 14:44:27
If you want your last code to work, you would have to turn it to:

module "/pliant/language/unsafe.pli"

function fibo n -> r
  arg Int32 n r
  if n < 2
    return 1
  else
    return (fibo (cast n-2 Int32))+(fibo (cast n-1 Int32))

console "out: "   (cast (fibo (cast 40 Int32)) Int)   "[lf]"


Also this peticular sequence:

function fibo n -> r
  arg Int32 n r

is the bad result of a 'C' culture,
and reveals not understanding at all what a computer processor is.

Basically, proper programming means that casting to and from fixed size
coding should append only at load and store from memory stage.
So, having something like this is meaningfull:

type AType
  field Int32 a_value

On the other hand, having something like this is just looking for troubles:

function a_functin an_arg
  arg Int32 an_arg


Message posted by naasking on 2005/04/09 14:56:08
>So, having something like this is meaningfull:
>
>type AType
>  field Int32 a_value
>
>On the other hand, having something like this is just looking for troubles:
>
>function a_functin an_arg
>  arg Int32 an_arg

I agree to a certain extent, but the latter function definition is functionally
equivalent to:

function a_functin an_arg
  arg AType an_arg

Surely you don't think that's a bad practice. Defining one's types by the
appropriate bounds/range they must hold/represent makes code self-documenting
and simplifies reasoning about program correctness. If you know AType values 
are bounded by the range of an Int32, then using Int32 ensures the value is 
always within range.
Message posted by hubert.tonneau on 2005/04/09 16:13:04
I get back to what typing is.

From my point of view, typing provides two nice features at once:
. catches some very obvious bugs at compile time
. makes the compiler life much easier

> If you know AType values  are bounded by the range of an Int32, then using
> Int32 ensures the value is always within range.

Here you try to raise the second feature.
I believe it is a bad idea because 'check' instruction is much better for that.

If you know that a value passed as a parameter should be positive, then some
of you will use 'uInt' in the prototype.
From my point of view, it's a bad idea.
You should use 'Int' in the prototype, and add a 'check x>=0' just below.

The reason is that my way of doing things scales much better.
Just imaging that you expect a strictly positive value.

So, in my mind, you should use 'uInt' only if you know that your algorithm
will carry some numbers that would overflow an 'Int' but are ok with an 'uInt'
or that you know that the overflow you will get with an 'uInt' is ok because
you recover through computations somewhere else.
Message posted by naasking on 2005/04/09 21:31:21
>If you know that a value passed as a parameter should be positive, then some
>of you will use 'uInt' in the prototype.
>From my point of view, it's a bad idea.
>You should use 'Int' in the prototype, and add a 'check x>=0' just below.
>
> The reason is that my way of doing things scales much better.
> Just imaging that you expect a strictly positive value.

Hmm... not sure if I agree with this. A function's interface specifies a 
contract to the caller. If a programmer sees that the interface accepts only
positive integers, then they know from the outset that the algorithm only works
for positive integers; using regular integers is thus misleading as they receive 
the mistaken impression that it operates on negative integers as well. It 
doesn't make sense to put those sorts of constraints within the implementation 
if the algorithm only applies to positive ints.

Furthermore, I don't see how adding checks could possibly be more scalable 
than using the inherent properties of the datatype itself. By using a uInt,
the compiled algorithm is just utilizing the raw bits as a native type; using
your approach, additional checks must be inserted to ensure that data conforms 
to the expected input range. Any approaches that eliminate the need for such 
checks would appear to be more scalable and inherently faster.

Could you expand on your reasoning here?
Message posted by hubert.tonneau on 2005/04/10 07:51:10
> Could you expand on your reasoning here?

Sure:

function foo a i0 i1
  arg Array:Int a ; arg Int i0 i1
  check i0>=0 and i1>=i0 and i1<=a:size
  ...

Good library programing (so effective code reuse) requires that you check all
assertions at the beginning of your function to catch miss use.

This is why Pliant insists on having several execution levels, with execution
level 2 effectively executing check instructions.
This is also one very weak point about C/C++.
People tend to use checks internaly, so compile in debugging mode, but when
calling a library from others, the get the optimised library from the Linux
distribution, so checks are desabled.
This is a serious source of hard to track bugs because it makes people
silently use others software outside of the specs, with unpredictable results.

With Pliant, since selecting the execution level is a run time choice and
does not require to distribut double size packages, when something goes wrong,
you can turn on check at every stages at once, so have much better probability
to have the bug cached early, so easily corrected.
Message posted by naasking on 2005/04/10 13:02:53
>function foo a i0 i1
>  arg Array:Int a ; arg Int i0 i1
>  check i0>=0 and i1>=i0 and i1<=a:size
>  ...
>
>Good library programing (so effective code reuse) requires that you check all
>assertions at the beginning of your function to catch miss use.

I agree with this. But, why not just specify i0 and i1 as uInt? That way they
must be positive by definition (so > 0 checks are unnecessary), and the only 
check that matters is then that i1 <= a:size. You assert that the following is
a bad idea:

function foo a i0 i1
  arg Array:Int a ; arg uInt i0 i1
  check i1<=a:size
  ...

but I just don't see why it's any worse than your approach. Sorry for taking 
up your time, by the way; I really appreciate all your help.
Message posted by hubert.tonneau on 2005/04/10 13:37:39
Because good sofware engeneering means getting rid of no use complexity.
You need basic typing because it makes compiler life so much easier,
and even helps reading the program.
You don't need complex typing because it does not seriously improve the
situation, and makes things more complex on large projets: check instruction
is better in this area.

You know, some languages (ADA as far as I remember) even allow you to define in
the data type that an integer is ranging let's say from 100 to 1000.
So writting the data type starts to be lenguish, but you still cannot say
that the number must be odd.
So it's good for the database to deduce that it can store the interger as uInt16,
but still not enough to meet algorithms requirements.

So you get back to what I previously described: fixed size integers are good
in some situation (as an example if you intend to exchange binary entity
through the network), but not for making the program safer.
C did it completely the wrong way. If you want a 32 bits integer, you have
no way to specify it (int on a 32 or 64 bits plateform, but long on a 16 bits
plateform), but on the other hand, people will tend to use it in function
prototypes where it has no value because it will (assuming that you use some
good calling convension as opposed to GCC default one) be passed as a register.

In a consistent computing world, there would be no byte notion.
I mean, the processor register size could vary, and then the memory would be
bit aligned instead of byte aligned, so you would declare something like:
Int 14
or
uInt 14
to specify that you want to load the integer and get unsigned or signed
extension.
In such a situation, using 'Int' for your function prototype can be more
obvious because the 'Int' means the larger value the processor can carry
efficiently without overflowing.
Now, you could argue that using 'uInt' brings one more bit, so it's better,
but then my answer is that it does not significantly change the picture.
If you need larger number, you will rather switch to a 64 bits plateform,
so having 'Int' as the plateform size is still consistent.

Now, the thing is that what is governing hardware design is compatibility,
so we have stupid design from the past, even in brand new architectures such
as the Itanium, and the reason is that it would break software compatibitity
at language level.

So, Pliant could have been born consistent, without the 'Byte' assertion,
I mean addresses are 8 bits aligned instead of single bit aligned,
but it would have been a significant trouble because it's highly likely that
there would be no harware plateform to it, so I decided to carry on the
stupidities.