ooc-lang / rock

:ocean: self-hosted ooc compiler that generates c99
http://ooc-lang.org/
MIT License
404 stars 40 forks source link

Add safe navigation operator (syntax needed) #810

Open alexnask opened 10 years ago

alexnask commented 10 years ago

Description of the C# safe navigation operator ?. provided by @davidhesselbom

While you're at it, have you thought of "?." ? See this: http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx
Basically, instead of 
// variation 1
var item = this.Parent;
item = (item == null) ? null : item.child;
item = (item == null) ? null : item.child;
var g1 = (parent == null) ? null : item.child;
if (g1 != null) // TODO

// variation 2
var g1 = (Child)null;
var item = this.Parent;
if (item != null)
{
    item = item.Child;
    if (item != null)
    {
        item = item.Child;
        if (item != null)
        {
            g1 = item.Child;
        }
    }
}
if (g1 != null) // TODO
you can do
var g1 = parent?.child?.child?.child;
if (g1 != null) // TODO
davidhesselbom commented 10 years ago

Would g1 = parent ? child ? child ? child work, or would it break the ternary operator?

fasterthanlime commented 10 years ago

Well, what would be the semantics of:

a := b ? c ? d : e
alexnask commented 10 years ago

Also, that would mean that we must force a space before the ? operator (becuase ? is valid in ooc identifiers), which I can't help but think is bad practice.

fasterthanlime commented 10 years ago

@shamanas That's already the case with the ternary operator. I had fun making that work in NagaQueen...

On Fri, Aug 22, 2014 at 1:44 PM, Alexandros Naskos <notifications@github.com

wrote:

Also, that would mean that we must force a space before the ? operator (becuase ? is valid in ooc identifiers), which I can't help but think is bad practice.

— Reply to this email directly or view it on GitHub https://github.com/fasterthanlime/rock/issues/810#issuecomment-53050942.

alexnask commented 9 years ago

@fasterthanlime @davidhesselbom

I still think this operator would be a great idea, how about using <space>? ?
For example:

// All fine!
g1 := parent ?child ?child ?child ?? getDefault()

g1 := parent ? child ? child ? child // This doesn't parse
davidhesselbom commented 9 years ago

Well... parent ?child makes it look like I'm asking whether child, not parent, is null. And letting the parser be so sensitive to where I put the space is kind of scary. Almost like bash :fearful:

alexnask commented 9 years ago

Both good points.

Perhaps actually using ?. is the solution (allowing spaces in between), although the dot does make the user think the operator is similar to the dot operator, when it obviously isn't.

I'm at a loss again I guess :/

davidhesselbom commented 9 years ago

Me too. I don't think ?. is a good idea either, because that's not what the dot usually does in ooc. In C# it makes a lot of sense, though! I think the problem to begin with is the fact that ? is allowed to terminate identifiers (which isn't a problem in itself, just unconventional, and leads to problems years later (i.e. now)).

alexnask commented 9 years ago

Yeah, I can't think of any "good" syntax.

I guess I'll just leave it to rest for now.

refi64 commented 9 years ago

What about using <$>, sort of like Haskell? Or just $?

davidhesselbom commented 9 years ago

"We ran out of ? so we decided to run with $ instead"

alexnask commented 9 years ago

I'd like something that involves ?, as it is already used by the nullco operator and I think it somewhat naturally brings up null checking.

It appears it can't be used though, so $ could do, it is somewhat noisy though, I wouldn't want ooc to suddenly turn into perl :P

refi64 commented 9 years ago

Ok then...what about \? Like K?

davidhesselbom commented 9 years ago

Or .

(I'm joking)

alexnask commented 9 years ago

I personally think ?> would be an OK choice (although foo?>bar can be confusing, assuming foo? and bar are variables) and I prefer $ over \ (\ is already used to continue a statement on another line, like C, btw).

In the end, I think we all agree that it is a really nice feature but it's up to Amos to take this decision.
I think we can live without it for now, until a decision is made, it is relatively simple to implement once we do so (trivially translated into a series of ternary expressions)

davidhesselbom commented 9 years ago

Yeah, it's just frustrating to know that all that's keeping it from becoming something we can use is that we can't figure out how to write the operator. It's like would-be parents deciding not to have a baby until they've figured out a good name for it.

alexnask commented 9 years ago

@davidhesselbom
I can hack up ?> on one of my branches if you wish, then merge it with its final syntax when we agree upon it.

I'm kinda bored anyways :)

All you will have to do when we change it is write a trivial script to convert the operator to its new syntax.

davidhesselbom commented 9 years ago

Assuming ?> is unique enough, haha.

vendethiel commented 9 years ago

also add a <?rock operator, if you're to add ?> :P

alexnask commented 9 years ago

Settled for $ for the moment, I've almost got working code right now, for some reason the replaced comma sequence doesn't get resolved at the moment.

@vendethiel
Sounds great and really convenient to inline ooc code into HTML, I will look into it! (/s, obviously :P)

alexnask commented 9 years ago

All the code is on my forks at nagaqueen/safe_navigation and rock/safe_navigation if anyone is interested.

alexnask commented 9 years ago

I wasn't resolving the root expression...
I nearly lost my mind debugging something that simple.

Seems to be working fine now!

Here is an example:

Bar: class {
    baz: String

    init: func (=baz)
}

Foo: class {
    bar: Bar

    init: func (=bar)
}

// Try this line out with null in different places
foo := Foo new(Bar new("yup"))
(foo $ bar $ baz ?? "nope") println()
alexnask commented 9 years ago

There may be some little syntactic issues, the one I found right away is that this:

foo $ bar baz

Doesn't work.
Imho, you should be able to "break" the safe navigation at any point without parenthesis (although it really doesn't make sense to do so)

By the way @fasterthanlime, do you have issues with the syntax, or should I merge this into master once I've worked out the details and added some tests?

davidhesselbom commented 9 years ago

Imho, you should be able to "break" the safe navigation at any point without parenthesis (although it really doesn't make sense to do so)

What if you're trying to access covers inside an object? On that note, what should happen if you try to use the safe navigation operator on a cover?

alexnask commented 9 years ago

@davidhesselbom
Rock will compile safe navigation expression blindly into exactly those things:

So (and I'm just guessing here) I think it would fail at C compiletime, because operator type checking is really sparse in rock (although I do think I remember seeing something about compatible pointer types in Comparison.ooc)

Also, what I mean when I say it doesn't really make much sense to break the safe navigation is that you are probably expecting the expression to return null, or at least you know there is a real possibility, so safe navigating somewhere and then blindly taking a member seems like suicide.

I do think this is a bug though and I will fix nagaqueen to make such accesses possible without parentheses.

davidhesselbom commented 9 years ago

Well, just because foo is likely to be null doesn't mean that bar or baz are likely to (if foo isn't null). It doesn't seem like suicide to me any more than do all the other times you use an object.

alexnask commented 9 years ago

Ah, I see what you're saying now, I am indeed mistaken.

That's what happens when I answer to posts 5 minutes after I wake up without having drunk my coffee _

alexnask commented 9 years ago

Covers do fail with a nice error message " Invalid comparison between operands of type Foo and Pointer", I am pleasantly surprised.

davidhesselbom commented 9 years ago

Good :)

davidhesselbom commented 9 years ago

Still,

Foo: class {
    x: Float
    init: func (=x)
}

foo: Foo = null
y := foo $ x

What should happen here? What type is y and what is its value?

alexnask commented 9 years ago

y should be set to null

The expression is translated to:

Foo: class { ... }

foo: Foo = null
safeNav1 : Foo

y := (safeNav1 = foo, foo != null ? foo x : null)
alexnask commented 9 years ago

Oooh, wrong again I guess.

alexnask commented 9 years ago

This doesn't compile because Float and null are incompatible but this is something we can't work around, cover vs class havs always been a weird part of the ooc typesystem.

davidhesselbom commented 9 years ago

What if we don't know at compile time whether foo is null?

alexnask commented 9 years ago

I should probably make it a nice error though, " Using different types in a ternary expression is forbidden. Find another way :)" doesn't really tell us anything in this case.

davidhesselbom commented 9 years ago

Okay, so, basically, this will never work if you want the result to be a cover, unless ooc starts supporting nullable types? What about...

Foo: class {
    bar: Bar
    init: func (=bar)
}

Bar: cover {
    baz: String
    init: func (=baz)
}

foo: Foo = null
y := foo $ bar baz

Would this work?

alexnask commented 9 years ago

This could work with a little trickery and a lot of nagaqueen hacking (as well as restarting the safe navigation after baz), I will be taking a look later today.

alexnask commented 9 years ago

@davidhesselbom

This works in my local copy, pushing to my branch in a while

Bar: cover {
    baz: String
    init: func@ (=baz)
}

Foo: class {
    bar: Bar
    init: func (=bar)
}

bar: Bar
bar init("yoyoyo")

foo := Foo new(bar)
y := foo $ bar baz $ _buffer

if (y) {
    y toString() println()
}
alexnask commented 9 years ago

Changes on shamanas:rock/safe_navgation, shamanas:nagaqueen/safe_navigation.

fasterthanlime commented 8 years ago

I think we should use the following syntax:

a ¯\_(ツ)_/¯ b ¯\_(ツ)_/¯ c

(In all seriousness, I have no idea what actually makes sense here, and I'm not sure I'm actually fond of the idea itself. It sounds like something useful mostly for dynamic languages, and I'm pretty sure it just helps hides a lot of programmer error rather than actually solve anything)

alexnask commented 8 years ago

Well, the better solution would be non-nullable types and/or an architecture where null NEVER appears but there are bound to be places in your code were null appears and you need to navigate into some class.

For example, navigating rock's AST, something like expr $ type ?? voidType, where an expression is optional and we should default to void, things like that.

Of course, this is your language after all and if you dislike the feature, I can just close the issue, the PRs and never talk about this again :)

fasterthanlime commented 8 years ago

I don't think blocking one more feature is going to make or break the language at this point.. if it's already working, I suppose $ is good enough for now.

davidhesselbom commented 8 years ago

The only downside is that someone might come up with a better syntax someday, change it, and break existing code. Not that a lot would have to be fixed even then...

alexnask commented 8 years ago

@davidhesselbom It will be merged soon, after I finish class templates so I don't mess up the grammar.

If I don't finish those today or tomorrow morning, I will be merging it alone tomorrow afternoon.