Open ozra opened 8 years ago
It's a bugger, certainly. I'm massively in favour of this feature, but the Ruby/Crystal ?
and !
make it difficult. There are plenty of methods that have both a ?
and a non-?
variant in Crystal, so making it "try one then the other" is a really bad idea!™
Maybe ??
with spaces?
puts a ?? b? ?? c -- b? is a method name here
And !!
, of course.
Now, if only I could think of a use for a !?
token... ;)
Yeah, it's not the preferred way of going, no doubt! But if gathering statistics on sources conclude that the prefer 'idfr?', else use 'idfr' could work out, and complete it with a check that return type definitely is Type|Nil
(and perhaps explicit check that a raise
doesn't occur in the method sugar-chained, it could still be possible to pull off. But, yes, the whole endeavour does smelllll!
As for the spaced suggestions it looks way to messy imo, gotta be a better way :-/
!?
== and/or crash! ;-)
I think the spaced syntax looks quite nice, personally. In the absence of a better suggestion, I propose that we go with that. We can always change it later (or add an alternative, to avoid breaking existing code).
No, that's simply a no go for me, we must come up with something better.
In an issue raised in Crystal for a feature from Ruby 2.0 (mind you I've never coded ruby [less some config-script or so]), there they had foo&.do-stuff&.do-other
, iirc, this of course is pretty much the nil-notation with an ampersand. Got me thinking to the very simple, and not at all that far fetched jump:
-- current inline-nil-check style using shorthand soft-lambda:
x = foo.try ~.do-stuff.try ~.do-other
-- derive from that soft-lambda symbol for the nil-sugar:
x = foo~.do-stuff~.do-other
-- instead of initially proposed:
-- x = foo?do-stuff?do-other
For x.not_nil!
the simple alias x.some!
will be fine - because its' usage is less common, so it's ok. Mirroring with none?
for nil?
check, just for symmetry.
Ruby 2.0 doesn't have anything of the sort, AFAIK.
For x.not_nil! the simple alias x.some! will be fine
I don't follow. Why do we need an alias here?
On that note, I've always found it strange that .any?
returns false
if there are only false items in the enumerable. We should have a .some?
function that means .length > 0
. Does none?
suffer from this problem?
Ruby 2.0 doesn't have anything of the sort, AFAIK.
Lucky I made my "never rubied" disclaimer, haha, in any event it's another idea for the syntax, but I'm still inclined to think the ?
-op vs fun?
-idfr should be solvable.
Regarding not_nil / some. There's no "need", I just think standard lib API names should be cleaned up for the Onyx-side of the universe, there are many name choices that I find ugly and/or unintuitive. That discussion of course should be a holistic perspective issue opened later on, so we'll just leave it in this discussion for now.
For the specifics you mentioned, please open it in a new issue, since they're not about the nil-sugar.
I've implemented this for trying out (needed a break from the macro-branch).
There are plenty of methods that have both a ? and a non-? variant in Crystal, so making it "try one then the other" is a really bad idea!™
When there are two identically named functions, differing only by ending ?
, it means foo?
return T|Nil and foo
returns T or raises. When there's only one, there can be no certainty, but that holds true sugar or not. So I chose to implement this PoC like so:
q = foo?bar?qwo -- look first for `foo?` else `foo`, `bar?` else `bar`, and then `qwo`
--> q = foo?.try ~.bar?.try ~.qwo -- or: foo.try ~.bar.try ~.qwo -- etc variations
-- Works also on terse subscript sugar (sugar with sugar on top):
x = obj.1?to-s --> obj[1]?.try ~.to-s
y = obj#key?to-s --> obj[#key]?.try ~.to-s
z = obj:key?to-s --> obj["key"]?.try ~.to-s
This is not locked down syntax, just what I chose for trying it in practice.
Will it work with spacing?
say foo ? bar ? qwo
What about spacing with ?
-functions?
foo? ->
bar? ->
qwo? ->
say foo? ? bar? ? qwo?
Spacing shouldn't infer ?
-ness:
foo? ->
say foo ? bar -- should fail, `foo` no such method
Currently, it goes the opposite route, just like foo#bar
, foo:bar
. It must not be spaced. Spacing would be an option only if if x ? y : z
syntax is changed.
In that case, also alternatively (just to stay with flow of other similar constructs, but it didn't turn out prettier though):
say foo ?.bar ?.qwo
say foo? ?.bar? ?.qwo?
So for spaced, your proposition is probably better than "staying with similar style".
And, correct, in that case there is no point for ?
-"inference".
Since it's a call-chain, it makes sense to connect the parts though. The essence is x = foo.bar.qwo
, but with breaking if nil is encountered, so x = foo?bar?qwo
conveys that imo.
I still prefer x ?? y
.
Nil Handling Sugar
This is strongly wanted. I was inspired by LiveScript, Swift has it too, etc.
The gotcha is crystal compatibility which is desirable to maintain, because of funcs ending with
!
and?
.The sugar is when an expression is ended with
?
or!
and followed by an identifier, or dot+identifier (alternatives):callable?only-if-non-nil
orcallable?.only-if-non-nil
.As usual, the approach is that methods are used "behind the syntax", and therefore the construct can be exploited by the programmer if wished.
x = a?b?c.d
becomesx = a.try ~.b.try ~.c.d))
internally (canonical form), that is, thetry
-method is invoked, with the arg being a "soft-lambda". For Niltry
is defined as nop, which is why it works.The Caveats (or Features...)
Crystal stdlib has a de facto standard of suffixing method-names with
?
if they return Type|Nil or Bool. This is inline with the behaviour, but clashes with the notation. This is not necessarily a problem, but can instead be utilized as a feature! For 'a?b' (or 'a?.b'), we would first look for a func nameda?
(unless it's a variable of course), if that is not found, we look fora
. By following the pattern of primarily putting nil-check functionality inidentifier?
that is prioritized for this pattern.So, to the other end:
a!b!c.d
ora!.b!.c.d
. This of course means "a is not nil - if it is: throw exception! ...", and so on for the others. It translates to canonical forma.not_nil!.b.not_nil!.c.d
.Here, the naming scheme usage in Crystal stdlib is a bit more varied. It generally means "beware, dangerous method". For instance, in many cases it means "value or throw if nil", which is what we want, but in some cases it means "mutates stuff", and in other cases "value or throw"-methods are named without suffix
!
. Surprising behaviours inherited from Ruby (!)Since this feature must rely on de facto convention of func/method naming, it needs some additional thought and examination of actually used patterns.
I would definitely favour its' implementation. The reason is that for asserting a value is not nil to the type inferer you generally assign a tested return value to a local var (which can't change from the outside), that's a design decision in the code. But if you do want to get the latest value no matter how or where it might have changed from, from a certain instance, you would want to test it at every reference. Also, when making a lookup deep in a tree of instances, it's also very clear and concise.
Syntax Alternatives
Show casing only the "try chain" side of it:
Personally I prefer
idfr?other
, with no additional dot. Since Onyx is space sensitive to some extent, this is one of the places it could be that.