A pragmatic language for the future

Started by
609 comments, last by ApochPiQ 16 years, 1 month ago
I need to make clear that where constraints are used, the system is statically typed, all verifications are done at compile time where possible. Its actually impossible for OddNumber to contain an even number: the compiler will choke if you attempt to assign an even literal to it, or the explicit cast required to assign a variable of another type to it will fail at runtime.

Falling Sky:
This language is mostly statically typed, I don't think Parrot would be a good fit. I think it is important we are able to target .Net, too.

How did they get IronPython's dynamic typing to work with .Net?
Advertisement
Quote:Original post by CoffeeMug
Quote:Original post by ApochPiQ
Static typing as a general default

I strongly disagree. I want inferred typing where possible by default. When inference fails, the most I can handle is a warning. I want to be able to place type constraints and have the type checker generate warnings. I don't want to spend my time making the compiler happy for the sake of making the compiler happy.


How do you propose implementing type inference when there are nontrivial semantic types defined by the user? I don't see these things being reconcilable. In any case I think the ability to define type munging operations makes type inference redundant for the most part.

Frankly, it has nothing to do with "making the compiler happy for the sake of making the compiler happy." It has to do with contractual obligations, consistent obedience of type semantics, and eliminating undefined behavior by clamping things to clearly denoted boundaries. The idea is to make it hard to write invalid code, not make it hard to write valid code.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Parrot also has numbers, strings, and floats. But yes I had .net in mind also. Its possible to do dynamic types in it...look at Boo's duck type. Seems pretty complex to imeplement it though.
Quote:Original post by ApochPiQ
If I can't be shown a simple example that clearly demonstrates the advantage of some feature, I don't want it.

This is a dangerous requirement. Many very useful language features are only seen to be useful with complex examples.

Think of the myriad arguments regarding "goto". Nefarious goto abolitionists demand simple examples where goto simply must be used; knowing full well that any simple example can be transformed into something equally simple that doesn't use goto.

Similar arguments apply to implicit typing -- in a simple example, adding explicit types isn't a hassle; labelled break and continue -- you can either use goto, or add a little more logic; lexical closures -- you can just use boost::lambda.

Closures are also a good example where a useful language feature is primarily useful in concert with other language features. Closures are really only syntax sugar in languages without garbage collection and a rich set of higher-order functions in the standard library. But then, without closures, having a rich set of higher-order functions in the standard library is easily argued against. And some people will always argue against garbage collection.
Quote:
  • Codify semantics of a system on both abstract and technical/concrete levels

  • Attempt to unify a compiler's total knowledge of a system with the programmer's total knowledge of that system


What do these mean?

Everything else is faultless.

Of particular note is the idea of building high-level abstraction upon low-level abstraction but, effectively, 'within the same language'. One obvious advantage of this is that you can mix-and-match high-level abstractions, rather than having to seperate your program into, let's say, seperate SQL, XSLT, PHP and Perl components. Microsoft are doing something similar with Comega, bringing together high-level abstractions from different domains, except their abstractions are fiat, rather than being built from lower level abstractions (so you couldn't, for example, adjust the XML reader if you have a special need to do so in your particular project).

Another point is that you can also mix-and-match the level of abstraction itself: have C-level access to the graphics hardware when you need it, but not have to sacrifice Perl/Python/Ruby-level access to everything else where you do that. Pyrex is an example of this approach: it (partially) unifies Python and C -- it wraps Python's syntax over C, and allows 'C' functions to transparently refer to 'Python' variables and functions as though they were C variables and functions, and vice versa.

As regards typing, another interesting concept is the fact that in most languages, an object always has the same type. These 'validating types', or predicate types as they are more usually known, are interesting because the type of a mutable object may change over the course of its lifetime.

Microsoft's Vault language has a similar system where it statically tracks the possible states an object could be in over the course of its lifetime. For example, a file might be closed or open. When closed, certain functions are not permitted upon it. Vault not only flags an error at run time when a closed file is written to (for example), but can also detect when a possibly-closed file is written to at compile time.

There are no just safety gains to be had, either. An OOP OpenGL wrapper's textures could be in the state of "bound" or "unbound". Rather than flagging an error if the wrapper attempts to set the scaling mode of an unbound texture, however, it could just bind it. The particular advantage of this is that when the compiler can prove that a given texture is still bound at compile time, it can avoid even checking if the texture is bound at run time.

class Texture{  state bound;  ...  void border_color= (Color c) [bound]  {    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, c.toArray.toPointer);  }  ...};// [bound] attribute ensures the caller will check if the texture is bound where// this is necessary.void do_something_with_texture (Texture [bound] texture){  texture.border_color = #ff0; // Compiler knows texture is still bound -- check elided.  texture.scale = SMOOTH;      // Compiler knows texture is still bound -- check elided.}void texture_mangler (Texture texture){  do_something_with_texture(texture); // Runtime check.  texture.priority = 1;               // No check.  texture.wrap.s = CLAMP;             // No check.}


What does this buy you? It buys you efficiency -- because the checks are elided when the compiler can prove they are unnecessary, your program spends less time performing calculations which are irrelevent. It buys you safety -- because you aren't responsible for checking that the texture is bound, you won't ever accidentally apply an operation to the wrong texture.

It's also an example of features working in concert: you can get efficiency without this feature, and you can get safety without this feature. But the only way to get efficiency is to sacrifice safety by manually ensuring you don't bind textures unnecessarily, whilst the only way to get safety is to sacrifice efficiency by always binding textures even if it might be unnecessary.

datatype oddnumber = integer where oddnumber % 2 == 1;datatype evennumber = integer and not oddnumber;oddnumber x = 3;evennumber y = 2;y = x; // barf, evennumber is explicitly not oddnumber

Quote:Original post by ZQJ
This problem got me thinking that a function is often more defined by what it does than what it operates on, so perhaps the concept of member functions is unnecessary.

I'm of two minds.

Member functions are certainly technically unnecessary. However, they do seem to aid discoverability and recall: I find it easier to remember methods associated with classes than module-level functions which accept a class instance as an argument. This may reveal something deep and fundamental about the human brain, or it could just be that I've been afflicted with too much Visual Basic and Java.

IDEs typically also make it easier to discover methods: type the object's name, press dot, and you get a list of the methods you can apply to that object. I've never seen that happening for unbound functions -- type the object's name, press nothing, and you get a list of the functions you could have put before the object's name.
Quote:
Of course if functions were separate to classes they would need a separate access scheme to prevent internal functions being called on classes. This would also allow multiple instead of single dispatch but might require all virtual functions to be listed in the class definition so the compiler knows what to look for when building the vtable. I think LISPs CLOS works something like this but I've never used it so I'm not sure.

Nope. I'm not sure what a "virtual function" would be when there are no member functions, but Common Lisp doesn't require the declaration of any functions when declaring a class, although it provides syntax for conveniently declaring accessors at the same time.
Quote:
4) Operators: LISP doesn't suffer any kind of precedence issues because the order is totally explicit. However I think this leads to a pretty verbose language and I'd prefer it if operators had precedence as in C/C++ because I think it makes formulae clearer.

Lisp isn't really that verbose:
      Terminal velocity                 Lorentz factorC     sqrt((2 * m * g) / (Cd * p * A))  1 / sqrt(1 - pow(u / c, 2))Lisp  (sqrt (/ (* 2 m g) (* Cd p A))    (/ (sqrt (- 1 (expt (/ u c) 2))))

For the most part, it's verbosity comes from the long (in some cases insanely long) names for things in the standard library.

For me, the primarily fault with Lisp's standard math syntax isn't the verbosity, but the fact that it's harder to compare your equation with the one in the physics book, because they don't look anything alike.
Quote:
6) Garbage collection - I'm not an expert on how to implement garbage collection efficiently and as far as I remember a lot of the sources I've read have been conflicting. Anyway it seems to me that in a lot of cases ownership of objects is pretty obvious and therefore garbage collection is superfluous so I think there should be a mechanism for restricting garbage collection to where's it's needed.

I think that object ownership is rarely terribly obvious. Perhaps the reason your experience differs is that you're used to programming with languages where object ownership has to be perfectly clear if you are to have a hope of managing your memory.

Whilst disabling garbage collection in particular parts of the program is not unreasonable, it should be noted that simply not reporting a given object to the garbage collector, thereby "disabling" GC for that object, is not usually reasonable.

A manually managed object could not, in general, contain references to automatically managed objects, because the garbage collector would never see those references and could destroy the automatically managed object, even though it can still be accessed.

[Edited by - Nathan Baum on March 28, 2006 6:48:20 PM]
Quote:Original post by Falling Sky
You guys realize its going to be really... "fun" implementing dynamic types in a compiled language? OR will this be interpreted? In my opinion I think we should use Parrot. Its the VM being made as a universal interpreter for dynamic languages and is being made for Perl 6's primary target.


It wouldn't be that difficult. Just make a super-type from which every other type is derived.

Quote:Original post by CoffeeMug
Quote:Original post by ApochPiQ
Static typing as a general default

I strongly disagree. I want inferred typing where possible by default. When inference fails, the most I can handle is a warning. I want to be able to place type constraints and have the type checker generate warnings. I don't want to spend my time making the compiler happy for the sake of making the compiler happy.


What does that have to do with whether it's statically typed or not?
In the static vs. inferred type argument, here's something worth considering: matrix inversion. The inversion algorithm (at least the version I usually use) involves knowing the absolute value of elements of the matrix (this isn't really necessary but there's no reason it shouldn't be allowed). For complex numbers obviously the absolute value is a different type from the original value, so in order to write a generic inversion function for matrices it must be possible to infer types.

Also, I think the Number type requires a lot of operators to be defined. For the most generic programming we want to require the fewest possible things of our types, therefore the DoubleNumber function should only require x can be multiplied by a number. The trouble with this seems to be that the function itself could rapidly become the shortest way of defining the requirements on a type which seems like a bad idea.

ApochPiQs idea of inferring the return type from what it's assigned to seems like a good one, unfortunately I think it also would require the compiler check the code of the called function, which could make compiling difficult.
Quote:Original post by Nathan Baum
Nope. I'm not sure what a "virtual function" would be when there are no member functions, but Common Lisp doesn't require the declaration of any functions when declaring a class, although it provides syntax for conveniently declaring accessors at the same time.


That's easy - it's a function which is selected at runtime based on the types of one or more of its arguments. However now that I think of it that's a silly thing to talk about at this point because it seems that static and dynamic dispatch should appear the same.

Quote:
I think that object ownership is rarely terribly obvious. Perhaps the reason your experience differs is that you're used to programming with languages where object ownership has to be perfectly clear if you are to have a hope of managing your memory.


Well, to take a simple example: a string in usually consists of an object containing a pointer to an array of characters and possibly an integer giving the length of that array. Nothing outside of the string object should ever point to that character array, and therefore it should be safe to say the they are both destroyed at the same time and there's no need to garbage collect the character array itself. I've never had much trouble with memory ownership, but as you say, perhaps that's my style, I've got C on the brain.
Quote:Original post by Roboguy
Quote:Original post by Falling Sky
You guys realize its going to be really... "fun" implementing dynamic types in a compiled language? OR will this be interpreted? In my opinion I think we should use Parrot. Its the VM being made as a universal interpreter for dynamic languages and is being made for Perl 6's primary target.


It wouldn't be that difficult. Just make a super-type from which every other type is derived.


Ummm wow....so your saying just to make a super type in asm...well, you do that. Wait...thats another fun fun fun thing, implementing OOP in asm! Youve really got to show me that ;) j/k

---

Apoch and I have been talking. We think the best approach is to have a compiler that takes Foo and spits out an intermediate language/bytecode. The intermediate lang is simply a bridge between Foo and other langs like asm. That way it would be easier to target asm, .net, ect ect.

Quote:Original post by Falling Sky
Quote:Original post by Roboguy
Quote:Original post by Falling Sky
You guys realize its going to be really... "fun" implementing dynamic types in a compiled language? OR will this be interpreted? In my opinion I think we should use Parrot. Its the VM being made as a universal interpreter for dynamic languages and is being made for Perl 6's primary target.


It wouldn't be that difficult. Just make a super-type from which every other type is derived.


Ummm wow....so your saying just to make a super type in asm...well, you do that. Wait...thats another fun fun fun thing, implementing OOP in asm! Youve really got to show me that ;) j/k

---

Apoch and I have been talking. We think the best approach is to have a compiler that takes Foo and spits out an intermediate language/bytecode. The intermediate lang is simply a bridge between Foo and other langs like asm. That way it would be easier to target asm, .net, ect ect.


Assembly and assembly-like languages are rarely statically typed, so I don't see how that would be an issue. The type checking is done by the compiler before it's compiled into the intermediate language.
Im talking about if you had dynamic types.
If you did say:
myVar = input()
myVar = 1000

The compiler doesnt know what type is first put into myVar. Variants are not easy to implement in asm

This topic is closed to new replies.

Advertisement