Fantom follows the traditional C/Java model where not all statements are expressions (something I would do differently if I went back in time). But there is one case I keep finding annoying, where I want to write this:
map[key] ?: throw KeyNotFound(key)
That code won't compile today because throw isn't an expression. But I think it would be very handy to make that code work because I keep running across issues where it requires three lines:
v := {some expr which might eval to null}
if (v != null) return v
throw Err
In a perfect world we would make throw a general purpose expression. However that gets into all sorts of ugly type system issues like Nothing/bottom type.
So I am proposing instead just a simple change to the grammar of ?: that allows either an expression or a throw statement.
Comments?
DanielFathSat 2 Jan 2010
Seems like a nice addition despite the limitation. But overall seems like a small improvement. Oh and Happy New Year.
tacticsSat 2 Jan 2010
I'd be for it. Fan just keeps nipping away at those keystrokes, and it's great.
DanielFathSat 2 Jan 2010
So am I. But personally, I think self documentation (as in letting Fandoc find and register what exceptions each method throws) of unchecked exceptions would be more awesome.
tacticsSat 2 Jan 2010
letting Fandoc find and register what exceptions each method throws
This is an interesting idea. Don't know if it belongs in Fandoc, though. But it would sure make a cool IDE tool!
DanielFathSat 2 Jan 2010
Thanks. The way I see it checked exceptions have one major advantage over unchecked. They are self documenting.
With Fandoc collecting and presenting (per method) a brief summary why is exception thrown (or just stating in Java-like manner which Errs are thrown) would make unchecked easy to spot and document and even easier to deal with.
Idea is that it works only for those exceptions that are thrown in the source. IDEs could detect even those exceptions that are thrown in the layers below the method invocation.
andySat 2 Jan 2010
Not sure I'm sold on that. The type system should be handling alot of cases where you would do that. So I don't think its worth adding that wrinkle.
casperbangSun 3 Jan 2010
The thing is, this "self documentation" of checked exceptions looks nice on paper, but really doesn't help much in practice - you can not enumerate all possible exit conditions and it just ends up polluting the signature.
I have yet to understand the paranoid rules around checked exceptions in Java (and for fun, have modified javac to get some more relaxed semantics).
heliumSun 3 Jan 2010
However that gets into all sorts of ugly type system issues like Nothing/bottom type.
Is this concept really too hard to understand? If something has type bottom it never returns. It's an infinite loop, throws an exception, terminates the process or whatever else you can think of that never returns normally.
Bottom can be used everywhere an expression of any type can be used. That makes sense as it won't ever be used anyway as something of type bottom never returns.
? : currently returns the common super type of its two possible result types. The common super type of any type and bottom is that type.
That's all there is to it. Bottom means "does not return", common super-type of anything and bottom is this anything.
foo := condition ? "some string" : Sys.exit(1)
foo has type String. (Assuming Sys.exit would be changed to have type bottom.)
Treat it generally just like Void (except that it also indicates that the function never returns). So don't allow something like lists of bottom or whatever and everything should be fine.
One additional rule: Methods of this type must not contain a return statement. But that's a pretty obvious rule.
Than you perhaps should find a different name for bottom in Fantom. All I currently come up with is something like NoReturn, witch I don't really like.
katoxSun 3 Jan 2010
Why did you narrow Statements as Expressions only to this? Rereading the original discussion there are only a few objections against the idea. And it doesn't really mean that we have to discard return everywhere, as JohnDG mentioned. I can't find the followup discussion maybe the reasoning against is there...
Sometimes, you can easily write the code in an alternative syntax - like if else converted into ? : but if you can't things get messy or tedious.
Noone is forced to actually use the values of "former-statements" so I'd say there is no harm unless there is some kind of an implicit rule which might bite you.
Ad Bottom type, I agree with helium.
brianSun 3 Jan 2010
Why did you narrow Statements as Expressions only to this?
Well during that discussion the feature to omit return on a single statement solved my original problem. Also when I got further into thinking about the grammar and compiler I realized it something that was going to be very difficult to to.
For this case, I'm thinking just making throw an expression instead of a statement in the general case is the right solution. That would simplify the language and give us the ability to use throw with ?: expressions.
The question is does making throw an expression instead of a statement require an official Nothing type be introduced into the language? Helium's right it isn't that difficult a concept. But I don't think we really need to introduce the type formally - I think it is can an aspect of the type system internal to the compiler.
Or put another way, is there really any good reason to allow a programmer to annotate a method as Nothing? There are a couple boundary cases like methods which throw exception, go into an infinite loop, or something like Sys.exit. But Void works good enough for those. So my leaning is to not introduce the bottom/nothing type.
KevinKelleySun 3 Jan 2010
map[key] ?: throw KeyNotFound(key)
has some utility, but seems like adding a special case when the real issue is tied up in the Statements as Expressions thread.
andySun 3 Jan 2010
has some utility, but seems like adding a special case when the real issue is tied up in the Statements as Expressions thread.
I agree - I think there are probably more common patterns that could be simplified if the root issue is solved (and then of course this one too). I know I've run across cases where this would help, I'll try to find some examples.
brianTue 5 Jan 2010
Promoted to ticket #892 and assigned to brian
I will look at making throw a general purpose expression. I will not be enhancing the Fantom type system (it will be handled internal to compiler).
jodastephenWed 6 Jan 2010
While considering this change, I'd suggest looking whether break, continue and retun can be handled in the same way, as they are in a similar vein (jumping control flow).
I can see all of these being useful, and they seem like a logical subset of statements.
heliumWed 6 Jan 2010
The bottom type would have some further advantages.
static Int method(Int x)
{
if (x < 0)
Sys.exit(1)
else
return x
}
Currently the compiler will not compile this completely valid method: "Must return a value from non-Void method".
tompalmerWed 6 Jan 2010
Currently the compiler will not compile this completely valid method
That's a great argument for "does not return". I dislike exit, but I've made convenience methods for throwing exceptions before, and maybe other cases exist.
DanielFathWed 6 Jan 2010
A quick question; Assuming Nothing is implemented how would that reflect on Void when it comes to Func? Would |->| become a |Nothing->Nothing| or some other combination of Nothing and Void?
tacticsWed 6 Jan 2010
Fantom isn't the right language to worry about the nuances of flow control. I don't really like the idea of a Nothing or allowing arbitrary statements to be used as expressions.
helium's exit example is a case where you just need a little coercion to compile your code.
static Int method(Int x)
{
if (x < 0)
Sys.exit(1)
return 0
else
return x
}
That doesn't muddy up the code. The mere existence of Sys.exit is a far worse crime than adding a dummy return value. I use PHP at work, and seeing die statements haphazardly littered about makes me nauseous (among everything else in the language).
tcolarWed 6 Jan 2010
I don't care for Nothing either.
To be that's mostly an academic concept that's really not needed in day 2 day code.
brianThu 7 Jan 2010
While considering this change, I'd suggest looking whether break, continue and retun can be handled in the same way
That would definitely be nice (I keep running across these cases). The problem is that throw is an exception flow control that always makes sense. But sometimes return might be really confusing:
if (foo ?: return x) ...
return foo ?: return x
So I'm thinking we should just stick with throw.
I don't care for Nothing either.
I don't think this feature requires adding the Nothing type to Fantom. It might be handy, but I don't think it is something we need to to now. We can always add it in the future without breaking anything.
jodastephenThu 7 Jan 2010
But sometimes return might be really confusing
This is the case with expression based languages in general:
I'm thinking there really isn't much difference with return/'continue'/'break'. So, we either add the feature, and people can write code thats a bit daft, or don't add the feature, and stay with safe separate statements.
brianThu 7 Jan 2010
This is the case with expression based languages in general:
I totally agree, the problem is that Fantom isn't an expression based language, it is statement based. So things like return, break, continue would be extremely difficult features to implement. I'd probably have to use an exception under the covers.
So I don't see this feature as a full move to an expression based language. Just one small change which makes throw an expression. But return, continue, break, if, while, etc all remain statements.
heliumThu 7 Jan 2010
I'd probably have to use an exception under the covers.
Could you explain why? I thought your current idea is to transform something like
The same transformation could be used for continue, break and return.
I'm not sure that I'd ever want to do this, but I don't see the problem with implementing it.
brianThu 7 Jan 2010
The transformation of "?:" into bytecode isn't statement based, it is stack based within the VM. And in general that gets into stack balancing issues (.NET especially doesn't like you leaving a method with an unbalanced stack).
I think that you are creating a special rule here, just for throwables. That will be a gotcha I suspect. I think it should be all jump-style statements become expressions, or none.
I'm afraid I don't understand the bytecode problem. It seems like a simple source code manipulation. In your bytecode above, you could surely add anything you like at line 18, including continue, break and return. How else would a return normally work?
andyThu 7 Jan 2010
I think that you are creating a special rule here, just for throwables.
I think I side with Stephen - just don't think this wrinkle is worth breaking the statement semantics over.
casperbangThu 7 Jan 2010
Just listening in here, but I agree it feels counter-intuitive to Fantom's problem statement. All or nothing.
brianThu 7 Jan 2010
Just to keep this discussion on track. There are several conflicting things being discussed:
make ?: special case for throw
make ?: special case for throw, return, break, continue
make throw an expression instead of an statement
make throw, return, break, continue all true expressions
I am proposing something very simple, change the grammar to make throw an expression instead of a statement. It is strictly a grammar/parser change, nothing else changes in the compiler or language. Just anywhere you can use an expression, you can now use a throw.
I am not proposing to make ?: a special case. I am not sure Stephen whether you are arguing for number 1 or 4.
Turning constructs like return, break, etc into special cases or making them full expressions is way beyond the scope of how I wish to change the language. If we were starting off fresh, then I'd love to create a pure expression language. But we're several years into a statement based language. If we want to revisit turning Fantom into an expression language then lets start a new topic. But I see this particular issue specifically as a single case, like we made return optional in single statement blocks.
jodastephenThu 7 Jan 2010
I've been arguing for #2, make ?: special case for throw, return, break, continue.
I agree that #4 is a big change, and not really suited to Fantom's style now.
Unless I'm misunderstanding, I think #3 is very messy. Don't these then compile?
doSomething(23, throw Err(), "very odd")
Obj? a := throw Err()
b := map[throw Err()]
Restricting it to ?: seems safer and more obvious, as there is always a continuation path close to the ?: with a normal type.
brianFri 8 Jan 2010
Don't these then compile
Yes, throw would be a true expression, not a statement. But I assume for example that is how Groovy already works today right?
For example whatever we do for ?:, I'd to work with normal ternary operator:
So while we can't follow the model of true expression languages, I think throw is a case where we should. Just limiting it to ?: operators might be ok, but is actually way more complicated from a grammar and compiler perspective.
jodastephenSat 9 Jan 2010
Both the ternary a?b:b and defaulting a?:b have the aspect of having a normal path through that is unaffected by the addition of the throw. In my view that converts this change from weird and bug-creating, to OK.
I'm firmly against making throw just a normal expression because of the weird things you can then do with it.
brianSun 10 Jan 2010
I agree that really throw really only makes sense for a ?: b and a ? b : c expressions. I don't really have a problem restricting it to those constructs, but that makes the grammar and compiler a little more complicated.
At that point I might consider return (I guess break, continue too but they are hardly ever used). But stack balancing that might be tricky.
brianFri 22 Jan 2010
Ticket resolved in 1.0.49
I have updated the grammar and compiler to support using throw as an expr in either a ternary or elvis expression:
I poked around what it would take to also support return but the stack balancing complexity is way more than I want to bite off right now. But the grammar is setup to allow it in the future.
Couple things to note about this grammar change...
The elvis precedence was changed to match precedence of the ternary operator (which it should have had all along). But in some rare cases if you were using elvis with conditional expressions you might need to wrap things in parens.
Also, this grammar change removes the ability to chain elvis:
a ?: b ?: c // previously allowed
(a ?: b) ?: c // now you need to wrap in parens
The compiler internally treats a throw expression as the Nothing type:
Int x := 5
y := x.isOdd ? x : throw Err()
In this example the inferred type of y is Int since the throw expression doesn't evaluate to an actual value.
brian Sat 2 Jan 2010
Fantom follows the traditional C/Java model where not all statements are expressions (something I would do differently if I went back in time). But there is one case I keep finding annoying, where I want to write this:
That code won't compile today because throw isn't an expression. But I think it would be very handy to make that code work because I keep running across issues where it requires three lines:
In a perfect world we would make throw a general purpose expression. However that gets into all sorts of ugly type system issues like Nothing/bottom type.
So I am proposing instead just a simple change to the grammar of
?:
that allows either an expression or a throw statement.Comments?
DanielFath Sat 2 Jan 2010
Seems like a nice addition despite the limitation. But overall seems like a small improvement. Oh and Happy New Year.
tactics Sat 2 Jan 2010
I'd be for it. Fan just keeps nipping away at those keystrokes, and it's great.
DanielFath Sat 2 Jan 2010
So am I. But personally, I think self documentation (as in letting Fandoc find and register what exceptions each method throws) of unchecked exceptions would be more awesome.
tactics Sat 2 Jan 2010
This is an interesting idea. Don't know if it belongs in Fandoc, though. But it would sure make a cool IDE tool!
DanielFath Sat 2 Jan 2010
Thanks. The way I see it checked exceptions have one major advantage over unchecked. They are self documenting.
With Fandoc collecting and presenting (per method) a brief summary why is exception thrown (or just stating in Java-like manner which
Err
s are thrown) would make unchecked easy to spot and document and even easier to deal with.Idea is that it works only for those exceptions that are thrown in the source. IDEs could detect even those exceptions that are thrown in the layers below the method invocation.
andy Sat 2 Jan 2010
Not sure I'm sold on that. The type system should be handling alot of cases where you would do that. So I don't think its worth adding that wrinkle.
casperbang Sun 3 Jan 2010
The thing is, this "self documentation" of checked exceptions looks nice on paper, but really doesn't help much in practice - you can not enumerate all possible exit conditions and it just ends up polluting the signature.
I have yet to understand the paranoid rules around checked exceptions in Java (and for fun, have modified javac to get some more relaxed semantics).
helium Sun 3 Jan 2010
Is this concept really too hard to understand? If something has type bottom it never returns. It's an infinite loop, throws an exception, terminates the process or whatever else you can think of that never returns normally.
Bottom can be used everywhere an expression of any type can be used. That makes sense as it won't ever be used anyway as something of type bottom never returns.
? :
currently returns the common super type of its two possible result types. The common super type of any type and bottom is that type.That's all there is to it. Bottom means "does not return", common super-type of anything and bottom is this anything.
foo
has type String. (AssumingSys.exit
would be changed to have type bottom.)Treat it generally just like
Void
(except that it also indicates that the function never returns). So don't allow something like lists of bottom or whatever and everything should be fine.One additional rule: Methods of this type must not contain a
return
statement. But that's a pretty obvious rule.Than you perhaps should find a different name for bottom in Fantom. All I currently come up with is something like
NoReturn
, witch I don't really like.katox Sun 3 Jan 2010
Why did you narrow Statements as Expressions only to this? Rereading the original discussion there are only a few objections against the idea. And it doesn't really mean that we have to discard
return
everywhere, as JohnDG mentioned. I can't find the followup discussion maybe the reasoning against is there...Sometimes, you can easily write the code in an alternative syntax - like
if else
converted into? :
but if you can't things get messy or tedious.Noone is forced to actually use the values of "former-statements" so I'd say there is no harm unless there is some kind of an implicit rule which might bite you.
Ad
Bottom
type, I agree with helium.brian Sun 3 Jan 2010
Well during that discussion the feature to omit return on a single statement solved my original problem. Also when I got further into thinking about the grammar and compiler I realized it something that was going to be very difficult to to.
For this case, I'm thinking just making
throw
an expression instead of a statement in the general case is the right solution. That would simplify the language and give us the ability to use throw with?:
expressions.The question is does making throw an expression instead of a statement require an official
Nothing
type be introduced into the language? Helium's right it isn't that difficult a concept. But I don't think we really need to introduce the type formally - I think it is can an aspect of the type system internal to the compiler.Or put another way, is there really any good reason to allow a programmer to annotate a method as
Nothing
? There are a couple boundary cases like methods which throw exception, go into an infinite loop, or something likeSys.exit
. ButVoid
works good enough for those. So my leaning is to not introduce the bottom/nothing type.KevinKelley Sun 3 Jan 2010
has some utility, but seems like adding a special case when the real issue is tied up in the Statements as Expressions thread.
andy Sun 3 Jan 2010
I agree - I think there are probably more common patterns that could be simplified if the root issue is solved (and then of course this one too). I know I've run across cases where this would help, I'll try to find some examples.
brian Tue 5 Jan 2010
Promoted to ticket #892 and assigned to brian
I will look at making
throw
a general purpose expression. I will not be enhancing the Fantom type system (it will be handled internal to compiler).jodastephen Wed 6 Jan 2010
While considering this change, I'd suggest looking whether
break
,continue
andretun
can be handled in the same way, as they are in a similar vein (jumping control flow).I can see all of these being useful, and they seem like a logical subset of statements.
helium Wed 6 Jan 2010
The bottom type would have some further advantages.
Currently the compiler will not compile this completely valid method: "Must return a value from non-Void method".
tompalmer Wed 6 Jan 2010
That's a great argument for "does not return". I dislike
exit
, but I've made convenience methods for throwing exceptions before, and maybe other cases exist.DanielFath Wed 6 Jan 2010
A quick question; Assuming Nothing is implemented how would that reflect on Void when it comes to
Func
? Would|->|
become a|Nothing->Nothing|
or some other combination of Nothing and Void?tactics Wed 6 Jan 2010
Fantom isn't the right language to worry about the nuances of flow control. I don't really like the idea of a
Nothing
or allowing arbitrary statements to be used as expressions.helium's exit example is a case where you just need a little coercion to compile your code.
That doesn't muddy up the code. The mere existence of
Sys.exit
is a far worse crime than adding a dummy return value. I use PHP at work, and seeingdie
statements haphazardly littered about makes me nauseous (among everything else in the language).tcolar Wed 6 Jan 2010
I don't care for
Nothing
either.To be that's mostly an
academic
concept that's really not needed in day 2 day code.brian Thu 7 Jan 2010
That would definitely be nice (I keep running across these cases). The problem is that throw is an exception flow control that always makes sense. But sometimes return might be really confusing:
So I'm thinking we should just stick with throw.
I don't think this feature requires adding the Nothing type to Fantom. It might be handy, but I don't think it is something we need to to now. We can always add it in the future without breaking anything.
jodastephen Thu 7 Jan 2010
This is the case with expression based languages in general:
I'm thinking there really isn't much difference with
return
/'continue'/'break'. So, we either add the feature, and people can write code thats a bit daft, or don't add the feature, and stay with safe separate statements.brian Thu 7 Jan 2010
I totally agree, the problem is that Fantom isn't an expression based language, it is statement based. So things like
return
,break
,continue
would be extremely difficult features to implement. I'd probably have to use an exception under the covers.So I don't see this feature as a full move to an expression based language. Just one small change which makes throw an expression. But return, continue, break, if, while, etc all remain statements.
helium Thu 7 Jan 2010
Could you explain why? I thought your current idea is to transform something like
into
The same transformation could be used for
continue
,break
andreturn
.I'm not sure that I'd ever want to do this, but I don't see the problem with implementing it.
brian Thu 7 Jan 2010
The transformation of "?:" into bytecode isn't statement based, it is stack based within the VM. And in general that gets into stack balancing issues (.NET especially doesn't like you leaving a method with an unbalanced stack).
Is compiled into:
jodastephen Thu 7 Jan 2010
I think that you are creating a special rule here, just for throwables. That will be a gotcha I suspect. I think it should be all jump-style statements become expressions, or none.
I'm afraid I don't understand the bytecode problem. It seems like a simple source code manipulation. In your bytecode above, you could surely add anything you like at line 18, including continue, break and return. How else would a return normally work?
andy Thu 7 Jan 2010
I think I side with Stephen - just don't think this wrinkle is worth breaking the statement semantics over.
casperbang Thu 7 Jan 2010
Just listening in here, but I agree it feels counter-intuitive to Fantom's problem statement. All or nothing.
brian Thu 7 Jan 2010
Just to keep this discussion on track. There are several conflicting things being discussed:
?:
special case for throw?:
special case for throw, return, break, continueI am proposing something very simple, change the grammar to make throw an expression instead of a statement. It is strictly a grammar/parser change, nothing else changes in the compiler or language. Just anywhere you can use an expression, you can now use a throw.
I am not proposing to make
?:
a special case. I am not sure Stephen whether you are arguing for number 1 or 4.Turning constructs like
return
,break
, etc into special cases or making them full expressions is way beyond the scope of how I wish to change the language. If we were starting off fresh, then I'd love to create a pure expression language. But we're several years into a statement based language. If we want to revisit turning Fantom into an expression language then lets start a new topic. But I see this particular issue specifically as a single case, like we made return optional in single statement blocks.jodastephen Thu 7 Jan 2010
I've been arguing for #2, make
?:
special case for throw, return, break, continue.I agree that #4 is a big change, and not really suited to Fantom's style now.
Unless I'm misunderstanding, I think #3 is very messy. Don't these then compile?
Restricting it to
?:
seems safer and more obvious, as there is always a continuation path close to the?:
with a normal type.brian Fri 8 Jan 2010
Yes, throw would be a true expression, not a statement. But I assume for example that is how Groovy already works today right?
For example whatever we do for
?:
, I'd to work with normal ternary operator:So while we can't follow the model of true expression languages, I think throw is a case where we should. Just limiting it to
?:
operators might be ok, but is actually way more complicated from a grammar and compiler perspective.jodastephen Sat 9 Jan 2010
Both the ternary
a?b:b
and defaultinga?:b
have the aspect of having a normal path through that is unaffected by the addition of the throw. In my view that converts this change from weird and bug-creating, to OK.I'm firmly against making throw just a normal expression because of the weird things you can then do with it.
brian Sun 10 Jan 2010
I agree that really throw really only makes sense for
a ?: b
anda ? b : c
expressions. I don't really have a problem restricting it to those constructs, but that makes the grammar and compiler a little more complicated.At that point I might consider
return
(I guessbreak
,continue
too but they are hardly ever used). But stack balancing that might be tricky.brian Fri 22 Jan 2010
Ticket resolved in 1.0.49
I have updated the grammar and compiler to support using
throw
as an expr in either a ternary or elvis expression:So now you can do things like this:
I poked around what it would take to also support
return
but the stack balancing complexity is way more than I want to bite off right now. But the grammar is setup to allow it in the future.Couple things to note about this grammar change...
The elvis precedence was changed to match precedence of the ternary operator (which it should have had all along). But in some rare cases if you were using elvis with conditional expressions you might need to wrap things in parens.
Also, this grammar change removes the ability to chain elvis:
The compiler internally treats a throw expression as the Nothing type:
In this example the inferred type of
y
isInt
since the throw expression doesn't evaluate to an actual value.