Skip to main content
gamedev.net
🔒 Locked

I need some syntax

Started by ToohrVyk Sep 5, 2006 at 3:30 AM 12 replies 1.4k views
Original Post
ToohrVyk
ToohrVyk
In a theoretical experiment in language creation, I'm stuck looking for a simple yet useful syntax that expressed these intended semantics: objects are non-mutable, the only possible operation is to create a new object or a modified copy of an old object. In addition to that, the language uses expression semantics, rather than statement semantics: nothing has a side-effect, so everything is an expression, including the body of a function. The main operation performed in this part of the language is the modification of the fields of these objects. Below is the current syntax for swapping objects in a pair, using the creation of a new object, and the modification of one:

compact Pair { first : int; second : int }

fun (swap_1:Pair (pair:Pair))
  (new Pair
    (first pair.second)
    (second pair.first))

fun (swap_2:Pair (pair:Pair))
  (let t pair.first
    (## (pair.first pair.second) (pair.second t)))
The syntax appears pretty clear to me, but there might be usability issues tied to the usage of the ## command. What do you think? Do you have any other syntax suggestions that are compatible with the semantics, but would be easier to manipulate?
smitty1276
smitty1276
I actually think the OPs syntax is very readable... There seems to be some deviation from "normalcy", though, where it may not be necessary...

Questions/comments...
// Why not "struct" or something familar?... why "compact"? // I'm assuming this is the intention.compact Pair { first : int; second : int }// Function swap_1, returns a NEW Pair, takes a Pair// So incoming pair is by reference (based on next function)?fun (swap_1:Pair (pair:Pair))  (new Pair    (first pair.second)    (second pair.first))// For consistency, shouldn't this be (let t:int pair.first)??fun (swap_2:Pair (pair:Pair))  (let t pair.first    (## (pair.first pair.second) (pair.second t)))


And what is with ##? Why not something like...

(set (pair.first pair.second) (pair.second t) (somevar someval) ... etc)

What does the ## actually mean?
ToohrVyk
ToohrVyk
The main reason I decided not to use statement syntax is because this mini-language with no side-effects is intended to be a part of a larger language with side-effects. The larger language works as would be expected: pointers to mutable classes, assignment, side-effects, everything that makes your average imperative program tick.

smitty1276: I use the term "compact" to differentiate from the "class" system of the larger language: they are, by all means, more compact than classes. A class may reference another class, which may reference it back, and so on. By contrast, a compact IS the sum of its components, and never contains anything outside itself. The objective is to completely disconnect programming in the low-performance state-based language which relies on side-effects from the referential-transparent strongly-typed language that happens to be thread-safe and easier to optimize.

Passing by reference or by value does not really make any difference, since objects are not mutable (in practice, everything is passed by const garbage-collected reference).

Also, the :Pair type specification is only required in function signatures, both for readability and for more complex language capabilities.

The reason ## is used is because of the existence of the single-modification #, which operates as (# field value). By contrast, ## is the multiple-modification value as (## (field value) (field value) ...). Although it might probably be more intelligible, as the AP suggests, to use := for both and use built-in recognition in the grammar.

AP: statement semantics are used elsewhere. What I don't want is people trying to use statement semantics outside the state-based part of the language. What I'm trying to avoid is the return keyword, because of the following situation:

// dot a vector with another, possibly with a shufflefun (hnd_dot:real (shuffle:bool) (v1:vector3) (v2:vector3))  (let v2    (if shuffle      (let t (v2.x)        (:= (v2.x v2.y) (v2.y v2.z) (v2.z t)))      (v2))    (+ (* v1.x v2.x) (* v1.y v2.y) (* v1.z v2.z)))


Introducing a return keyword would introduce the false impression that it is the function which returns, instead of simply defining the value of the multiple-modification expression. Especially since the state-based part of the language obviously has a return statement.
ToohrVyk
ToohrVyk
Quote:
Original post by Anonymous Poster
So whatever syntax you use to symbolize object field update, if you insist on doing it in a purely functional manner things like conditionally updating fields will probably be more awkward because you'll have to explicitly manage the order of the intermediate calculations using lets and ifs. This can quickly turn into a superindented nightmare.


I agree with your suggestions, and will probably use statements to correct this problem.

Quote:
(I don't really understand what (v2)) is supposed to mean, it'd error out in common lisp or scheme because v2 is not a function, but I understand what you're trying to say):


Any value is also function without arguments.
ToohrVyk
ToohrVyk
There remains a problem, when mixing the state-based language with the expression-only part, such as below:

proc Hit(a:Ball,b:Paddle) {  a.movement <-     (if b.sticky       (b.movement)      (:=         (a.position (position a now))         (a.time now)         (a.direction (reflect a.direction b.side)))  b.solidity <- (--b.solidity)}


In such situations, using return would conceivably affect the procedure, instead of the sub-expression used. Moving the if-clause out of the expression and into the procedure would result in slightly different behavior (since expressions are thread-safe and easily optimized, but procedure code hardly is).
ToohrVyk
ToohrVyk
Sorry about the code, I got mixed up. It should have been:
proc Hit(a:Ball,b:Paddle) {  a.movement <-     (let m a.movement      (if b.sticky         (b.movement)        (:=           (m.position (position m now))           (m.time now)           (m.direction (reflect m.direction b.side))))  b.solidity <- (--b.solidity)}


Also, while procedures cannot return something, they can be forced to return; to cease their execution.
ToohrVyk
ToohrVyk
// This(:=   (m.position (position m now))   (m.time now)   (m.direction (reflect m.direction b.side)))// Is equivalent to this(:=   (:=     (:= m.position (position m now)).time now).direction    (reflect m.direction b.side)))// Which is more or less equivalent to:(let m  (:= m.position (position m now))  (let m    (:= m.time now)    (let m      (:= m.direction (reflect m.direction b.side))      m)))


That is, a modification sequence has a value equal to the modified copy that it creates. As for the now variable, oddly enough it is the only variable that is actually safe as far as referential transparency goes, because it is passed as an argument to the functional parameters.

In essence, code knows when it will be called (because this is how scheduling works), so the now value is known in advance for every single line of code. What isn't known is the value of the various references when the line of code is reached. So, code without side-effects can be executed in advance whenever most of its properties are known, and it can even be pre-empted if the values of references are suspected not to change. By contrast, this is not possible if there are side-effects to be reversed.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.