Skip to main content

Notice

Please note that most of the software linked on this forum is likely to be safe to use. If you are unsure, feel free to ask in the relevant topics, or send a private message to an administrator or moderator. To help curb the problems of false positives, or in the event that you do find actual malware, you can contribute through the article linked here.
Topic: Programming Languages (Read 35088 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Programming Languages

Reply #75
Quote
Bringing this thread back from the dead...

Here's a very interesting presentation from Tim Sweeney, Unreal's main programmer, on the future of programming languages for game development:
[a href="index.php?act=findpost&pid=361505"][{POST_SNAPBACK}][/a]


Yeah.  I just recently saw that posted on Ltu, where Sweeney has posted some additional comments about it in various threads.  I haven't had a chance to read it until now, but it's very interesting.

First of all, I'd like to point out that a lot of what he says is pretty similar to what I've already been trying to convey in this thread.  In particular, you notice that he is not fond of C# or Java, and he gives some very good reasons why, e.g., exceptions everywhere, lack of true safety (lame type systems), poor/broken concurrency support, etc.

It's very interesting, given the kind of work he does and the complexity of his programs, that he makes these observations and then comes down so strongly in support of static functional languages (even mentioning Haskell!) with mathematically-founded safety characteristics.  I think it goes to show that I'm not just crazy in my advocacy of these sorts of things, even though I might be the only one in this thread really interested in any of it...

There are a couple of specific things I'd like to comment on in the slides:
  • Numeric computation essentially functional -  This is very interesting.  He says that the computation of their numeric stuff is essentially carried out through functional modification of constant data.  This seems to make sense to me when dealing with a context as complex as the game worlds that probably exist in Unreal 3 stuff, but I have to admit I'm very surprised to see a game developer advocate this approach.  However, given the problems of mixing wide-spread concurrency with stateful computation, I guess it's understandable.  I suspect more game developers are going to start to realize the advantages of this approach, that is, if they can manage to survive the transition from current architectures to throughly parallel architectures on the Xbox360 and PS3.
  • Concurrency - Notice how much emphasis he puts on this.  Like I said earlier, this is just going to keep becoming more and more of a central focus in programming.  It's a very hard problem to solve with current approaches, and almost impossible to handle cleanly in systems that are primarily stateful.  One thing I'm a bit surprised he doesn't mention (maybe he did and I missed it) are the possibilities for languages built around cutting edge process calculi.  Hoare's CSP and Milner's CCS have been around for awhile, and now we have the even more powerful pi-calculus and related calculi like the Join-calculus and Ambient-calculus.  There have been some languages already built around the pi-calculus (Pict, Nomadic Pict, and others), and they show some promise I think.  It just makes sense, I think, to build concurrency into the language itself in a way that is mathematically sound and amicable to type systems (e.g., the typed pi-calculus).  These sorts of systems (e.g., see something like TyPiCal) can ensure statically, through the type system, that your code is free of deadlocks and race conditions.  Just the sort of thing Sweeney seems to want.  I think there's a ton of promise here, and that future languages are going to start drawing a lot more inspiration from these calculi.  Right now I'm actually working on, for example, a formal language for a certain hardware communication project where the language is based upon the typed pi-calculus (rather than my previous lambda calculus based extended System-F-omega experiments), and I'm using it for just these sort of guarantees of safety and mathematical soundness.
  • Laziness - I agree with him here.  Laziness is good when it can be specified optionally (in a way fundamental to the language, and not hacked in via libraries).  I would like to note that Alice ML, for example, has just this sort of approach.  Everything is static by default, but laziness is as natural as prefixing an expression with 'lazy.'  Through the use of futures, you get a form of laziness encoded in the type system in a way that turns out to be pretty intuitive; AFAIR, you don't need to even explicitly ask for evaluation as Sweeney suggests.  I'd like to see this approach gain more support.
  • Functional vs. Imperative - Again this is interesting.  He seems to like an approach that is functional by default, offering the benefits of referential transparency, but also allowing to break that with imperative computation and side effects in special cases.  Hrmm.. sounds a lot like ML.
  • Garbage Collection -  Boy.. the "garbage collection is slow and clunky" zealots are going to have to find a new one now.  Who would have thought that Sweeney is a fan of this, and that they already use this approach in their engines?  I think an interesting possibility here, which Sweeney doesn't mention (unless I missed it again) is region inference.  Region inference seems promising from a performance point of view, and might end up working better in the context of massive numerical computations like what is going on in these game engines.  Of course, region inference theory is still somewhat in its infancy and is nowhere near as developed as garbage collection yet.  But as with static type systems, which have eventually caught up to and (at least in the math/theory realm, if one would disagree in the practical realm) far overtaken 'dynamic' systems, I think that static-analysis driven memory allocation approaches will eventually come into prime as well.
  • Type Inference - I disagree with Sweeney on the "type inference doesn't scale" bit.  As a general statement, I think that's incorrect.  I also think his "examples" in terms of Haskell issued type errors are very straightfoward; I don't understand the problem. It's true that type inference in some realizations may not be as friendly to heavily modularized systems as possible, but there is research being done in this area, and it is premature to make such a sweeping assumption.  Alice ML provides some new approaches to handling the problem of dynamically loaded modules (over networks, etc.), for example.  Haskell too can support dynamically loaded plugins now, and even things like eval.  And then there are other systems like Limbo (the programming language for Inferno, both of which I've been toying with a lot lately) which use static type systems and dynamically loaded modules in a pretty natural way.  Type inference is a pretty fertile area of research and new things are being discovered all the time.  Back when the initial untyped lambda calculus was introduced, all of this crazy static analysis possible today was unimaginable.  We've progressed hugely since then.  I believe the situation with complexity of type systems and modules will resolve itself in a way Sweeney will find acceptable, eventually.  New work in process calculi, as I mentioned above, will probably help this process along.

Anyway... a very cool presentation, IMO, and certainly inspirational for someone who has faith in type theory and static functional programming languages.  If someone as established and influential in the "practical" industry as Sweeney thinks there is a lot to be gained from such approaches, then the future certainly looks a lot less grim indeed.

Programming Languages

Reply #76
Quote
One thing in which i agree with the slides, is that the only solution to rapid-fire concurrency with ten-thousands of objects, is atomic transactions.
[{POST_SNAPBACK}][/a]


Hrmm.. I don't think atomic transactions are the answer.  What is needed is a good mathematical theory of concurrency.  Something where you can prove certain parts of the system won't misbehave in certain ways, and that general characteristics hold across large tracts of code.  Atomic transactions by themselves don't address any of this, they just ensure things won't "blow up" when making a change.  But they don't tell you how you can structure your code in ways that make it massively scalable, and they don't let you prove aspects of behavior by themselves.

The pi-calculus is an example of a theory that already provides the kind of stuff I just described.  It's only be used in inspiration for a few languages in recent years though (the theory itself is only from the 90's IIRC), but I think this will change as time goes on.

Quote
I also agree that exceptions currently are too unpredictable and need more reliable management in the mid-level code.


I think this falls directly in line with the above observations really.  Exceptions, or something better which provides similar functionality in the end, should fit into the theory from the get go.  Also, these things should be very rarely needed in a properly designed system.  If you have to spend all your time dealing with error cases, you have a bad system and a bad theory.

Quote
At the same time, industrial use requires a complex language.


I really don't think industry needs languages as complex and unwieldy as are currently popular.  Java is a great example of this.  It's so big primarily because the core of the system is an ugly kludge that is not easily extended in an intuitive and safe way.  Everything gets thrown into the libraries (including [a href="http://brinch-hansen.net/papers/1999b.pdf]broken concurrency[/url]), which just keep getting bigger and bigger because of the bloated and inexpressive syntax, and the lack of flexibility induced by object-oriented tunnel vision.

But you have languages like Haskell which offer a lot of the same functionality as Java (including libraries for most of the same purposes, such as graphics, windows toolkits, networking, etc.), but are overall much more manageable, and which have remained pretty elegant over years of evolution.  The haskell libraries are simple and easy to understand, yet complete.  Likewise, basic Haskell education can be compressed into a book of under 400 pages, a lot like the original C Reference.  Compare this to the thousand pound tombs that comprise current Java books.

Industry doesn't need "complex languages," it just needs "good languages."  Ever since C++ arrived on the scene, things have just been going downhill.  Maybe that trend will change.

Quote
This in turn could only happen by doing it the firefox/foobar2000 way: making the core language simple, and implementing everything else as extensions/plugins. I'm aware that current complex languages already make use of extensions - however, they currently tend to have a very large standard-library - the core is still much larger and complex than the "simple languages".


What is interesting is that the lambda calculus did this, and it was introduced in the 1930s... heh.  Most modern state of the art programming languages are based on the lambda calculus extended with higher-order type systems (which makes it System-F or System-F-omega now).  You can't really get any simpler than the lambda calculus: it's just a basic mathematical model for computation, with only a few operations, all of which are mathematically tractable.  To get ML or Haskell, you simply take the lambda calculus and add both new syntactic forms to it (e.g., "if" or "let" expressions) and new semantic rules for typing and evaluation.  For Haskell, you change to call-by-need evaluation rather than call-by-value which is the more typical.  There are other "small" details, but this is the essence of it.

This is the way you start with a simple core, and extend it.  I've done this myself in a few programming languages I created recently for experimentation.  Each new capability gets added onto the basic lambda calculus in a very clean way.  You can see an example of a program in my language here if you like (and output of the program here).  The current syntax is a little bit verbose and clunky since I haven't had time to refine it much, and there are way more type annotations than needed in this example since I didn't have type inference implemented initially, but both are easily fixed.  Keep in mind this program uses no "library" either, so it's a lot more complex than a typical program would be if the language were developed into something for everyday usage.  This language is just the basic lambda calculus with a higher-order type system added, then the addition of "if", "let", "letrec", "case", records, tuples, lists, variants, polymorphism, side-effects, numbers, booleans, strings, etc., etc.  If I wanted to, which I didn't really, I could have added objects and well-understood subtyping (for inheritance type stuff) based upon System-F<:.  Overall, it's a good example of a "modular" language, since each time I extend it, I have to change almost nothing from before.

When I look at current language design approaches in most 'dynamically typed' languages, or in popular OO languages, I really kind of have to wonder... It often seems that the people behind these languages have some aversion to learning from the past, and that they have absolutely no interest in mathematical rigor, which is surprising given it's well known benefits in science and engineering in general.

Eventually though, as can be seen in part by the things Sweeney points out in his presentation, programming problems are going to get so complex that this approach is untenable.  Really, I'm surprised it has taken this long.  As a glimpse of things to come, outside of Sweeney's presentation, you can take a look at the Fortress language that Sun is going to be pushing, as well as C# 3.0.  Both have some very functional inspired qualities to them, and both are making a lot more use of the theoretical advantages of type theory.  C# is much less this way than Fortress though, and IMO neither of them are probably radical enough to really last in the long term, but we'll see I guess.

Programming Languages

Reply #77
Quote
Also, these things should be very rarely needed in a properly designed system.  If you have to spend all your time dealing with error cases, you have a bad system and a bad theory.
[a href="index.php?act=findpost&pid=361970"][{POST_SNAPBACK}][/a]

Hmm, i asume you're estimating a scenario where you asume that all the developers are skilled programmers? What about a scenario like this:

- you have a system which describes objects
- the objects have properties
- these properties in turn are described via many "templates" and allow mixins
- objects in turn can be built via templates, which just contain a list of commands on which properties to attach and setting their values
- the objects have AIs
- the AI in turn is a modular plugin-architecture.
- AIs are manufactured via templates which can attach those plugins, the templates allow mixins(sub-templates)
- etc. etc.

The whole template-system is designed so that unskilled 3rd-party scripters and designers can express "ideas" while having low experience in coding.

So, you have a very complex modular system with high concurrency, and the screws and bolts of the system are not trustworthy, because they are submitted by unskilled scripters. Thus, a single bad script can wreak maximum havoc.

Automatic as well as manual reviewing of those "templates" is obviously necessary - yet, you need a good "middle-layer" error-management as a last line of defence(so, basically every part of the scripting-API as well as template-loading, needs error-management) - or am i missing some other possibility here? To clarify: i'm not talking about complex error-cases, but mostly about malformed commands(the scripter just made a mistake).

- Lyx
I am arrogant and I can afford it because I deliver.

Programming Languages

Reply #78
Quote
Hrmm.. I don't think atomic transactions are the answer.  What is needed is a good mathematical theory of concurrency.  Something where you can prove certain parts of the system won't misbehave in certain ways, and that general characteristics hold across large tracts of code. 


AFAIK, Petri nets do that.

Programming Languages

Reply #79
Quote
Quote
Hrmm.. I don't think atomic transactions are the answer.  What is needed is a good mathematical theory of concurrency.  Something where you can prove certain parts of the system won't misbehave in certain ways, and that general characteristics hold across large tracts of code. 


AFAIK, Petri nets do that.
[a href="index.php?act=findpost&pid=362089"][{POST_SNAPBACK}][/a]


Petri nets were an early model that addressed some of this, but they had some serious technical limitations.  Both Milner's Calculus of Communicating Systems (CCS) and Hoare's Communicating Sequential Processes (CSP) were subsequent, more mature models.  The pi-calculus (also from Milner and co.) is the most recent and most advanced of these group of process calculi.  There are some newer calculi derived directly from the pi-calculus, or based upon some parts of it, like the higher-order typed pi-calculus, or the Join-calculus, etc.

Programming Languages

Reply #80
Quote
Quote
Also, these things should be very rarely needed in a properly designed system.  If you have to spend all your time dealing with error cases, you have a bad system and a bad theory.
[a href="index.php?act=findpost&pid=361970"][{POST_SNAPBACK}][/a]

Hmm, i asume you're estimating a scenario where you asume that all the developers are skilled programmers? What about a scenario like this:


Not really, no.  Similar to the ideas Sweeney expressed, you should really have the compiler catch the bulk of errors for you.  Furthermore, your system should be expressive enough to deal with abstractions that don't require you to insert an exception handler every 10 or so odd lines of code.  Something like Monads are a good way to avoid explicit error handling in complex tasks, but there are other ways to do the same thing.  Many of these approaches happen to not be very easy to work with in certain popular languages though because of expressivity problems in these languages.

Quote
- you have a system which describes objects
- the objects have properties
- these properties in turn are described via many "templates" and allow mixins
- objects in turn can be built via templates, which just contain a list of commands on which properties to attach and setting their values
- the objects have AIs
- the AI in turn is a modular plugin-architecture.
- AIs are manufactured via templates which can attach those plugins, the templates allow mixins(sub-templates)
- etc. etc.


I'm not really getting a good visualization of the system from this description.  It's just too vague the way you stated it I think.  It would be better if you had some code or some sort of graphs to describe it.

But anyway, it's not impossible to describe in a "safe" objects communicating in a world, where the objects have certain attributes, including AI, and there is much use of generic programming.

Quote
The whole template-system is designed so that unskilled 3rd-party scripters and designers can express "ideas" while having low experience in coding.

So, you have a very complex modular system with high concurrency, and the screws and bolts of the system are not trustworthy, because they are submitted by unskilled scripters. Thus, a single bad script can wreak maximum havoc.


If a "bad script" can "wreak maximum havoc," then as I said before, that's a very poorly designed system.  Your system should be fault tolerant, but that doesn't mean you need exception spaghetti all over the place.

Quote
Automatic as well as manual reviewing of those "templates" is obviously necessary - yet, you need a good "middle-layer" error-management as a last line of defence(so, basically every part of the scripting-API as well as template-loading, needs error-management) - or am i missing some other possibility here? To clarify: i'm not talking about complex error-cases, but mostly about malformed commands(the scripter just made a mistake).
[a href="index.php?act=findpost&pid=362029"][{POST_SNAPBACK}][/a]


Malformed commands in the script should be caught by the parser (you should compile to some sort of 'bytecode' or more compact representation anyway for performance reasons).  Semantic mistakes in the script should be caught by the type checker.

This leaves mostly only errors possibly involving communication with non-existant objects somewhere down the line.  You have 2 possibilities here: 1) You can deal with this by making the type system of the scripting language slightly more complex so that it is possible to specify and handle failure liable computations without explicit manual intervention, or 2) you can use some sort of communication model which provides its own implicit type of error handling.  Both are similar, and could potentially be viewed as the "same thing" depending on how you look at it.

But really, if you have a system where you have a lot of interacting parts, some of which may be described in an ad-hoc fashion by unskilled developers, then you need to make sure that as much static error detection is performed as possible, so that if their script is bad, it won't even compile and enter into the system.

Programming Languages

Reply #81
Code: [Select]
Gameworld(DB)
  /|\ |
   |  +-->Objectmanager(must explicitely state object-type)
   |          |                         /|\
   |          |                          |
   |          |                     (obj_lookup)
   |         \|/                         |
   +---Object-Instance(Script-API)       |
      /|\                                |
       +--------(obj_manipulation)-----Script
 

Pseudo-Scriptcode:
1) myobject = objmanager.find(objecttype: "Foo", searchpattern: "Bar")
2) myobject.action.kick.attach
3) otherobject = objmanager.find(objecttype: "Bleh", searchpattern: "Blah")
4) myobject.action.kick.use(target: otherobject)


Explanation:
1) script sends lookup command to objectmanger. Objectmanager searches the gameworld-DB, finds the desired object, creates an object-instance of it(which serves as script-API to this individual object) and returns this instance to the script. Script creates a "myobject"-alias for the returned instance.
2) script attaches the action "kick" to myobject. Changes are immediatelly sent to the gameworld-DB.
3) Like "2)" for for "otherobject". It is a different type of object, therefore its available script-API(methods) will differ. But we dont care about this in this example(more on this later).
4) script makes myobject kick otherobject ;)


There are two kinds of errors which may happen here:
1. the script executes API-commands(methods) which dont exist for this particular object-type (reason may be typo, or the scripter's memory was wrong when he tried to remember which commands are availabe for this object). Since type-declaration only happens in the lookup, i can only see two solutions to this: A: built error-handling into the script-API (disadvantage: constant performance-cost). B: code a preprocessor which checks all scripts during server-initialization (disadvantage: not easy to code, makes management of API-changes more complex(every change needs to be done in the API and the preprocessor)).

2. the script may lookup a nonexisting object. Obviously, the scripter should create error-handling for that, but what if he didn't? Then, the safest way to handle the situation would be to stop the script, throw an exception, and propagate the exception up to the event which called the script. This leads to the next issue.

3. If a script aborts in the middle of its code because of an exception, then it may already have done some manipulations to the world, thus having done incomplete work. Two solutions which i could think of: A: create a journal of all actions done by the script and do a rollback if it errors. B: make it so that all script-commands are first written to a temporary buffer - if the script finishes without error, write the temp-buffer to the real world, if it errors then drop the buffer completely. Problem of solution B of course is how to make the buffer "transparent" to the script, so that for the script it looks as if the buffer doesn't exist.

In any case, this all means quite complex error-handling mechanisms - not so much in the scripts, but in the components which execute scripts and the script-API.

Compile-time error-checking obviously can only help with type-errors here. But its helpless in most other cases, because the compiler cannot know beforehand which objects do exist in the gameworld.

- Lyx

edit: to me, the most promising approach appears to be the "temp-buffer"-approach: since scripts do NOT run concurrently, one could make it so that the DB-accessors allow oneself to set the db-writer to "buffer-mode" - this buffer-mode could be activated when a script is executed. Then, whenever an error in the script happens, just throw an exception, let the script-executer catch it and drop the buffer.

When scripts read from the DB, the temp-buffer would be taken into account. The rest of the system would only read from the permanent-DB. Thus, to scripts their commands would seem "instant", while to the rest of the system, events(groups of executed scripts) would be atomic. If a script fails, the entire event never takes place.

The only issue would be how to make it so that during script-DB-reading, the DB-accessors should first look in the buffer, and then in the permanent DB - without too high performance penalties. Need to check SQL-syntax for that - maybe its possible to simply use a join-method for that... would be cheap if the buffer is memory-only. If i can get this to work, then i would get a nice freebie: i dont need to write code to "collect" write commands to bundle them in db-transactions - instead, when the script finishes, i can just bundle everything in the temp-buffer, into a write-transaction to the permanent DB.
I am arrogant and I can afford it because I deliver.

Programming Languages

Reply #82
- post may be deleted -
I am arrogant and I can afford it because I deliver.

Programming Languages

Reply #83
First of all, I have to express my gratitude towards Dibrom for his very informative and thorough replies to this thread. They ultimately got me interested in functional programming!

I've been somewhat active in the field of programming for about ten years now including five years of university. I've been a professional software engineer for about a year now. Before browsing through this thread about a month ago I had very little idea about functional programming, although I have some very faint recollections that I might have heard about lambda calculus in first/second year computer science theory courses. My university mostly used a Pascalish/Javaish pseudo-language for teaching the 'science' portion of computer science and Java for all the rest, much to my discontent. This actually got me to switch universities at one point.

Needless to say, I've never encountered functional programming during my university time besides some vague hints and possibly some brief mentions about lambda calculus. Dibrom's posts encouraged me to finally look FP up, the language features he mentioned were just too interesting to leave the subject alone.

After starting this chore I had a major problem of choice -- what language to start learning? The list seemed endless; Lisp, Scheme, Dylan, Logo, ML, Alice ML, SML, Haskell, O'Caml, Erlang, Clean etc. I wanted to learn just one language and learn it well since I knew I was diving into something totally new and scary. I have no problems learning multiple imperative languages simultaneously, but a radical paradigm shift into a totally new world with several different and possibly contradictory languages might be too much.

Reading up about the languages only confused me more since nobody seems to have any idea what Lisp even is, and the same goes for ML which doesn't seem to have any real implementations, just variants with widely different emphasises and standard libraries. After that came in the 'pure/unpure functional' controversy, which was pretty scary. After Dibrom's posts I decided to pursue Haskell and O'Caml. I browsed through the Haskell 98 Report from the home page, but that was so dense that it made Bjarne Stroustrup's books seem easy. I did try to write some Haskell programs, but I dind't really get it. But there were a lot of things I did get, including what 'pure functional' really means. I decided that a jump into pure functional after ten years of imperative might be too much at once and I wanted an easy start, so I started examining Scala and O'Caml.

With Scala I was continuously being awestruck (Higher order functions? Wow! Pattern matching? Wow!!) and also the OO features seemed to be an order of magnitude better than in typical run of the mill industry leading OO languages. Scala did seem to have some problems though, the .NET integration leaves much to be desired and the syntax seems quite cluttered compared to some other languages like Boo and O'Caml. Then by pure accident I ran into Microsoft's F# which is basically a .NET implementation of O'Caml, or 'F# shares a common compilable subset with the O'Caml language' as Microsoft expresses it. This boosted my efforts towards learning O'Caml, which turned out to be easy since the net is full of O'Caml tutorials for imperative programmers. Then it really struck me -- my god, the syntax of that language is like insanely elegant! I found out I could reimplement a couple of my pet projects (one of them being a complete tagging library supporting APE, ID3v1, ID3v2, Vorbis, Lyrics3 etc) comprising of N lines of C/C++/C# code in about sqrt(N) lines of O'Caml code looking so beautiful it could be submitted into a poetry contest. This combined to the fact that O'Caml is fast makes it a sure winner.

Whereas Scala seems pretty interesting I found O'Caml's (or actually F#'s) clutter-free syntax and type system so nice that I ended up dropping Scala for my personal projects. But I would pick Scala instantly if I had to target JVM for some reason. Personally I have absolutely no interest in JVM but at my workplace where we are basically forced to use JVM, Scala could turn out to be really useful.

Now I feel I might have found my functional cup of tea in O'Caml and Scala. I very much like statically typed languages with type inference which instantly disqualifies the Lisps, and Haskell just... well, it just seems so very elitistic. It's similar enough to O'Caml that I still consider learning it, but it's seemingly total denial of sequental execution of commands (with side effects) is IMHO as bad as the dreaded OO tunnel vision -- computers work by executing instructions in a sequental order so I don't think a programming language should make it as difficult and obscure as possible just to make two or more things happen at a certain order.

But my reservations with Haskell aside, in the end I found myself really enjoying functional programming. Lately when I've been browsing through imperative code I found myself really shunning functions like 'void doIt()' which don't take any arguments and don't return anything, i.e. only rely on side effects. Perhaps I eventually end up being a Haskell user after all

Too bad I (indirectly) work for Nokia and not Ericsson so I can't use functional programming at work. But perhaps it's safer this way -- I'm a total n00b in FP but a pretty seasoned professional with OO. But only for the time being, that is!