All I want For Christmas...

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Moderators General, Prelates

All I want For Christmas...

Postby Yakk » Mon Apr 30, 2007 8:43 pm UTC

in a programming language.

Multi-type return functions.

Code: Select all
{double, string, int} ParseValue(string);

void ReadConfig(string value) {
  switch(ParseValue(value)) {
    case double f: {
      // we have a double named f that was returned from ParseValue() here
    } break;
    case string s: {
      // this case is reached if ParseValue returns a string
      // the return value is stored in s, naturally
    } break;
   case int i: {
     // guess?
   } break;
  }
}


You can do this with exception syntax in a number of languages, and do it with manual type safety in most languages.

Even better syntax might be:
Code: Select all
auto result =  ParseValue(value);

which would basically do the above switch/case statement, but with the remaining body of code being the same.

Toss in the ability to do an explicit switch on the type of the variable...

...

Full-scale compile-time code generation, where you can build functions from primitive statements hooked together.

...

Incomplete functions with "loose" external references that can be hooked up by metaprogramming. Ie, a function might have a loose variable called "chicken" that needs to be bound before it can be used. You could place it into a class, and hook "chicken" up to a class variable, or you could wrap it with another function that accepts "chicken" as a parameter.

...

Multiple parallel type systems. I want to be able to decorate something with physics-style dimensional notation, etc, but still be the same underlying computational type. Units with restricted conversion between them. Algebras with limited valid symbols. This should work at both compile and run time (often dimensional analysis can be checked before you actually execute a formula or algorithm).

...

Compile and run-time type checking. I want to be able to request compile-time guaranteed type-checking in my code, and if the compiler cannot guarantee it, it would generate an error. I also want run-time type checking when it is a better option, both automatic and manual.

...

Named arguements. Everywhere.

...

Compile-time type-safe varargs. Run-time type-safeable varargs.

...

The ability to build expression tree syntax with minimal effort. The ability to include compile-time guarantees in my expression tree syntax.

...

classes. classes of classes. classes of classes of classes. Virtual classes. Virtual classes of classes. Virtual classes of virtual classes. Classes of virtual classes of classes.

method-level polymorphism. Run-time polymorphism. Compile-time-locked polymorphism. duck-type polymorphism.

...

In essence, I want leverage. I want my language to let me rewrite large chunks of the language that is used to write the language to write my code. I want to be able to write code that writes code that writes code, and have the debugger be able to point at the code that wrote the code that wrote the code that I am running, and say "unhandled division by zero here".

That is what I want for christmas.

Anyone else?
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby musicinmybrain » Tue May 01, 2007 12:15 am UTC

A lot of this is pretty natural in Python, but I get the feeling you're looking for a compiled language. Also, I admit I skimmed and didn't understand all of your wishes.

Still, in case you find it interesting:

Multi-type return functions...


Code: Select all
def ParseValue(string):
  try:
    return int(string)
  except:
    pass
  try:
    return float(string)
  except:
    pass
  return string

parsed = ParseValue(your_value_here)

# parsed is now a float, int, or str
# now if you wish you can "switch" (Python doesn't have a
# true switch statement) on the type of parsed

if type(parsed) == float:
  # do something with the float
elif type(parsed) == str:
  # do something with the string
elif type(parsed) == int:
  # do something with the int


Full-scale compile-time code generation, where you can build functions from primitive statements hooked together.


I'm not sure if that's unclear or just over my head.

Incomplete functions with "loose" external references that can be hooked up by metaprogramming. Ie, a function might have a loose variable called "chicken" that needs to be bound before it can be used. You could place it into a class, and hook "chicken" up to a class variable, or you could wrap it with another function that accepts "chicken" as a parameter.


So these loose external reference you refer to, they're not arguments, right? Why not just use an argument instead?

Multiple parallel type systems. I want to be able to decorate something with physics-style dimensional notation, etc, but still be the same underlying computational type. Units with restricted conversion between them. Algebras with limited valid symbols. This should work at both compile and run time (often dimensional analysis can be checked before you actually execute a formula or algorithm).


Python lets you derive classes from built-in types. The dirty work of implenting the behavior you want is up to you, of course. You can also overload all the operators I can think of in Python in a pretty precise way.

Compile and run-time type checking. I want to be able to request compile-time guaranteed type-checking in my code, and if the compiler cannot guarantee it, it would generate an error. I also want run-time type checking when it is a better option, both automatic and manual.


I'm not sure exactly what you want here, but then again I'm feeling too lazy to think about it very hard. And then again, Python is interpreted so there is no compile-time, only run-time.

Named arguements. Everywhere.


Some excerpts from the Python docs:

Code: Select all
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print "-- This parrot wouldn't", action,
    print "if you put", voltage, "volts through it."
    print "-- Lovely plumage, the", type
    print "-- It's", state, "!"


Valid calls:

Code: Select all
parrot(1000)
parrot(action = 'VOOOOOM', voltage = 1000000)
parrot('a thousand', state = 'pushing up the daisies')
parrot('a million', 'bereft of life', 'jump')


Compile-time type-safe varargs. Run-time type-safeable varargs


Well, with Python there's only run-time, of course, but here's how they work.

Code: Select all
def cheeseshop(kind, *arguments, **keywords):
    print "-- Do you have any", kind, '?'
    print "-- I'm sorry, we're all out of", kind
    for arg in arguments: print arg
    print '-'*40
    keys = keywords.keys()
    keys.sort()
    for kw in keys: print kw, ':', keywords[kw]


Sample call:

Code: Select all
cheeseshop('Limburger', "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           client='John Cleese',
           shopkeeper='Michael Palin',
           sketch='Cheese Shop Sketch')


Result:

Code: Select all
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch


Of course, you could use the built-in type() function to check types at any point.

The ability to build expression tree syntax with minimal effort. The ability to include compile-time guarantees in my expression tree syntax.


I have altogether no idea what you're talking about. Perhaps somebody more computer sciency and less electrical engineeringy would.

classes. classes of classes. classes of classes of classes. Virtual classes. Virtual classes of classes. Virtual classes of virtual classes. Classes of virtual classes of classes.

method-level polymorphism. Run-time polymorphism. Compile-time-locked polymorphism. duck-type polymorphism.


Virtual classes of virtually virtual classes of virtually virtually virtually virtual classes of virtual classes of virtually virtual classes! Objects that polymorph polymorphically! duck-type, weasel-type, AMC Gremlin-type, and polymorphic-type polymorphism! Code that doesn't actually exist, but materializes out of the aether!

Sorry, I got carried away.

Anyway, that's not an early Christmas, and Python is an interpreted language, but maybe there were a few things in there you hadn't seen before. Or maybe not. I just like Python.
+ + + + + + + + [ > + + + + [ > + + > + + + > + + + > + < < < < - ] > + > - > + > > + [ < ] < - ] > > . > > - - - . + + + + + + + . . + + + . > . < < - . > . + + + . - - - - - - . - - - - - - - - . > + . > + + .
User avatar
musicinmybrain
 
Posts: 96
Joined: Sun Dec 31, 2006 2:50 am UTC
Location: Greensboro, NC

Postby Yakk » Tue May 01, 2007 1:55 am UTC

I like python too. But it lacks the static type-checking and compile-time code generation toys that I want.

Full-scale compile-time code generation, where you can build functions from primitive statements hooked together.


I want to be able to manipulate code and hook it together like

So these loose external reference you refer to, they're not arguments, right? Why not just use an argument instead?


I want to be able to bind them where I want them, and for the function to be invalid (and uncallable) until they are bound. And I want my compiler to tell me when I fail these requirements. . .

Yes, you can do this pseudo-manually at run time.

And then again, Python is interpreted so there is no compile-time, only run-time.


An interprited langauge can have a compile-time phase in which non-trivial code can be executed. Most don't.

Well, with Python there's only run-time, of course, but here's how they work.


*nod*, very much like C++0x's incoming compile-time version. Except C++0x uses ... instead of stars.

I have altogether no idea what you're talking about. Perhaps somebody more computer sciency and less electrical engineeringy would.


Expression trees are a form of lazy evaluation, or building application specific languages within the code.

a+b+c would produce the expression tree, which could be walked and evaluated at runtime.

Imagine a "real" library where operations on the real gets build into an expression tree. Finally, when you want to evaluate it, you would specify the precision.

This would cause code to execute that would query each of the operations, asking how expensive precision is in each one, and generating a result with a known amount of error in the end.

Similar trickses for building parsing grammers, etc, can be done. It is rather neat.

Anyway, that's not an early Christmas, and Python is an interpreted language, but maybe there were a few things in there you hadn't seen before. Or maybe not. I just like Python.


I like it to. :)

It just doesn't give me all of the other features I like.
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby warriorness » Tue May 01, 2007 3:51 am UTC

I'm not the best Java coder in the world (i.e. wasn't able to make an example class) but couldn't you do this in Java with reflection and/or generics? Just make a method that returns an Object, and do a bit of messing around with class casting.
Iluvatar wrote:Love: Gimme the frickin' API.
yy2bggggs, on Fischer Random chess wrote:Hmmm.... I wonder how how a hypermodern approach would work
User avatar
warriorness
Huge Fucking-Lazer
 
Posts: 1610
Joined: Thu Dec 28, 2006 10:33 am UTC
Location: CMU, Pittsburgh, PA, USA

Postby EvanED » Tue May 01, 2007 4:02 am UTC

warriorness wrote:I'm not the best Java coder in the world (i.e. wasn't able to make an example class) but couldn't you do this in Java with reflection and/or generics? Just make a method that returns an Object, and do a bit of messing around with class casting.


A lot of what he wants is static stuff; anything with reflection at least is going to be wholly runtime.
EvanED
 
Posts: 3781
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI

Postby warriorness » Tue May 01, 2007 4:13 am UTC

EvanED wrote:
warriorness wrote:I'm not the best Java coder in the world (i.e. wasn't able to make an example class) but couldn't you do this in Java with reflection and/or generics? Just make a method that returns an Object, and do a bit of messing around with class casting.


A lot of what he wants is static stuff; anything with reflection at least is going to be wholly runtime.


Well, it'd be pretty hard to do it compile-time:

Code: Select all
public static void main(String[] args)
{
     (String, int, boolean) a = getSomething(0);
     a.someMemberFunction(...);
}

public (String, int, boolean) getSomething(int what)
{
     if (what == 0) return new String("hello");
     else if (what == 1) return 5;
     else return false;
}


How do you know whether someMemberFunction is present or not? The best you could do, keeping all the binding at compile-time, would be to say Object a = getSomething(0); instead, and then you'd only get the functionality of an Object; you wouldn't have any String-specific member functions. And in that case, it'd be just as effective to declare the method as public Object getSomething(...).

Runtime binding seems like the only way to approach this problem.
Iluvatar wrote:Love: Gimme the frickin' API.
yy2bggggs, on Fischer Random chess wrote:Hmmm.... I wonder how how a hypermodern approach would work
User avatar
warriorness
Huge Fucking-Lazer
 
Posts: 1610
Joined: Thu Dec 28, 2006 10:33 am UTC
Location: CMU, Pittsburgh, PA, USA

Postby Yakk » Tue May 01, 2007 1:31 pm UTC

I gave a few syntaxes on how to do it at compile-time. Heck, you can currently do it at compile-time using exceptions and ugly syntax.

Code: Select all
try {
  call_many_return_func();
except (bool a)
{
}
except (int i)
{
}
except (string s)
{
}


The above, like go-to, is a syntax that doesn't express what I want concisely.

The kind of syntax I want is either:
Code: Select all
switch_type (many_return_function()) {
  case {int} a:
    // code
  case {double} b:
    // code
  case {string} s:
    // code
}

where the type of the variable you have access to is guaranteed to be the type of variable that exists.

Alternatively:
Code: Select all
auto_type return_value = many_return_function();

where return_value can be one of many types: in effect, that that point the code forks into N different implementations based on what the type many_return_function() returned.

Operations on return_value that ducktype legally on all of the possible return values work.

Operations on return_value that don't ducktype legally are a compile-time error.

With template type-magic, switch_type like syntax, and overridden functions, this lets you write pretty damn generic code that does the right thing.

And all of the above can be solved at compile time.

Note that I didn't intend this thread to be a discussion of my christmas language wishes -- don't you guys have language features you want?
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby evilbeanfiend » Tue May 01, 2007 1:34 pm UTC

seems like you want something similar boost::any with some additional concept checking.
in ur beanz makin u eveel
User avatar
evilbeanfiend
 
Posts: 2650
Joined: Tue Mar 13, 2007 7:05 am UTC
Location: the old world

Re: All I want For Christmas...

Postby tendays » Tue May 01, 2007 2:43 pm UTC

Yakk wrote:Multi-type return functions.


You mean type disjunction? I know some languages permit writing things like T1|T2 to mean "T1 or T2" (where Ti are types).
So for instance x:int|string; would say that x is either of these.

Otherwise you can do that quite easily with type variants, if you don't mind typing labels. In caml:

Code: Select all
#type multitype = Int of int | Float of float | String of string;;


It's a bit more heavy because you need to write the labels, e.g.
Code: Select all
let parse x = if x is_numeric then (Int parse_int x) else (String x);;

and
Code: Select all
match (parse x) with
 Int i -> do_something_with i
| String s -> do_something_else_with s
| Float f -> ...;;


Incomplete functions with "loose" external references that can be hooked up by metaprogramming. Ie, a function might have a loose variable called "chicken" that needs to be bound before it can be used. You could place it into a class, and hook "chicken" up to a class variable, or you could wrap it with another function that accepts "chicken" as a parameter.

Do you mean currying ?

For instance (still in caml - I know next to nothing of other languages)

Given
Code: Select all
let sum a b = a+b;;
,

Code: Select all
let plustwo = sum 2
is an incomplete call of the sum function, that still needs the second argument:

Code: Select all
plustwo 5;; -> 7


I don't know if you can name arguments, which would be required for out-of-order parameter passing

Multiple parallel type systems. I want to be able to decorate something with physics-style dimensional notation, etc, but still be the same underlying computational type. Units with restricted conversion between them. Algebras with limited valid symbols. This should work at both compile and run time (often dimensional analysis can be checked before you actually execute a formula or algorithm).


Intersection types, then? as in x: T1&T2, meaning that x is both of type T1 and type T2? I don't know if that exists already...

That is what I want for christmas.


You are early :-)

Edit:
Yakk wrote:Note that I didn't intend this thread to be a discussion of my christmas language wishes -- don't you guys have language features you want?


For christmas I want time, so that I can learn what already exists (caml, haskell, generic haskell and friends). Then look at their code and extend it.
User avatar
tendays
 
Posts: 945
Joined: Sat Feb 17, 2007 6:21 pm UTC
Location: INDIA

Postby taylor_venable » Tue May 01, 2007 3:02 pm UTC

You should look into the statically-typed functional languages, like Standard ML, OCaml, and Haskell.

In Haskell:
Code: Select all
data MyType = SomeInt Int | SomeFloat Float

someFunc :: MyType -> IO ()
someFunc (SomeInt i) = do { putStrLn ("Int: " ++ (show i)) }
someFunc (SomeFloat f) = do { putStrLn ("Float: " ++ (show f)) }

giveInt :: MyType
giveInt = SomeInt 3

giveFloat :: MyType
giveFloat = SomeFloat 4.5

anotherFunc :: IO ()
anotherFunc = case y of
                (SomeInt x) -> someFunc y
                (SomeFloat x) -> someFunc y
              where y = giveInt

main :: IO ()
main = do { someFunc (SomeInt 1); someFunc (SomeFloat 2.5) }
someFunc always knows what type of data is being carried inside, and anotherFunc can tell what the return type of giveInt is and act accordingly. The real power is the use of the safe, static variant.

Edit: Also, what I want for Christmas is for R6RS to be finished.
Last edited by taylor_venable on Tue May 01, 2007 6:04 pm UTC, edited 1 time in total.
My website: http://real.metasyntax.net:2357/
"I have never let my schooling interfere with my education." (Mark Twain)
User avatar
taylor_venable
 
Posts: 30
Joined: Mon Apr 09, 2007 7:22 pm UTC
Location: United States

Postby Yakk » Tue May 01, 2007 3:07 pm UTC

*nod*, a varient on currying.

Take a function:
Code: Select all
incomplete strict {real z} int norm(container x) {
  container::value_type result;
  for(auto i: x) {
    result += power(i, z);
  }
  result = power(result, 1./z);
  return result;
}

This function has a loose variable "z" which is of type real.

One would have to tie it down before calling the function. You could call it leaving "z" loose.

Then, one could do something like the following:
Code: Select all
class nary<scalar s, size n> {
  scalar values[n];
  static function auto norm := bind(::norm<this_type>, z:=2);
  method auto magnitude := methodize(this_type::norm, x:=this);
};
typedef nary<real, 3> three_space_vector;


and then three_space_vector::norm would be a function that takes a three_space_vector and returns a real in the L2 norm, and three_space_vector.magnitude() would apply the L2 norm to itself.

The point of all of this is that what I described can be checked to be valid at compile time. I'm providing code that generates the code that is to be compiled to be run, rather than generating code to be compiled to be run.

And I can't help but think that a language that does that kind of thing well will be useful. Generics are a restricted kind of this kind of programming.
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby hotaru » Tue May 01, 2007 3:21 pm UTC

something like this (jscript .net)?
Code: Select all
function ParseValue(value:String){
 var s:String='this is a string';
 var n:Number=Math.PI;
 if(value=="s") return s;
 if(value=="n") return n;
}

function ReadConfig(value:String){
 var result=ParseValue(value);
 switch(typeof(result)){
  case "string":
   print("it's a string.");
   break;
  case "number":
   print("it's a number.");
   break;
 }
}
User avatar
hotaru
 
Posts: 932
Joined: Fri Apr 13, 2007 6:54 pm UTC

Postby evilbeanfiend » Tue May 01, 2007 3:31 pm UTC

i think the point was he wanted a contract for the allowed return types. returning any type is a solved problem in most languages.
in ur beanz makin u eveel
User avatar
evilbeanfiend
 
Posts: 2650
Joined: Tue Mar 13, 2007 7:05 am UTC
Location: the old world

Postby Yakk » Tue May 01, 2007 4:58 pm UTC

*nod* -- you are querying RTTI in that case, and the completeness of the return can only be checked at compile time.

RTTI queries restrict you to heavy-weight return values, not primitive types.

Type-safety only guaranteable at run-time means type-safety of your code has to be explicitly tested manually, and isn't free.

I want to avoid having to do a dynamic cast on the return value. Instead, I want the function to return many possible different types (which type in particular determined at run-time), and have my code be able to deal with a variable that is one of a set of possible different types (checked for correctness at compile-time).

I don't want a virtual base class, I want to be able to insert generic code into the body of a function without placing it inside of a different function.

It is the difference between:
Code: Select all
std::vector<base_class*>

and
Code: Select all
std::vector<T*>


One is a vector of some unknown base class, and to get what is really inside of it you have to do RTTI queries.

The other is a vector of some generic pointer. At compile time, the code knows which of the types it contains, and can guarantee type-correctness.

You can pull off this trick by feeding through a function as follows:
Code: Select all
template<typename functor>
void do_mojo( parameters p, functor f )
{
  switch(p.details()) {
    case we_want_an_integer:
      f(p, p.getint());
      break;
    case we_want_a_double:
      f(p, p.getdoulbe());
      break;
   }
  }
}

struct my_functor {
  void operator()( parameters p, int i ) const {
    printf("%s %i\n", "We got an int!", i );
   }
  void operator()( parameters p, double d ) const {
    printf("%s %d\n", "We got an double!", d );
   }
};

parameters para = parameters();

do_mojo( para, my_functor() );


Here, the functor f is called with a different type depending on run-time logic. The last variable to the functor f is not a base class or an indeterminate type -- f is always called with an explicit type.

I don't want to have to do that kind of hoops. I want a dynamic_cast that returns either a variable of the right type, or a null pointer of the null pointer type, and the code that has access to the variable of the right type never has a null pointer.

Make sense?
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby hotaru » Tue May 01, 2007 5:33 pm UTC

evilbeanfiend wrote:i think the point was he wanted a contract for the allowed return types. returning any type is a solved problem in most languages.

this code:
Code: Select all
function ParseValue(value:String){
 var s:String;
 var n:Number;

 // assign something to s and/or n here

 if(value=="s") return s;
 if(value=="n") return n;
}

cannot return anything other than a string or number. trying to assign anything other than a string to s will fail, and trying to assign anything other than a number to n will fail. how is that any different from having a contract for the allowed return types?
User avatar
hotaru
 
Posts: 932
Joined: Fri Apr 13, 2007 6:54 pm UTC

Postby tendays » Tue May 01, 2007 5:52 pm UTC

hotaru wrote:
evilbeanfiend wrote:i think the point was he wanted a contract for the allowed return types. returning any type is a solved problem in most languages.

this code:
Code: Select all
function ParseValue(value:String){
 var s:String;
 var n:Number;

 // assign something to s and/or n here

 if(value=="s") return s;
 if(value=="n") return n;
}

cannot return anything other than a string or number. trying to assign anything other than a string to s will fail, and trying to assign anything other than a number to n will fail. how is that any different from having a contract for the allowed return types?


The "returning string or int"-property is not specified in the contract. Consider the following pseudo code (I am using the : symbol to mean choose randomly from one of the branches - in practice it could depend on user input or something)

Code: Select all
function f { return "string" : 2}
function g { return 2 : 'c' }
function k { return f : g }


So, f returns sometimes a string sometimes an int. You could say it has type () -> string|int
g returns sometimes an int sometimes a char. You could say it has type
() -> int|char
Now, what is k's type?
You could say it has type ()-> (()->string|int) | (()->int|char) (i.e. it either returns a function returning string or int, or returns a function returning int or char.
You could also say (weaker) that k has type () -> () -> string|int|char, i.e. it returns a function returning strings, ints or chars.

The first way is the strongest but quickly becomes intractable, both for the compiler to analyse the program, and for the programmer to understand error messages.
Errors get delayed. Say you pass k to a place where functions returning chars are unacceptable. The compiler will have no way to know where you made a mistake, is g wrong, is f wrongly returning g, are you wrongly passing k, or is the function getting k itself incorrectly handling chars?
The way using variants as in my (or taylor_venable's) example above allows telling the compiler what is the list of types that may be returned by the function

EDIT: By the way, what was maybe not obvious to you is that we are talking about static checkability, in that we want the compiler to make sure than when we write

Code: Select all
switch (k.gettype) {
case string: ...
case int: ...
}


k can't possibly be of any other type than string or int, and refuse to compile otherwise.
User avatar
tendays
 
Posts: 945
Joined: Sat Feb 17, 2007 6:21 pm UTC
Location: INDIA

Postby Yakk » Tue May 01, 2007 7:03 pm UTC

The point of the contract is two-sided.

On the side of the calling code, they can make assumptions about what the function returns. The switch example.

On the side of the called code, it means you can check it for contract violations without calling it. If the function tries to return the wrong type, it tells you when it is being built.

You can infer a contract from the context a function is called in, and from the body of the function -- but such an inferred contract is less stable, much harder for a compiler to figure out, and can't guard against mistakes in calling or writing a function. Plus it tends to lead to games of "where is the error", which results in spew-like error messages, or errors far removed from the place where the actual mistake was made.

That is why I like having static types in languages (notice I didn't say a staticly typed language). Because they provide me with simple contracts that I can extend upon.

Almost all of my requests are syntaxtical. The turing tar-pit exists: every task that any programming langauge can do can be done in any programming language. The existence of the turing tar-pit doesn't mean my requests are meaningless. :)
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby EvanED » Tue May 01, 2007 7:47 pm UTC

I'll just expand on something Yakk said for a while.

Yakk wrote:The point of the contract is two-sided.

On the side of the calling code, they can make assumptions about what the function returns. The switch example.

On the side of the called code, it means you can check it for contract violations without calling it. If the function tries to return the wrong type, it tells you when it is being built.

You can infer a contract from the context a function is called in, and from the body of the function -- but such an inferred contract is less stable, much harder for a compiler to figure out, and can't guard against mistakes in calling or writing a function. Plus it tends to lead to games of "where is the error", which results in spew-like error messages, or errors far removed from the place where the actual mistake was made.


This is one of the benefits of C++0x concepts for those of you who were following that.

Currently if you write the following code:
Code: Select all
std::list<int> l;
std::sort(l.begin(), l.end());


GCC 3.4 gives the following error on my system (file names truncated most of the way; the longest line is about 2100 pixels wide on my system):
Code: Select all
include/c++/3.4.4/bits/stl_algo.h: In function `void std::sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]':
delete.cc:8:   instantiated from here
include/c++/3.4.4/bits/stl_algo.h:2553: error: no match for 'operator-' in '__last - __first'
include/c++/3.4.4/bits/stl_algo.h: In function `void std::__final_insertion_sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]':
include/c++/3.4.4/bits/stl_algo.h:2554:   instantiated from `void std::sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]'
delete.cc:8:   instantiated from here
include/c++/3.4.4/bits/stl_algo.h:2197: error: no match for 'operator-' in '__last - __first'
include/c++/3.4.4/bits/stl_algo.h:2199: error: no match for 'operator+' in '__first +  _S_threshold'
include/c++/3.4.4/bits/stl_algo.h:2200: error: no match for 'operator+' in '__first +  _S_threshold'
include/c++/3.4.4/bits/stl_algo.h: In function `void std::__insertion_sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]':
include/c++/3.4.4/bits/stl_algo.h:2203:   instantiated from `void std::__final_insertion_sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]'
include/c++/3.4.4/bits/stl_algo.h:2554:   instantiated from `void std::sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]'
delete.cc:8:   instantiated from here
include/c++/3.4.4/bits/stl_algo.h:2113: error: no match for 'operator+' in '__first + 1'
include/c++/3.4.4/bits/stl_algo.h:2203:   instantiated from `void std::__final_insertion_sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]'
include/c++/3.4.4/bits/stl_algo.h:2554:   instantiated from `void std::sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]'
delete.cc:8:   instantiated from here
include/c++/3.4.4/bits/stl_algo.h:2119: error: no match for 'operator+' in '__i + 1'


Whew!

The problem should be described very simply (modeling it after if you call, say, foo(double) as foo("hi")):
Code: Select all
delete.cc:8: cannot convert `list<int>::iterator' to `RandomAccessIterator' for argument `1' to `void sort(RandomAccessIterator first, RandomAccessIterator last)'
delete.cc:8: cannot convert `list<int>::iterator' to `RandomAccessIterator' for argument `2' to `void sort(RandomAccessIterator first, RandomAccessIterator last)'


There isn't an error in the sort function, or in __insertion_sort, which I don't even know about, so why is the compiler giving you errors there? I didn't even WRITE those functions! They're part of the standard library!

Well, it's because it doesn't KNOW that. It doesn't know whether you are violating sort's contract within the function itself (by calling functions that you shouldn't assume) or within the call (by calling it with something outside of what sort should handle).

It's the same thing here. Suppose you have the following (borrowing tenday's notation):
Code: Select all
foo() { return (new vector) : (new list) }
bar() {
   auto ret = foo();
   return ret.size();
}


Yakk wants this to compile, because both vector and list have a size method.

But now suppose foo is changed to
Code: Select all
foo() { return (new vector) : 5 };


Where should the compiler give an error? If you say "on foo, because it shouldn't return an int", I will respond with "I have changed the specification for foo so that it may; the problem is in bar for not following that interface." If you say "in bar," I will similarly respond with "why should bar check for an integer when foo's interface guarantees that it won't return one? The problem is in foo(); it is not written correctly."

This is why, IMO, while type inference is nice, it cannot go all the way.

(BTW, you don't need to resort to complexities like multiple return values to see this.)
EvanED
 
Posts: 3781
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI

Postby Yakk » Tue May 01, 2007 9:18 pm UTC

Or, what is worse in a program like python...

You generate an object at point X. You then store it in a map for later use.

Then, way later on, you read the object from the map. You use it. Horrid errors! The object is not of the right type.

If one had a check to see that every object inserted into the map had the right interface, this goes away. In a language with static types (be they inheritance or ducktype based) this is easy: simply say that the map stores objects of a particular type that support that interface.

Then when you add the wrong object type to the map, you get an error at that point.

And equally important, when you pull an object out of the map, and then use it in a way that doesn't match the static type of the map, you get an error there. Even if everything that had been put into the map would work with your alternate operation.

So when upteen years down the road you modify your app so that a different type of object that matches the static contract of the map storage type is put into the map, the people using your object still work.

That is why I like static infectious type checking.

I also like the ability to break the type system -- but that should be done using explicit isolated functions to turn an object from a strictly typed variable into a looser typed variable.

All of this is pretty long-winded. The short version:

Strong static type checking means errors happen earlier instead of later.

I as many errors as possible to happen early.

My requests are all about early static error checking, in-language. I can generate nearly all of the above code via a code generator, but that doesn't give me the in-language debugging features I want. (the error isn't happening in the _generated_ code, but rather in the _generating_ code. I should be debugging into the generating code, not the generated code.)
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby Scarblac » Tue May 01, 2007 10:04 pm UTC

Yakk wrote:Or, what is worse in a program like python...

You generate an object at point X. You then store it in a map for later use.

Then, way later on, you read the object from the map. You use it. Horrid errors! The object is not of the right type.

If one had a check to see that every object inserted into the map had the right interface, this goes away. In a language with static types (be they inheritance or ducktype based) this is easy: simply say that the map stores objects of a particular type that support that interface.

Then when you add the wrong object type to the map, you get an error at that point.

In Python-land, a normal answer would be something like: you're an adult, just don't insert the wrong items into the list.

But if you need this functionality, simply subclass the builtin dict and add that type check to its put functions. It's quite trivial.

Code: Select all
>>> class mydict(dict):
...    def __setitem__(self, key, item):
...       if not isinstance(item, dict):
...          raise ValueError("You can only insert dicts into this dict")
...       dict.__setitem__(self, key, item)
...
>>> x=mydict()
>>> x['whee'] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 4, in __setitem__
ValueError: You can only insert dicts into this dict
>>> x['whee'] = {}
>>> x
{'whee': {}}
>>>


Re: thread start; usually you'll find that all of those things have existed in (Common) Lisp for decades...
Scarblac
 
Posts: 43
Joined: Wed Feb 28, 2007 11:39 am UTC

Postby tendays » Wed May 02, 2007 12:48 pm UTC

Scarblac wrote:In Python-land, a normal answer would be something like: you're an adult, just don't insert the wrong items into the list.


So, for instance, you are saying that the following code should compile, but just horribly (or nicely) crash at runtime, because it's the programmer's responsibility to check he isn't putting the wrong type in a variable?
Code: Select all
int x;
x="some string";


The idea of a type system is to check for mistakes at compile time. True, in an ideal world with perfect programmer we wouldn't need type systems, but we (at least I) are not perfect, and we make mistakes...

But if you need this functionality, simply subclass the builtin dict and add that type check to its put functions. It's quite trivial.
(...)


"simply" - you saw how long your code is? Would you write a class every single time you need such a feature?

Moreover, this is mere *runtime* checking. Suppose you have a bug that once in one thousand cases attempts putting the wrong type. You might never notice the problem while testing. A type checker, otoh, would complain at compile time, and when you manage to write a program that compiles without warning then you are *guaranteed* that there won't be typing errors.
User avatar
tendays
 
Posts: 945
Joined: Sat Feb 17, 2007 6:21 pm UTC
Location: INDIA

Postby Scarblac » Wed May 02, 2007 12:58 pm UTC

tendays wrote:
Scarblac wrote:In Python-land, a normal answer would be something like: you're an adult, just don't insert the wrong items into the list.


So, for instance, you are saying that the following code should compile, but just horribly (or nicely) crash at runtime, because it's the programmer's responsibility to check he isn't putting the wrong type in a variable?
Code: Select all
int x;
x="some string";


The idea of a type system is to check for mistakes at compile time. True, in an ideal world with perfect programmer we wouldn't need type systems, but we (at least I) are not perfect, and we make mistakes...

Right. But type checking only goes up to a point, anyway. You might have had

int x;
x = 32321;

But later found that it should have been 32320 instead. Type checking can only protect you from a very limited class of error, you always still need unit tests.

But if you need this functionality, simply subclass the builtin dict and add that type check to its put functions. It's quite trivial.
(...)


"simply" - you saw how long your code is? Would you write a class every single time you need such a feature?

It was 4 lines. I can't imagine needing such a feature very often. Yes, I probably would.
Scarblac
 
Posts: 43
Joined: Wed Feb 28, 2007 11:39 am UTC

Postby Rysto » Wed May 02, 2007 2:20 pm UTC

We already have a static vs. dynamic typing dicsussion in CS(why they moved it there I'll never know; the whole reason that I started the topic was so that the static-dynamic hijack would stop showing up in Coding).
Rysto
 
Posts: 1421
Joined: Wed Mar 21, 2007 4:07 am UTC

Postby Yakk » Wed May 02, 2007 2:42 pm UTC

Scarblac wrote:
Yakk wrote:Or, what is worse in a program like python...

You generate an object at point X. You then store it in a map for later use.

Then, way later on, you read the object from the map. You use it. Horrid errors! The object is not of the right type.

If one had a check to see that every object inserted into the map had the right interface, this goes away. In a language with static types (be they inheritance or ducktype based) this is easy: simply say that the map stores objects of a particular type that support that interface.

Then when you add the wrong object type to the map, you get an error at that point.

In Python-land, a normal answer would be something like: you're an adult, just don't insert the wrong items into the list.


And I want the ability to write stronger contracts. This is a wishlist. Python does not full fill the wishlist requirements.

If it isn't your wishlist, post your own wishlist.

I think Python is a neat language, but it isn't what I want. I use it all of the time for toy problems (read: problems that can be solved in under a year by a single skilled programmer).

Perfect programmers don't need programming languages. I have yet to meet a perfect programmer.

Scarblac wrote:Right. But type checking only goes up to a point, anyway. You might have had

int x;
x = 32321;

But later found that it should have been 32320 instead. Type checking can only protect you from a very limited class of error, you always still need unit tests.


I disagree with "very". You can write an impressive amount of error checking into a strong type system. I know this, because I do it: 90%+ of my bugs are caught during the compilation phase.

This is why I have a wishlist.

It contains code generation wishes, and strong typechecking wishes.

I understand that you don't wish for the same things. What kind of things do you wish for in a programming language, Scarblac?
User avatar
Yakk
 
Posts: 10063
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Postby evilbeanfiend » Wed May 02, 2007 2:52 pm UTC

i wish the static vs dynamic typing argument would go away ...
in ur beanz makin u eveel
User avatar
evilbeanfiend
 
Posts: 2650
Joined: Tue Mar 13, 2007 7:05 am UTC
Location: the old world

Postby Scarblac » Wed May 02, 2007 3:11 pm UTC

Yakk wrote:What kind of things do you wish for in a programming language, Scarblac?

Eep. That came out of left field - now you're making me think when I'm bored with work :-)

I can list the sort of things that I like:

I like Python's simplicity for simple things, and the ways you can reach into its internals and tweak them. I like its simple system for modules, classes, objects - they're all dicts.

I like Twisted Python's "deferred" - functions that return before they have an actual answer, returning an object you can attach callbacks to that get called when the answer arrives.

I like Haskell's lazy semantics. Defining an infinite list and using it like any other value.

I like languages with full Unicode support everywhere.

I like Perl's huge library repository; every language should have solid idiomatic libraries for all the things that are common today; the Web, databases, image processing, etc etc.

I like easy massive paralellization. Probably need to eliminate side effects for that. A language that can automatically detect that parts of the code have no side effects would be cool. So are languages that can seamlessly spread execution of a program over several machines.

I like Common Lisp's macros. Don't they provide the compile time building of code that you want?

I like execution speed, and compilation to machine code.

So, a combination of these would do :-)

For now, I love Python, I want to learn Common Lisp well but keep getting repulsed by the lack of libraries, I write Perl and advice companies on Java in my current job, but will start a new job in image processing research in a couple of months, where they mostly use LabView and C.
Scarblac
 
Posts: 43
Joined: Wed Feb 28, 2007 11:39 am UTC


Return to Coding

Who is online

Users browsing this forum: No registered users and 5 guests