What modern programming languages lack or doesn't need.

A place to discuss the science of computers and programs, from algorithms to computability.

Formal proofs preferred.

Moderators: phlip, Moderators General, Prelates

User avatar
MHD
Posts: 630
Joined: Fri Mar 20, 2009 8:21 pm UTC
Location: Denmark

What modern programming languages lack or doesn't need.

Postby MHD » Sun Nov 06, 2011 9:42 pm UTC

I am looking at compiling a list of features for a language I am making and I am looking for some inspiration.

One of the things I feel strongly about is the machine word types. C popularised having them available as keywords and sadly major players like Java, C#, C++ and D has taken after it. This is not a good thing. Machine word types lead to overflow errors that are hard to catch and insidiously common, and the fact that a machine word integer is only three keystrokes away makes this just about the most abundant "stealthy" logic error. You almost never really need machine word integers, and arbitrary precision libraries are not evil.

Also, why is it that everyone and their dog implements GMP integers but not GMP floats or GMP fractions?
EvanED wrote:be aware that when most people say "regular expression" they really mean "something that is almost, but not quite, entirely unlike a regular expression"

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Mon Nov 07, 2011 10:30 pm UTC

Machine word types, maybe hidden away.

Implement higher level types in the language, like GMP integers.

Ease of dealing with exceptions (like memory allocation errors). This is basically multiple return paths (or alternative types) from a function.

Ie, a+b could return an integer, or it could return a memory allocation error. The "normal" code path is the integer return value -- some means for the alternative return path to exist is needed. Ideally the fact that it could return a memory allocation error should be obvious (or other errors), and the code that calls it needs to deal with it explicitly, even if just a modifier on function declarations.

The alternative is treating "not enough memory" as a non-recoverable error, which many high level languages do. But examine what it does to overflow errors -- if overflow errors have to be handled explicitly, then machine-integers are less dangerous in a sense.

Expression trees are a wonderful part of C++ that lets you do crazy things like implement a parser using an ASL that structurally expresses a functional algorithm built up using operators. A "low level" version of it is infinite precision reals -- real reals -- that, when operated on, return functions from precision to floating point estimate.

So a real is a function from precision to floating point approximation. The sum of two reals is another such function. The composition of a real with a function that takes a floating point value cannot be a real, because you don't know the precision of that function. But a function taking a real can instead take a floating point value, and return a real. And a function taking a real, who gets a real with is perfectly precise (a floating point value) can return a real this is perfectly precise.

I like distinguishing between compile time and run time, insofar as code generation is really powerful, and effort spent at compile time can sometimes be "less important" than effort spent at run time. Things like the C# run time optimizer is also good.

Garbage collection should be implemented on top of the language, instead of as part of it. Raw pointers should be as "far away" as machine word integers, but available. The garbage collection algorithm used by the various kinds of pointers should be implemented in-language, allowing people using your language to use different garbage collection techniques for different classes of problems.

Objects can be good, but I want the power to determine how classes are related to each other, and what method overriding means. Constructor, destructor, virtual and non-virtual methods all have hard-coded behavior in something like C++ or Java -- there is no concept of "this method, when overriden, will be called in sequence by the class, breadth-first", or reverse, for normal methods (while this is required for constructors/destructors). The example of multiple construction and/or destruction phases is a simple example of why this power is needed -- multiple interface/implementation inheritance is another example. Languages like Lua give you a powerful OO system by allowing you to define the meta-classes (and, in theory, meta-meta classes) by which inheritance is defined.

Ie, I want to write syntax as much as possible in the language, instead of the language being syntax, much like LISP-like languages are.

I want to specify in-language how to parse a numerical constant of a particular type, which runs code at compile-time to build a compile-time constant version of that value (which could just be a string buffer and a run-time parser of that string buffer if you choose). (you can see an example of this in C++0x, as part of their crusade (only partway there) of removing the advantages that the "built in types" have over other types). So long as your built in types are first-class citizens, people will avoid using better types. Instead of downgrading all types to second-class citizens, upgrade all types to the kind of features that people can expect from built in types (compile time evaluation of simple expressions, for example).

Python style generators are cool. Both how they are used (relatively transparently in place of lists) and in how they are built (using coroutines). General coroutines might not be as obviously useful, but writing a coroutine system into your language and then writing general iterators using these coroutines is (to me) a better option. This should not mean you should have syntax that is significantly harder to use than python generators -- maybe you'd have to decorate the function with the fact it is a generator before using the yield keyword.

Modules are important. Syntactic inclusion is a bad idea (#include), because it scales poorly and generates poor error reporting. So your modules need some means of (compile time) exporting of interfaces to other modules to import. Ideally this could include the ability to parse another language's interface and from that, generate an interface (which lets your language use other languages libraries easily).

Types, being contracts for what operations a particular variable support, is ridiculously useful when debugging. Static typing being an option means you can statically know things about what is going on. The amount of documentation captured in a function saying it returns a particular type is huge.

Enumerations that are introspectable is good.

Typed algebras are good. A common low-level example is the bit-flag field, which is an algebra over |, &, ^ and ~ over a few initial values.

Lambdas are good. I prefer lambdas that allow explicit state import control, instead of having to introspect the entire lambda to know what state it uses.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

EvanED
Posts: 4331
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI
Contact:

Re: What modern programming languages lack or doesn't need.

Postby EvanED » Tue Nov 08, 2011 3:05 am UTC

Yakk wrote:Ideally the fact that it could return a memory allocation error should be obvious (or other errors), and the code that calls it needs to deal with it explicitly, even if just a modifier on function declarations.

In my opinion, in this way leads madness. What are you supposed to do in a no-memory situation? A lot of times, you can't do anything except maybe go "oops, stuff broke. sorry." Do you really want to annotate nearly every function in your program with "can fail allocations"? Even Java doesn't go nearly that far, and its (implementation of) checked exceptions area already obnoxiously onerous a lot of the time.

Garbage collection should be implemented on top of the language, instead of as part of it. Raw pointers should be as "far away" as machine word integers, but available.

I now disagree with this view. I think there are a lot of neat things you could do if you can guarantee memory safety, though not with today's OSs. (And OSs like this won't arise while we're using memory-unsafe languages.) For instance, imagine running all programs in the same memory space. This has the potential to cut the cost of context switches substantially: no need to do a TLB flush.

I'm to the point now where I think virtually nothing should be written in memory-unsafe languages -- including operating systems. Of course, I seem to be in the minority.

Goplat
Posts: 490
Joined: Sun Mar 04, 2007 11:41 pm UTC

Re: What modern programming languages lack or doesn't need.

Postby Goplat » Tue Nov 08, 2011 3:33 am UTC

MHD wrote:One of the things I feel strongly about is the machine word types. C popularised having them available as keywords and sadly major players like Java, C#, C++ and D has taken after it. This is not a good thing. Machine word types lead to overflow errors that are hard to catch and insidiously common, and the fact that a machine word integer is only three keystrokes away makes this just about the most abundant "stealthy" logic error. You almost never really need machine word integers, and arbitrary precision libraries are not evil.


Runtime array bound checking generally prevents integer overflow from causing any real problems, and it's a lot cheaper to check every array access than to turn every arithmetic operation into a loop (or even just to check them all for overflow). Arbitrary precision integers also can introduce denial-of-service attacks (memory exhaustion) if an attacker can make them grow to any size.

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Tue Nov 08, 2011 12:40 pm UTC

EvanED wrote:
Yakk wrote:Ideally the fact that it could return a memory allocation error should be obvious (or other errors), and the code that calls it needs to deal with it explicitly, even if just a modifier on function declarations.
In my opinion, in this way leads madness. What are you supposed to do in a no-memory situation?
A memory allocation failure doesn't mean you are in a no-memory situation.

It could mean you tried to allocate an image (or integer) of size 2^40.

This fails, but there is little reason to think that the program is now unable to function after it failed.
A lot of times, you can't do anything except maybe go "oops, stuff broke. sorry." Do you really want to annotate nearly every function in your program with "can fail allocations"?
Yes. But I want to make that easy.

Ie, you have a standard function decorator that marks up the function with the standard "can fail allocations" possibility, and implicitly hooks up the "can fail allocations" path for each function called within it.

So now you have a language behaves much like most or many languages on a memory allocation failure (the program halts or crashes), but the option to deal with them systematically if the user so chooses.
Garbage collection should be implemented on top of the language, instead of as part of it. Raw pointers should be as "far away" as machine word integers, but available.
I now disagree with this view. I think there are a lot of neat things you could do if you can guarantee memory safety, though not with today's OSs. (And OSs like this won't arise while we're using memory-unsafe languages.) For instance, imagine running all programs in the same memory space. This has the potential to cut the cost of context switches substantially: no need to do a TLB flush.

I'm to the point now where I think virtually nothing should be written in memory-unsafe languages -- including operating systems. Of course, I seem to be in the minority.

Sure. But which memory safe model do you use? Are you smart enough to figure out which memory safe model is ideal?

Instead of writing your language assuming you can build a perfect memory model, why not let your programmers make a memory model inside the language? Languages that use one particular memory model might not be allowed to "exit" to the lower level of raw pointers within the "safe code" subsystem that the programmers (which, in theory, could include library developers) themselves set up.

I like it when I can implement as many of the language "services" as possible within the language. Why not the memory model?
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

User avatar
MHD
Posts: 630
Joined: Fri Mar 20, 2009 8:21 pm UTC
Location: Denmark

Re: What modern programming languages lack or doesn't need.

Postby MHD » Tue Nov 08, 2011 4:02 pm UTC

Another thing I have up my sleeve:

Destructors are evil.
Data destructors are necessary in a language like C++, because there is no garbage collection. This ensures that upon leaving scope garbage memory is collected and so on.
But there are a few problems: They're slow; each object is another destructor call, they're unpredictable; you do not know how long a destructor may take to execute, and they are unsafe; arbitrary code can be run from them.

If your language is garbage collected properly you can get rid of 99% of all destructors.

But what about resources other than memory? Close them manually.
That's hard? Have the language make it easy. D code:

Code: Select all

file f = open("myfile", read);
block(exit) f.close();

mutex.acquire();
block(exit) mutex.release();
EvanED wrote:be aware that when most people say "regular expression" they really mean "something that is almost, but not quite, entirely unlike a regular expression"

Goplat
Posts: 490
Joined: Sun Mar 04, 2007 11:41 pm UTC

Re: What modern programming languages lack or doesn't need.

Postby Goplat » Tue Nov 08, 2011 4:43 pm UTC

MHD wrote:Destructors are evil.
Data destructors are necessary in a language like C++, because there is no garbage collection. This ensures that upon leaving scope garbage memory is collected and so on.
But there are a few problems: They're slow; each object is another destructor call, they're unpredictable; you do not know how long a destructor may take to execute
Like GC?
and they are unsafe; arbitrary code can be run from them.
By that logic, any procedure call is unsafe. Just because I'm not looking at the source code for Foo::~Foo() at the moment doesn't make it "arbitrary code"; it's not going to magically change into something other than what I wrote.

But what about resources other than memory? Close them manually.
That's hard? Have the language make it easy. D code:

Code: Select all

file f = open("myfile", read);
block(exit) f.close();

mutex.acquire();
block(exit) mutex.release();
It's even easier to forget to put in the try{}finally{} or block(exit) and end up with code that appears to work fine but leaks the resource if an exception is thrown.

EvanED
Posts: 4331
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI
Contact:

Re: What modern programming languages lack or doesn't need.

Postby EvanED » Tue Nov 08, 2011 5:28 pm UTC

MHD wrote:That's hard? Have the language make it easy. D code:

Code: Select all

file f = open("myfile", read);
block(exit) f.close();

Which is easier, that or

Code: Select all

file f = open("myfile", read);

?

Which one lets you forget stuff?

(I'll try to respond to Yakk's comments later.)

User avatar
MHD
Posts: 630
Joined: Fri Mar 20, 2009 8:21 pm UTC
Location: Denmark

Re: What modern programming languages lack or doesn't need.

Postby MHD » Tue Nov 08, 2011 10:21 pm UTC

Goplat wrote:By that logic, any procedure call is unsafe. Just because I'm not looking at the source code for Foo::~Foo() at the moment doesn't make it "arbitrary code"; it's not going to magically change into something other than what I wrote.


No, that is all fine and dandy if you wrote it. What if you didn't? What if a destructor throws an exception? What if a destructor leaks?

It's even easier to forget to put in the try{}finally{} or block(exit) and end up with code that appears to work fine but leaks the resource if an exception is thrown.


That is a good point, but it takes very little discipline to remember.

EvanED wrote:
MHD wrote:That's hard? Have the language make it easy. D code:

Code: Select all

file f = open("myfile", read);
block(exit) f.close();

Which is easier, that or

Code: Select all

file f = open("myfile", read);

?

Which one lets you forget stuff?

(I'll try to respond to Yakk's comments later.)


I am not sure I understand... This example presupposes GC.
The block(exit) statement executes when the scope is left, whether by an exeption, a return from function, or just normally...
EvanED wrote:be aware that when most people say "regular expression" they really mean "something that is almost, but not quite, entirely unlike a regular expression"

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Tue Nov 08, 2011 10:26 pm UTC

MHD wrote:
Goplat wrote:By that logic, any procedure call is unsafe. Just because I'm not looking at the source code for Foo::~Foo() at the moment doesn't make it "arbitrary code"; it's not going to magically change into something other than what I wrote.
No, that is all fine and dandy if you wrote it. What if you didn't? What if a destructor throws an exception? What if a destructor leaks?
What if a function you call throws an exception? What if a function you call leaks?

The problem is that with gc, destruction happens at some non-deterministic time and place, moreso than a problem with destructors.
It's even easier to forget to put in the try{}finally{} or block(exit) and end up with code that appears to work fine but leaks the resource if an exception is thrown.

That is a good point, but it takes very little discipline to remember.

This is not my experience when dealing with code that other people have written. Objects that self-cleanup pretty reliably are cleaned up. Objects that don't self-cleanup quite often end up being leaked.
I am not sure I understand... This example presupposes GC.
The block(exit) statement executes when the scope is left, whether by an exeption, a return from function, or just normally...

Basically, destructors for stack variables is an automatic injection of a block atexit in the parents context when the object is returned from the parent class.

Note that a "no destructors" also means that no block atexit if you have the ability to have coroutine type syntax (which includes how many languages now implement iterators and generators), because blocks can be exited during your gc phase (as someone can hold a reference to an interator in a gc context, presumably).
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

EvanED
Posts: 4331
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI
Contact:

Re: What modern programming languages lack or doesn't need.

Postby EvanED » Wed Nov 09, 2011 2:52 am UTC

Yakk wrote:
EvanED wrote:
Yakk wrote:Ideally the fact that it could return a memory allocation error should be obvious (or other errors), and the code that calls it needs to deal with it explicitly, even if just a modifier on function declarations.
In my opinion, in this way leads madness. What are you supposed to do in a no-memory situation?
A memory allocation failure doesn't mean you are in a no-memory situation.

It could mean you tried to allocate an image (or integer) of size 2^40.

This fails, but there is little reason to think that the program is now unable to function after it failed.

For a lot of times there is... or at least reason to think that it can only be fixed by aborting a substantial amount of computation, quite a ways up the call chain. If the goal of your program is to manipulate that image, then you can't do it if you can't allocate the space. At least in my experience, that's typical: if you can't allocate the memory you need, you can't do it. The best you can hope for is if you have something like Photoshop which is manipulating a bunch of images, and you just say "sorry, can't do that one" and leave the others open. That means your "no allocation" error needs to propagate up who knows how many functions in the call stack -- and all of those need to be annotated.


A lot of times, you can't do anything except maybe go "oops, stuff broke. sorry." Do you really want to annotate nearly every function in your program with "can fail allocations"?
Yes. But I want to make that easy.

Ie, you have a standard function decorator that marks up the function with the standard "can fail allocations" possibility, and implicitly hooks up the "can fail allocations" path for each function called within it.

It's easy in Java to mark a function as "can throw ..." too. It's still obnoxious because you have to do it a lot -- and annotating functions that could throw in a language where arbitrary-precision numbers is default would be way more obnoxious because you'd have to annotate way more functions. I'd honestly be surprised if less than, say 1% or 2% of functions in a typical program would not be annotated.

(Though that does provide an alternative: provide a way to annotate that a function can not throw a particular exception.)

Sure. But which memory safe model do you use? Are you smart enough to figure out which memory safe model is ideal?

Instead of writing your language assuming you can build a perfect memory model, why not let your programmers make a memory model inside the language? Languages that use one particular memory model might not be allowed to "exit" to the lower level of raw pointers within the "safe code" subsystem that the programmers (which, in theory, could include library developers) themselves set up.

I like it when I can implement as many of the language "services" as possible within the language. Why not the memory model?

I do like the idea at some level. However, to a first approximation it does lose some benefits like the one I gave above, at least if the overall system loses memory safety. There's a huge difference between "can prove this is memory safe (at least with respect to only accessing memory that 'belongs' to the component)" and not. If you can't prove safety, then you have to use runtime isolation. If you can prove it, you don't.

Now, it's possible that maybe you could do something like give a specification of a "safe code" subsystem, prove that your program obeys that, and prove that the specification actually ensures that level of memory safety (that your program can't access memory outside of what it has allocated). But I sorta feel like that would be a couple PhD dissertations. :-)

Edit: forgot to add this
MHD wrote:I am not sure I understand... This example presupposes GC.
The block(exit) statement executes when the scope is left, whether by an exeption, a return from function, or just normally...

Yes. And is it easier to add the block statement, or not add the block statement?

In my strong opinion, the evils of the "magicness" of destructors are far surpassed by the utility of them for cleaning up resources. I'm not necessarily opposed to make "resource cleaners" like that look a little different, but I am opposed to mechanisms that make it more convenient to get it wrong than to get it right. Even something like C#'s using or Python's with are pushing it a bit. (I'd be happier with that if you could annotate a class as "must use within a using statement". I'm not sure of details of that approach; you might need an extra "uncollected" keyword or something that would lift that requirement for one particular instance. Something like this, using mock C# syntax:

Code: Select all

collected class File implements IDisposable { ... }

void foo1() {
    File f = new File();  // error
    ...
}

void foo2() {
    using(File f = new File()) {    // good
        ...
    }
}

void foo3() {
    uncollected File f = new File();  // design smell, but legal
    ...
}

jareds
Posts: 436
Joined: Wed Jan 03, 2007 3:56 pm UTC

Re: What modern programming languages lack or doesn't need.

Postby jareds » Wed Nov 09, 2011 9:20 am UTC

EvanED wrote:I now disagree with this view. I think there are a lot of neat things you could do if you can guarantee memory safety, though not with today's OSs. (And OSs like this won't arise while we're using memory-unsafe languages.) For instance, imagine running all programs in the same memory space. This has the potential to cut the cost of context switches substantially: no need to do a TLB flush.

I'm to the point now where I think virtually nothing should be written in memory-unsafe languages -- including operating systems. Of course, I seem to be in the minority.

Sorry to join in picking on you, but you do know you're in the minority...

The possibilities I can think of are:
1) Proof-carrying code
2) Everything is compiled to some fixed abstract virtual machine (or fixed set of VMs, or at any rate making a new VM available is privileged)
3) Compilers are privileged software and mark their output with a safety bit in the filesystem or something (this seems bad, but is included for completeness)
4) I am missing something significant

So...how much execution time is spent on TLB flushes anyway? Suppose we have a 10 ms timeslice, and a TLB flush is probably a penalty of microseconds, then we are looking much less than 1%, and that is only if we actually switch between user-mode applications on every timeslice, which the trend to additional cores makes it easier to avoid.

I can't believe that TLB flushes outweigh running everything in a VM. Granted, VMs can be pretty fast, but the more serious problem is that making the VM choice part of the OS seems constraining on language diversity.
Proof-carrying code is interesting, but then this goes quite a bit beyond writing everything in memory-safe languages, to substantially improving the technology of proof-carrying code.

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Wed Nov 09, 2011 2:37 pm UTC

EvanED wrote:For a lot of times there is... or at least reason to think that it can only be fixed by aborting a substantial amount of computation, quite a ways up the call chain. If the goal of your program is to manipulate that image, then you can't do it if you can't allocate the space. At least in my experience, that's typical: if you can't allocate the memory you need, you can't do it. The best you can hope for is if you have something like Photoshop which is manipulating a bunch of images, and you just say "sorry, can't do that one" and leave the others open. That means your "no allocation" error needs to propagate up who knows how many functions in the call stack -- and all of those need to be annotated.
Yes. But programs whose only job is to manipulate a single image require a UNIX style process separation "toolbox" and "toolchain" model, which has fallen out of fashion.

A toolbox model that exists in-process is tempting to me, where I can actually call a function that can fail catastrophically and not break the rest of my program.
A lot of times, you can't do anything except maybe go "oops, stuff broke. sorry." Do you really want to annotate nearly every function in your program with "can fail allocations"?
Yes. But I want to make that easy.

Ie, you have a standard function decorator that marks up the function with the standard "can fail allocations" possibility, and implicitly hooks up the "can fail allocations" path for each function called within it.

It's easy in Java to mark a function as "can throw ..." too. It's still obnoxious because you have to do it a lot -- and annotating functions that could throw in a language where arbitrary-precision numbers is default would be way more obnoxious because you'd have to annotate way more functions. I'd honestly be surprised if less than, say 1% or 2% of functions in a typical program would not be annotated.

I said I wanted to make it easy.

To the level that people who use the program just assume that the annotation you put in front of the function that bundles all of the default assumptions is how you define "this is a function".

Ie:
std::func void foo( int bar )
where std::func annotates the foo function with the "this function can throw memory allocation errors, etc".

People who want a strict function would then not use such an annotation. If they wanted to be able to throw other errors, they would use that annotation.

Adding additional error paths would be easy, and the caller would be compelled to deal with them.

I'm not certain how to make the multiple return path/type thing elegant. One-dimensional code makes this tricky. Ideally, at the point where a function with multiple return types is called, different code should follow based on the return type.
(Though that does provide an alternative: provide a way to annotate that a function can not throw a particular exception.)
Exactly -- except, I'm willing to have a bit of sugar next to every function that marks up the function as having all of the default assumptions of the (sub)language, while leaving the baseline available.

And someone could go and write up a new set of better assumptions.
I do like the idea at some level. However, to a first approximation it does lose some benefits like the one I gave above, at least if the overall system loses memory safety. There's a huge difference between "can prove this is memory safe (at least with respect to only accessing memory that 'belongs' to the component)" and not. If you can't prove safety, then you have to use runtime isolation. If you can prove it, you don't.
That is sort of easy. Imagine if there was an annotation for such a safe function. And such a safe function was prohibited from calling non-safe functions, and using non-safe data.

Now you can call such a safe function and get all of the benefits of the memory model implied.

I would want to give the annotations on code and variables real teeth.
Now, it's possible that maybe you could do something like give a specification of a "safe code" subsystem, prove that your program obeys that, and prove that the specification actually ensures that level of memory safety (that your program can't access memory outside of what it has allocated). But I sorta feel like that would be a couple PhD dissertations. :-)

I don't think it would require PhD level work -- it just requires seriously viral and (nearly) unbreakable contracts on code.

Note that "safe" might be "safe up to signature by this public key", which is how you'd implement the memory architecture at the unsafe level and then use it in the safe code, and gives you the right to bypass the safety system if needed, yet allow 3rd party developers to write their own trusted memory subsystem.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

Goplat
Posts: 490
Joined: Sun Mar 04, 2007 11:41 pm UTC

Re: What modern programming languages lack or doesn't need.

Postby Goplat » Wed Nov 09, 2011 4:38 pm UTC

jareds wrote:So...how much execution time is spent on TLB flushes anyway? Suppose we have a 10 ms timeslice
Timeslicing isn't the problem. The cost of context switching becomes important when one process needs to use a service provided by another process; this "remote call" requires two context switches. Traditional OSes don't do this, they just put all these services into the kernel, which sucks for security (just look at the recently found bug in Windows font parsing, which allows a malicious font to run arbitrary code in kernel mode).

There is one experimental OS I know of, Microsoft's Singularity, that uses a memory-safe VM language rather than separate address spaces to isolate processes, so it's not an entirely unprecedented idea.

User avatar
Xeio
Friends, Faidites, Countrymen
Posts: 5101
Joined: Wed Jul 25, 2007 11:12 am UTC
Location: C:\Users\Xeio\
Contact:

Re: What modern programming languages lack or doesn't need.

Postby Xeio » Wed Nov 09, 2011 4:46 pm UTC

EvanED wrote:In my strong opinion, the evils of the "magicness" of destructors are far surpassed by the utility of them for cleaning up resources. I'm not necessarily opposed to make "resource cleaners" like that look a little different, but I am opposed to mechanisms that make it more convenient to get it wrong than to get it right. Even something like C#'s using or Python's with are pushing it a bit. (I'd be happier with that if you could annotate a class as "must use within a using statement". I'm not sure of details of that approach; you might need an extra "uncollected" keyword or something that would lift that requirement for one particular instance. Something like this, using mock C# syntax:
That might handle a local scope, but the issue of course being what if there is a class-level disposable resource? How can you enforce those resources be cleaned up as well? Maybe force all classes that use a disposable to be disposable themselves, and that all disposable class variables must be disposed in it...?

I'm not sure if that should be the compiler's job to enforce that so much as the developer's (or even better, code analysis tools).

jareds
Posts: 436
Joined: Wed Jan 03, 2007 3:56 pm UTC

Re: What modern programming languages lack or doesn't need.

Postby jareds » Wed Nov 09, 2011 6:34 pm UTC

Goplat wrote:
jareds wrote:So...how much execution time is spent on TLB flushes anyway? Suppose we have a 10 ms timeslice
Timeslicing isn't the problem. The cost of context switching becomes important when one process needs to use a service provided by another process; this "remote call" requires two context switches. Traditional OSes don't do this, they just put all these services into the kernel, which sucks for security (just look at the recently found bug in Windows font parsing, which allows a malicious font to run arbitrary code in kernel mode).

OK, that was my mistake. However, we're discussing running all native code in kernel mode, so we can resolve the problem you point out just as effectively by writing only the OS and its services in a safe language. A call from user-mode to kernel-mode doesn't require a TLB flush, and is fast on x86_64. If a user-mode process wants to allow fast context switching to provide a service, it can use whatever mechanism for known safety to be run in kernel mode. I still don't see the advantage to forcing the whole system into the same memory space. However, maybe this concedes too much. It would be silly in any event not to retain user-mode to sandbox unsafe applications.

A more serious objection is that in a traditional microkernel, the trusted code base is extremely small--typically thread switching and memory protection. Here, it has to include an entire JIT compiler.

Another objection is that context switching doesn't have to be as slow as it is now. You could provide multiple sets of MMU state (TLB, etc.) per core and let the OS select which one to use, so the effect of memory management on context switching would be near zero for the most important / recently used processes. Then, you have to consider that writing an OS in a safe language and getting people to use it is probably not that much easier than getting Intel or AMD to improve context switching.

User avatar
MHD
Posts: 630
Joined: Fri Mar 20, 2009 8:21 pm UTC
Location: Denmark

Re: What modern programming languages lack or doesn't need.

Postby MHD » Thu Nov 10, 2011 11:18 pm UTC

There is this project "Valix" that aims to make a safe OS that is essentially a giant VM. The language is prototype-OO similar to Self and Smalltalk.

Anyway:

Return variables are another thing that makes me shiver with excitement.

Code: Select all

def func(int input) (int output)
  if input < 0
    output = -input
  else
    output = input
  end
end

No more storing your result in a local variable so that you can do That One Last Thing Before Return (tm).

Object Inheritance is also a kludge compared to encapsulation/delegation.
EvanED wrote:be aware that when most people say "regular expression" they really mean "something that is almost, but not quite, entirely unlike a regular expression"

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Fri Nov 11, 2011 2:59 pm UTC

There are 3 kinds of parameters to a function.
Input variables. These are immutable within the function, or any changes to them are not reflected outside of the function.
Output variables. These are non-readable within the function (or, possibly, not readable until written to, but that is some ugly syntax and/or difficulty verifying).
Input/Output variables. These are both readable and writable within the function.

I consider having all 3 to be important, because how the code outside of the function should treat parameters varies based on what kind of parameter it is.

Output parameters prior state should be discarded -- they are being "assigned to", like a int foo = bar() type statement. But there is little reason why there should be only one, or that they shouldn't have names. An uninitialized output parameter is expected.

Input parameters are not modified by the function, assuming it is pure functional and they cannot be reached by indirect means.

Input/Output parameters are also important in a number of contexts.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

User avatar
Meteorswarm
Posts: 979
Joined: Sun Dec 27, 2009 12:28 am UTC
Location: Ithaca, NY

Re: What modern programming languages lack or doesn't need.

Postby Meteorswarm » Tue Nov 15, 2011 6:50 am UTC

Yakk wrote:There are 3 kinds of parameters to a function.
Input variables. These are immutable within the function, or any changes to them are not reflected outside of the function.
Output variables. These are non-readable within the function (or, possibly, not readable until written to, but that is some ugly syntax and/or difficulty verifying).
Input/Output variables. These are both readable and writable within the function.

I consider having all 3 to be important, because how the code outside of the function should treat parameters varies based on what kind of parameter it is.

Output parameters prior state should be discarded -- they are being "assigned to", like a int foo = bar() type statement. But there is little reason why there should be only one, or that they shouldn't have names. An uninitialized output parameter is expected.

Input parameters are not modified by the function, assuming it is pure functional and they cannot be reached by indirect means.

Input/Output parameters are also important in a number of contexts.


Why not just get rid of your output parameters and have multiple return values? The only case I can think of where it's useful to hand in some data for output purposes is in pointer-y languages like C where you want to make sure that memory allocation and deallocation is sane, but for most languages you don't need that.
The same as the old Meteorswarm, now with fewer posts!

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Tue Nov 15, 2011 2:55 pm UTC

Output parameters are return variables.

However, you'll note that many languages have things like "optional input parameters" by position and sometimes name, which I find quite useful.

Optional output parameters that are named that can be distinguished from input parameters might actually be useful.

Most languages that I know of that return multiple parameters at best provide a tuple-like syntax, with no names, and handle optional parameters by blank placeholders in the list (at best). That is about as good as optional position based input parameters. Or they use input/output parameters as output parameters, with no syntactic hint that they are actually output only. And many languages make all parameters input/output implicitly -- others do so for some types, but not others (the languages where base types and non-base types act fundamentally different, where base types (like integers) are pass-by-value, and non-base types are pass-by-reference implicitly).

I don't know what the syntax for a language that supported named and sometimes optional I, IO and O parameters would look like, but it doesn't mean it wouldn't be nice to have.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

EvanED
Posts: 4331
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI
Contact:

Re: What modern programming languages lack or doesn't need.

Postby EvanED » Tue Nov 15, 2011 4:37 pm UTC

Yakk wrote:I don't know what the syntax for a language that supported named and sometimes optional I, IO and O parameters would look like, but it doesn't mean it wouldn't be nice to have.

There's a good chance you know this, but C# doesn't give you optionally named bit, but they give you the rest. When declaring a function, you can declare variables as out or ref (just like C++ references). However, when calling a function that uses one of these, the call site has to match. This gives you an actual syntactic difference (and semantic difference too; I believe out parameters need to be definitely-assigned before they are read) both at the call site between the different parameter types as well as between just output and input-output:

Code: Select all

void foo(int x, out int y, ref int z) { ... }

int a,b,c;
...
foo(a, b, c); // error
foo(a, out b, ref c); // correct


The other interesting approach for multiple return values is given by Common Lisp. Functions can return a values "object"; this consists of one value that you get if you don't do anything special, but then some additional information it's possible to extract if you're interesting. This could be useful if there is some additional information that a particular call site might be interested in, but not necessarily. (Maybe some timing information.)

Code: Select all

* (defun foo (x) (values x (+ 1 x)))
FOO
* (foo 3)
3
4
* (let ((x (foo 3)))  ;; here 'x' just gets the main value, 3
    x)
3
* (multiple-value-bind  (x y) (foo 3) (list x y))  ;; by using multiple-value-bind, we pick up the second value too
(3 4)
*

User avatar
Xanthir
My HERO!!!
Posts: 5413
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex
Contact:

Re: What modern programming languages lack or doesn't need.

Postby Xanthir » Tue Nov 15, 2011 9:14 pm UTC

EvanED wrote:The other interesting approach for multiple return values is given by Common Lisp. Functions can return a values "object"; this consists of one value that you get if you don't do anything special, but then some additional information it's possible to extract if you're interesting. This could be useful if there is some additional information that a particular call site might be interested in, but not necessarily. (Maybe some timing information.)

Code: Select all

* (defun foo (x) (values x (+ 1 x)))
FOO
* (foo 3)
3
4
* (let ((x (foo 3)))  ;; here 'x' just gets the main value, 3
    x)
3
* (multiple-value-bind  (x y) (foo 3) (list x y))  ;; by using multiple-value-bind, we pick up the second value too
(3 4)
*

While I love the CL approach, it's still not *quite* ideal. We should be able to *also* provide a map of values (with some easy way to designate the default value). That would make it perfect.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))

User avatar
WarDaft
Posts: 1583
Joined: Thu Jul 30, 2009 3:16 pm UTC

Re: What modern programming languages lack or doesn't need.

Postby WarDaft » Wed Nov 16, 2011 1:10 am UTC

CPS lets you do multiple 'returns' pretty easily. But, then you have to code in CPS. Maybe if you can find a nice way to hybridize it?
All Shadow priest spells that deal Fire damage now appear green.
Big freaky cereal boxes of death.

User avatar
Meteorswarm
Posts: 979
Joined: Sun Dec 27, 2009 12:28 am UTC
Location: Ithaca, NY

Re: What modern programming languages lack or doesn't need.

Postby Meteorswarm » Wed Nov 16, 2011 5:23 am UTC

Why not just return (retval * map) ? Tuples are handy.

Also, one thing many languages really need: algebraic data types and pattern matching.
The same as the old Meteorswarm, now with fewer posts!

HungryHobo
Posts: 1708
Joined: Wed Oct 20, 2010 9:01 am UTC

Re: What modern programming languages lack or doesn't need.

Postby HungryHobo » Wed Nov 16, 2011 1:58 pm UTC

I came across this a while back:
http://colinm.org/language_checklist.html

it's very tongue in cheek but it may be useful to peruse if you're writing yourself a list of things you want to avoid or things your language needs.
Give a man a fish, he owes you one fish. Teach a man to fish, you give up your monopoly on fisheries.

User avatar
MHD
Posts: 630
Joined: Fri Mar 20, 2009 8:21 pm UTC
Location: Denmark

Re: What modern programming languages lack or doesn't need.

Postby MHD » Fri Nov 18, 2011 10:39 am UTC

What I mean with return variables is not the same as return parameters.
Parameters are passed to a function.
Return variables are literally the returned value.

Code: Select all

def func(param1, param2) (ret1, ret2)
  ret1 = param1 x param2
  ret2 = param2 x param1
end

(r1, r2) = func(p1, p2)


Multiple returns, tuples optional; syntax awesome.
EvanED wrote:be aware that when most people say "regular expression" they really mean "something that is almost, but not quite, entirely unlike a regular expression"

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Fri Nov 18, 2011 1:37 pm UTC

That needs an arrow, like ->.

Then, add in named parameters (rt2 = ret2) = func(param2 = p2, param1 = p1), and maybe a distinguished token to indicate "argument binding" like :=

Code: Select all

// Prototype:
def func( param1, param2 ) -> (ret1, ret2, ret3, ret4, ret5)

// Use, ignoring ret3 and ret4:
(rt2 := ret2, r1 := ret1, r5 = ret5) = func(param2 := p2, param1 := p1)


Dunno.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

User avatar
MHD
Posts: 630
Joined: Fri Mar 20, 2009 8:21 pm UTC
Location: Denmark

Re: What modern programming languages lack or doesn't need.

Postby MHD » Sun Nov 20, 2011 2:05 pm UTC

Yakk wrote:That needs an arrow, like ->.

Then, add in named parameters (rt2 = ret2) = func(param2 = p2, param1 = p1), and maybe a distinguished token to indicate "argument binding" like :=

Code: Select all

// Prototype:
def func( param1, param2 ) -> (ret1, ret2, ret3, ret4, ret5)

// Use, ignoring ret3 and ret4:
(rt2 := ret2, r1 := ret1, r5 = ret5) = func(param2 := p2, param1 := p1)


Dunno.


The arrow part I dig, that can be used in lambdas too or something... The named argument part gets verbose quickly.
EvanED wrote:be aware that when most people say "regular expression" they really mean "something that is almost, but not quite, entirely unlike a regular expression"

User avatar
Yakk
Poster with most posts but no title.
Posts: 11128
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: What modern programming languages lack or doesn't need.

Postby Yakk » Sun Nov 20, 2011 5:19 pm UTC

Sure -- which is why it should be optional. Positional by default (for brevity), but you can use the "named bind" operator (:=) to hook up variables in random order.

This can also make structure construction much less annoying.

In some senses, it is similar to objective-C / smalltalk multi-arguement messaging with reordering allowed.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.

mr-mitch
Posts: 477
Joined: Sun Jul 05, 2009 6:56 pm UTC

Re: What modern programming languages lack or doesn't need.

Postby mr-mitch » Sun Nov 20, 2011 11:38 pm UTC

What's wrong with multiple return paths through objects or data types/classes? I'm partial to functional programming/Haskell's Just x/Nothing.

User avatar
Meteorswarm
Posts: 979
Joined: Sun Dec 27, 2009 12:28 am UTC
Location: Ithaca, NY

Re: What modern programming languages lack or doesn't need.

Postby Meteorswarm » Thu Nov 24, 2011 3:58 pm UTC

mr-mitch wrote:What's wrong with multiple return paths through objects or data types/classes? I'm partial to functional programming/Haskell's Just x/Nothing.


Yeah, algebraic data types with good matching (and also tuples) solve a lot of these problems.
The same as the old Meteorswarm, now with fewer posts!


Return to “Computer Science”

Who is online

Users browsing this forum: No registered users and 6 guests