C++ sucks for games

R

rmagere

Pascal Bourguignon said:
Not sufficient:

1 + (2 * 2 / 4) - (1 * 1)

Do you expect: 1 or 0 ?
<source language=c>
1 + (2 * (2 / 4)) - (1 * 1), /* ==> 0 */
1 + ((2 * 2) / 4) - (1 * 1) /* ==> 1 */
</source>

I am sorry I am thick but how do you get 0?
1 + (2 * (2/4)) - (1 * 1) ==>
1 + (2 * 0.5) - 1 ==>
1 + 1 - 1 ==>
1

Now if the equation was A + (B / C / D) - (E * F) now the problem would
exist as / is not associative.
So
1 + (2 / (2 /4)) - (1 * 1) ==> 4
while
1 + ((2 / 2) / 4) - (1 * 1) ==> 0.25

By the way I am a Lisp user not a C++ user
 
I

Ingvar

rmagere said:
I am sorry I am thick but how do you get 0?
1 + (2 * (2/4)) - (1 * 1) ==>
1 + (2 * 0.5) - 1 ==>
1 + 1 - 1 ==>
1

This is C, remember.

1 + (2 * (2 / 4) ) - (1 * 1) ==
1 + (2 * 0) - (1) ==
1 + 0 - 1

//Ingvar
 
P

Petter Gustad

Maahes said:
For protecting your implementation from the other programmers.
If you don't protect your variables and strictly define your interfaces, and
limit their power, you'll find the other programmers will completely
misusing your code. Then when you go to rewrite it to provide new features,
you'll find you can't because all the variables you want to trash are being
used in critical parts of multiple projects.

I'd really like to know what team projects the Lisp posters have worked on,
and what team games they have completed this way? Some of these things are
only learnt from doing things in a team environment on a project with a
dozen people in a couple of projects all accessing your engine interfaces
without direct guidance from you.

You assumed that the poster asked the question because he /did/ /not/
/know/ what to use access specifiers for. I think the poster wanted to
know what /you/ use access specifiers for to come up with a reasonable
example since there are several methods for protecting data, functions
and methods in Common Lisp.

In Common Lisp you normally use packages to protect the implementation
from other programmers. You can protect a slot (i.e. a member
variable) by specifying a reader accessor to make it read-only.
Further, you can use closures to hide the data so that nobody (not
even yourself) can access the data other than through the specified
functions, e.g.

(let ((stack nil))
(defun stack-push (element)
(push element stack)
element)
(defun stack-pop ()
(pop stack))
)

In this case there is no way you can access the stack other than
stack-push and stack-pop. This has nothing to do with CLOS (the Common
Lisp Object System). It's /one/ method for access protection.

Petter
 
R

rmagere

Ingvar said:
This is C, remember.

1 + (2 * (2 / 4) ) - (1 * 1) ==
1 + (2 * 0) - (1) ==
1 + 0 - 1
Ah so is / like truncate?
i.e.
1 + (2 * (2 / 4)) - (1 * 1) == (+ 1 (* 2 (truncate 2 4)) (- (* 1 1))) == 0
while
1 + ((2 * 2) / 4) - (1 * 1) == (+ 1 (truncate (* 2 2) 4) (- (* 1 1))) == 1
 
G

Greg Menke

Maahes said:
For protecting your implementation from the other programmers.
If you don't protect your variables and strictly define your interfaces, and
limit their power, you'll find the other programmers will completely
misusing your code. Then when you go to rewrite it to provide new features,
you'll find you can't because all the variables you want to trash are being
used in critical parts of multiple projects.

If controlling well-defined interfaces is important, its easy enough
to create a Common Lisp package with the interface functions exported.

The big lie with C++ is that the access specifiers actually enforce
access- its trivial to typecast an instance's pointer and arbitrarily
manipulate the object's members. Its more correct to say the access
specifiers provide some naive compile-time limits on how instances of
a class can be manipulated. If you need to protect your object
internals from other programmers, I think you'll need a more robust
technique.

The C++ answer to this is the programmer <should> honor the intent of
the specifiers, conforming access to the given interface- which is
exactly what Common Lisp is telling you when an interface is exposed
via a package.

Gregm
 
I

Ingvar

rmagere said:
Ah so is / like truncate?
i.e.
1 + (2 * (2 / 4)) - (1 * 1) == (+ 1 (* 2 (truncate 2 4)) (- (* 1 1))) == 0
while
1 + ((2 * 2) / 4) - (1 * 1) == (+ 1 (truncate (* 2 2) 4) (- (* 1 1))) == 1

It's a type-conserving "divide", with some form of type-contagion.

int / int => int
int / long => long (I believe)
int / float => float
int / double => double

So given integers (and an undecorated 2 or 4 is an int), / is indeed
like TRUNCATE.

//Ingvar
 
M

mikel

Maahes said:
For protecting your implementation from the other programmers.
If you don't protect your variables and strictly define your interfaces, and
limit their power, you'll find the other programmers will completely
misusing your code. Then when you go to rewrite it to provide new features,
you'll find you can't because all the variables you want to trash are being
used in critical parts of multiple projects.

I'd really like to know what team projects the Lisp posters have worked on,
and what team games they have completed this way? Some of these things are
only learnt from doing things in a team environment on a project with a
dozen people in a couple of projects all accessing your engine interfaces
without direct guidance from you.

If any Lisp programmers have worked on games in Lisp, please let me know who
you are so I know which posters are just being theoretical and which are
speaking from experience. This thread is getting to big to be reading
everyones articles when, I suspect, most of the Lisp programmers have only
ever experienced solo lisp programs, or never taken a group Lisp project to
completion...

Team programming in Lisp is eminently doable. I worked on four sizeable
team projects using Lisp for development:

1. GATE was a three-person, two year project that developed a
knowledge-based, network-aware automated testing system that was used to
find hundreds of pre-release bugs in Mac System 7. It was written in
Common Lisp.

2. The unnamed first version of the Newton operating system was a
60-person, four year project written mostly in Ralph, a Lisp dialect
that later evolved into the Dylan language. (The Dylan compiler and IDE
were written in Common Lisp.)

3. Bauhaus was the second Lisp-based version of the Newton operating
system, also written in Ralph (a third version, written in C++ and
NewtonScript, and chosen for non-technical reasons, was the one
shipped). It was a five-person, two-year project.

4. SK8 was a 10-person, five-year project to develop a general-purpose
multimedia authoring system. It was used to build perhaps a dozen
projects at Apple, some of which were later productized and some of
which were given away to educational projects. SK8 itself was eventually
released as freeware. Sk8 was written in Common Lisp.

None of them were games (though SK8 was used to build several games and
several applications that incorporated interactive video elements).
 
P

Peter Seibel

[Followups set to comp.lang.lisp]

Gerry Quinn said:
It looks like a good primer. One comment I would make, though, is that
at a glance everything seems to be a parser of some sort. A touch of
hammer and nail syndrome?

Hmmm. If it is weighted toward one kind of program it probably has
more to do with my interests and the set of things that can be
explained without assuming a lot of prior domain knowledge.

At any rate there are quite a few things--most of them I'd say--that
aren't parsers: an in-memory database, a test framework, a Shoutcast
server, a web interface for the Shotcast server, an HTML generation
library. In fact there are only two parsers--the binary data parser
from chapter 20 is used to write the ID3 parser in chapter 22. And if
I get a chance to write chapter 28 (a bit at risk at the moment for
time and space reasons) it'll be about text parsing, a la Yacc, ANTLR,
etc.

-Peter
 
C

chris

Maahes said:
C: rabbit_jump(rabbit, 3)





C: rabbit_jump(rabbit, 3)
What is this? are you saying that the class rabbit has a static member
function that takes a rabbit and a 3? I'm not sure why you would want
such a thing..

Also are you saying in lisp when I write (jump rabbit 3) it's impossible
to tell if I'm calling a member function of Rabbit that takes a single
parameter, or a general function that takes two parameters? Is that
really an advantage?

If you don't want member functions in C++ of course, you can just not
write them. I find the distinction between jump(rabbit,3) and
rabbit.jump(3) helpful.

Chris
 
M

mikel

chris said:
What is this? are you saying that the class rabbit has a static member
function that takes a rabbit and a 3? I'm not sure why you would want
such a thing..

Also are you saying in lisp when I write (jump rabbit 3) it's impossible
to tell if I'm calling a member function of Rabbit that takes a single
parameter, or a general function that takes two parameters?

If you mean to ask whether the syntax tells you whether it's a method
call or a non-method call, it doesn't.
Is that
really an advantage?

I think so; generic functions and ordinary functions are both appliable
objects that take arguments; I don't see any special reason why they
should have different syntax. Macro calls and uses of other special
operators also use the same general syntax:

(operator arg1 arg2 ... argn)

As an aside, in Lisp there is no such thing as a member function:
methods are not members of classes, because the particular method may be
selected by the types (or values) of any number of parameters. For example:

(defmethod foo ((x square)(y integer))
;; do first thing...
)

(defmethod foo ((x square)(y float))
;; do second thing...
)

(defmethod foo ((x circle)(y integer))
;; do third thing...
)

(defmethod foo ((x circle)(y float))
;; do fourth thing...
)

(defmethod foo ((x circle)(y (eql 5)))
;; do fifth thing...
)

[The last example shows an 'eql specializer' -- CLOS methods may be
selected by particular values, rather than by type.]
 
J

Jock Cooper

Gerry Quinn said:
Gerry Quinn said:
I disagree - it is in fact rare for code to be changed per se. What
would be the point in writing code whose only purpose is to be written
over? Whether original code is erased or not is completely irrelevant
to the issue of whether code is self-modifying. The *program* is still
self-modifying.

Because I might get tired of typing something like:

(let ((addr-1 (get-html-value "addr-1"))
(addr-2 (get-html-value "addr-2")) [--]
so I write a macro to transform some code and now I can
write thusly:

(with-form-values (addr-1 addr-2 city state zip (name "username")
(other-value "other" some-function))
...some code...)

which is transformed into the top code.

Now I have eliminated a source of typos and made the code much more
readable. Note that a function will not work for this because I am
establishing lexical bindings for "...some code..."

Sure, that's a reason why you might want to use this. And I think, if
you are making the point that this should not be called 'self-modifying
code' in the proper sense, you do have a point - you are manipulating
strings in a way that might be handled elsewise in another language. If
you look at the above, it's really data you are manipulating, not code.

No, I am not manipulating strings or data, I am manipulating code.
Although C macros work on strings, Lisp macros do not, despite the
fact that the Lisp source code does reside in a file. This is because
the Lisp reader parses the expressions for me and hands my macro a
data structure. This particular example is rather simple, but there
are plenty of other uses of macros in which a code 'tree' is
traversed, examined and transformed. This is far more powerful than
simple string substitution as C has.

Another example:

(ITER (WITH specials = (get-sorted-specials unsorted-list))
(WITH result = '())
(FOR (sym . str) IN specials)
(FOR char0 = (char str 0))
(FOR char1 = (and (= 2 (length str)) (char str 1)))
(FOR lchar0 PREVIOUS char0 INITIALLY nil)
(FOR pair = (assoc char0 result :test #'char=))
(if (null pair)
(push (setf pair (cons char0 nil)) result)
(case lchar0
(#\a (COLLECT char0 into a-list))
(#\b (COLLECT char0 into b-list))))
(FOR new = (cons char1 sym))
(push new (cdr pair))
(FINALLY (return result)))

ITER is a macro, and it does not get a big string to process. It gets
a forms (fyi a form is either an atom or a list of atoms and/or
forms), which it examines and transforms to build the resulting code.
Here, some of those forms contain symbols defined by ITER, some are
'ordinary' lisp. The ITER symbols can be anywhere in the code block,
ITER will find them and handle them properly in the result. I
capitalized them above. Notice the COLLECT words down in the IF's
else form. Lisp macros are so powerful because the macro operates on
the parse tree of the code. It is trivial for ITER to locate the
COLLECT words and insert the code for them.

Again I also want to stress that I can't use a function to do this type
of abstraction; I want all of the resulting code (including transformed
bits and other bits just used as is) to be in the same lexical scope.

Below is what the ITER macro expands into for the above code. I show
you all this to give you an idea of the amazing amount of crap that
can be cut out of your programs if you use macros to abstract away the
scaffolding.


(LET* ((SPECIALS (GET-SORTED-SPECIALS UNSORTED-LIST))
(RESULT 'NIL)
(#:LIST36 NIL)
(SYM NIL)
(STR NIL)
(CHAR0 NIL)
(CHAR1 NIL)
(LCHAR0 NIL)
(PAIR NIL)
(A-LIST NIL)
(#:END-POINTER37 NIL)
(#:TEMP38 NIL)
(B-LIST NIL)
(#:END-POINTER39 NIL)
(NEW NIL)
(#:pOST-SAVE-CHAR0-40 NIL))
(BLOCK NIL
(TAGBODY
(SETQ #:LIST36 SPECIALS)
(SETQ LCHAR0 NIL)
(SETQ #:pOST-SAVE-CHAR0-40 NIL)
LOOP-TOP-NIL
(IF (ATOM #:LIST36) (GO LOOP-END-NIL))
(LET ((#:G3840 (CAR #:LIST36)))
(SETQ SYM (CAR #:G3840))
(SETQ STR (CDR #:G3840))
#:G3840)
(SETQ #:LIST36 (CDR #:LIST36))
(PROGN (SETQ LCHAR0 #:pOST-SAVE-CHAR0-40))
(SETQ CHAR0 (CHAR STR 0))
(PROGN (SETQ #:pOST-SAVE-CHAR0-40 CHAR0))
(SETQ CHAR1 (IF (= 2 (LENGTH STR)) (CHAR STR 1)))
(SETQ PAIR (ASSOC CHAR0 RESULT :TEST #'CHAR=))
(IF (NULL PAIR)
(SETQ RESULT (CONS (SETQ PAIR (CONS CHAR0 NIL)) RESULT))
(LET ((#:G3841 LCHAR0))
#:G3841
(IF (EQL #:G3841 '#\a)
(PROGN
NIL
(PROGN
(SETQ #:TEMP38 (LIST CHAR0))
(SETQ #:END-POINTER37
(IF A-LIST
(SETF (CDR #:END-POINTER37) #:TEMP38)
(SETQ A-LIST #:TEMP38)))
A-LIST))
(IF (EQL #:G3841 '#\b)
(PROGN
NIL
(PROGN
(SETQ #:TEMP38 (LIST CHAR0))
(SETQ #:END-POINTER39
(IF B-LIST
(SETF (CDR #:END-POINTER39) #:TEMP38)
(SETQ B-LIST #:TEMP38)))
B-LIST))))))
(SETQ NEW (CONS CHAR1 SYM))
(LET* ((#:G3844 NEW)
(#:G3843 PAIR)
(#:G3842 (CONS #:G3844 (CDR #:G3843))))
(COMMON-LISP::%RPLACD #:G3843 #:G3842))
(GO LOOP-TOP-NIL)
LOOP-END-NIL
(RETURN RESULT))
NIL))

However, there are those who feel that defining new syntax is too
powerful a feature for a language, they should stay clear of this sort
of thing.

Downloaded your 'UltraFractal'. I was curious to see if it was written
in Lisp, but it doesn't appear so. [Not that that invalidates anything
you say.]

Actually Ultra Fractal was written by Frederik Slijkerman and is available
(as you must have found) at www.ultrafractal.com. I just used it to create
the various images and animations on my site. I believe UF is written in
Delphi.
 
G

Greg Menke

chris said:
What is this? are you saying that the class rabbit has a static member
function that takes a rabbit and a 3? I'm not sure why you would want
such a thing..

Also are you saying in lisp when I write (jump rabbit 3) it's
impossible to tell if I'm calling a member function of Rabbit that
takes a single parameter, or a general function that takes two
parameters? Is that really an advantage?

The function 'jump' is generic, for each class that cares about having
an action for jump, the programmer defines a function named that with
the parameters specialized accordingly so Lisp will call the proper
one. Its analagous to function/operator overloading in C++. In
general, you cannot have regular functions named the same as generic
functions, so there is no confusion about which function is going to
be called.

There are no "member functions" in Lisp, all functions exist outside
the definition of classes. That will sound strange to people only
familiar with the C++ style of OO, but it works very nicely once you
get used to it.

Gregm
 
J

JKop

The big lie with C++ is that the access specifiers actually enforce
access- its trivial to typecast an instance's pointer and arbitrarily
manipulate the object's members. Its more correct to say the access
specifiers provide some naive compile-time limits on how instances of
a class can be manipulated. If you need to protect your object
internals from other programmers, I think you'll need a more robust
technique.

Next you'll be complaining that TV isn't water-proof.

Putting stuff in the "private" or "protected" section is purely for ease of
use - it's *not* intended to "protect" the internals of the class.


-JKop
 
C

Cameron MacKinnon

Maahes said:
Oh god, its almost reads like your saying Lisp requires less commenting :)

I can't possibly mean this, so I'm sure I've misinferred here..
If anything, Lisp looks more like assembly, where you would expect a lot of
commenting on every single line...

I just ran a quick comment count on the same code that I used elsewhere
in this thread (CMUCL's source tree in the Lisp corner, VNC in the other
corner). Lisp weighs in at:

Characters: total 16061565, comment 3020763 (18.8%)

versus

Characters: total 9947254, comment 2331753 (23.4%)

25% more flab on the challenger.
 
J

Jerry Coffin

[ ... ]
Why bother arguing about shift/reduce and reduce/reduce conflicts and
the de facto and de jure meanings of e.g. a[x+++y] = x+++y; - every
language has a wart or two in the corner cases, and C defenders are just
going to reply that such things are bad style and not done anyway.

Those weren't the cases I was talking about -- in fact, none of what
you've cited above is really an ambiguity either.

There are, however, ambiguities to be found, such as:

a b(c);

In C++, this could be either a definition of b as an object of type a,
with c as an initializer, OR it could be a declaration of b as a
function that returns an a and takes a c as a parameter. There are a
number of other examples along this line, but (at least from a
viewpoint of pure syntax) these are real ambiguities. C++ has a fairly
simple rule about how to deal with all of them, but even though the
rule itself is simple, implementing it is anything but, and is
semantic, not syntactical -- the syntax itself really IS ambiguous.

[ ... ]
Say, speaking of citing, don't you still owe us one from your claim that
"[t]ests have repeatedly shown that C is far more readable than Lisp."

Where do you find a mention (by anybody) of citing anything?

In any case, I never said or implied that I'd provide citations. I
said those were results from tests that I conducted. I conducted the
tests for a (temporary) employer, so I'm pretty sure I couldn't
(legally) publish the results without their permission. Since that
company went under in the .com bust I'm not even sure whose permission
I'd need at this point.

In the end, I'm reasonably certain other tests have been done, but if
you want them, you'll have to find them yourself -- until or unless
there's some change in Lisp syntax that invalidates what I've already
done, I'm not much more interested in searching for such results than
I am in searching for new proofs that the world is round.
 
R

Ray Blaak

mikel said:
I think so; generic functions and ordinary functions are both appliable
objects that take arguments; I don't see any special reason why they should
have different syntax. Macro calls and uses of other special operators also
use the same general syntax:

(operator arg1 arg2 ... argn)

The reason to have a difference syntax is to better model how humans think in
certain situations.

I have used Lisp's syntax. It is nicely general. Ada has a similar syntax for
OO method calls.

The problem is that very often I want to think about asking individual objects
to do certain behaviour. Saying:

obj.doSomething(withData)

clarifies that better than:

doSomething(obj, withData)

It is a mental organization thing.

Essentially, class scopes can indeed be useful.

Consider a record or struct syntax in the standard langages: data.field

Mind you, Lisp is consistent here too, calling a field accessor function/macro
on the data.

There is no question that Lisp's approach is more general, especially given
generic methods. Sometimes, the class scope/focus of things is useful.

E.g. in Lisp or scheme, something like:

(obj doSomething withData)

Don't know how to do that with macros, however. The approaches I have seen
usually force things like

(obj 'doSomething withData)

with the implied inefficient dynamic lookup of methods.
 
M

mikel

Ray said:
The reason to have a difference syntax is to better model how humans think in
certain situations.

I have used Lisp's syntax. It is nicely general. Ada has a similar syntax for
OO method calls.

The problem is that very often I want to think about asking individual objects
to do certain behaviour. Saying:

obj.doSomething(withData)

clarifies that better than:

doSomething(obj, withData)

It is a mental organization thing.

Fair enough. This is, however, just syntactic sugar, and if you really
want it you can build it with a macro. The Lispy result might look like,
for example,

(send obj do-something with-data)

Because the macro gets expanded to

(do-something obj with-data)

there is no difference, apart from the differenet surface syntax, which
is provided because the user happens to like it for this case.

As an aside, Dylan began as a Lisp dialect and later evolved an infix
syntax; for a while it had both syntaxes, and the syntax

obj.doSomething(withData)

was exactly equivalent to

doSomething(obj,withData)

or

(doSomething obj withData)

Indeed, it was fully general and worked the other way; anything that
could be called like this:

foo(bar)

could also be called like this:
bar.foo()
Essentially, class scopes can indeed be useful.

That might be true, but doesn't mean the same thing. Assuming that by
'class scopes' you mean that you want objects that contain methods as
data elements, you can do that in Lisp as well, in a couple of different
ways, but if you want a comprehensive system of function-calling,
inheritance, and the like that resembles a Smalltalk-like object system
or a C++-like object system, then you have to do a little more work
defining metaclasses and method combinations and so on.
 
M

Marco Baringer

Ray Blaak said:
Sometimes, the class scope/focus of things is useful.

E.g. in Lisp or scheme, something like:

(obj doSomething withData)

[untested]

(defmacro with-svo (var &body body)
`(macrolet ((,var (gf-name &rest args)
`(,gf-name ,',var ,@args)))
,@body))

(with-svo (obj)
(obj doSomething withData))

this will only "work" when you work in your own world because the
entire clos culture thinks differently and you'll have issues (none of
them technical) when working with others.

p.s. - the above is only a short read macro away from:

(enable-svo-syntax)

[obj.doSomething withData]

--
-Marco
Ring the bells that still can ring.
Forget your perfect offering.
There is a crack in everything.
That's how the light gets in.
-Leonard Cohen
 
G

Greg Menke

JKop said:
Next you'll be complaining that TV isn't water-proof.

Putting stuff in the "private" or "protected" section is purely for ease of
use - it's *not* intended to "protect" the internals of the class.

Oh I'm not complaining- its just how C++ is. My point is that access
specifiers are essentially no more than a suggestion to the programmer
about how an interface should be used.

But I don't think ease has anything to do with it. There is a
suggestion of access enforcement via the specifiers that is not backed
up by other parts of the language, and this is not usually discussed
alongside the merits of specifiers.

Gregm
 
S

Sashank Varma

What is it that you use access specifiers for?

For protecting your implementation from the other programmers.
If you don't protect your variables and strictly define your interfaces, and
limit their power, you'll find the other programmers will completely
misusing your code. Then when you go to rewrite it to provide new features,
you'll find you can't because all the variables you want to trash are being
used in critical parts of multiple projects.[/QUOTE]

Okay, so access specifiers are for protection.

JKop said:
Putting stuff in the "private" or "protected" section is purely for ease of
use - it's *not* intended to "protect" the internals of the class.

Okay, so access specifiers are *not* for protection.

What are access specifiers for again?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,202
Messages
2,571,058
Members
47,668
Latest member
SamiraShac

Latest Threads

Top