Open stugol opened 8 years ago
Interesting. The opposite would be even better though..
You mean default to non-nil? In which case, the suffix should probably be ?
.
Yeah. And perhaps a slightly more formal "it has to do with the type"-notation should be used, it won't cost more than two chars in exchange for semantic clarity. Con being that it's uglier, but that can be a pro, depending on how often nils are to be expected:
foo(x, y) ->
foo(x *, y *) -> -- equivalent to above
foo(x *?, y *?) -> -- also accept nils
*
means match any type (BUT Nil, according to the altered proposition), *?
would mean match any type including Nil. Maybe you'll think it's regexp! :-P
lol, it does look a bit like a regex. But I reckon it's serviceable. After all, String?
means String | Nil
anyway, so it makes sense.
Go for it.
What about variables?
a = 1
a = "Fred"
a = nil -- error, `a` cannot be inferred as nil
b *? = nil -- acceptable
I'm not convinced about that one, to be honest. Perhaps stick to function parameters (and global variables?) for this limitation.
Or, what might be better, do it for everything but allow the following shorthand:
b? = nil -- equivalent to b *? = nil
Similarly:
b = 7 if condition -- error, nil possible here
b? = nil -- this reference to nil is valid, but DOES NOT solve the error above
b? = 7 if condition -- ok
So the shorthand is not so much "b
may be nil", as "b
may be set to nil HERE":
fn1(a *?) ->
a = 4
say fn1 nil -- "4"
fn2(a *?) ->
a = nil
say fn2 nil -- "nil"
fn3(a?) ->
a = 4
say fn3 nil -- "4"
fn3(a?) ->
a = nil -- COMPILE ERROR!
So, I guess we have two separate mechanisms here:
nil
if the type is specified as *?
(obviously if the type is specified any more explicitly, e.g. String?
, there is no inference anyway)nil
if it is assigned to with a ?
suffix (e.g. x? = nil
)Currently local vars can't be type restricted, there is syntax implemented or it, but no semantics. For them a type-prefix is required, since the parser doesn't know i you're calling a function with a type as a parameter or type annotating otherwise. So, although not yet officially supported (it will cause problems with crystal<>onyx-macro atm, which I'm still working on):
a `TypeHere
Currently (these are also syntax constructs implemented since the start of Onyx, but with no real function yet - be aware) there are three type prefixes:
a 'Type -- generic type prefix - auto according to context (mutable most likely)
b ^Type -- explicit const / "let" variable
c ~Type -- explicit mutable
But as said, those symbols are up for thinking about, and perhaps it's better with mut
, auto
and - hmm, forgot the last one atm, let's say const
for the second, (they are also implemented as these alphanum alternatives apart from the symbols).
But, once again, these are very loose ghost implementations atm.
In any event, if fully implemented, type restricting a local var will need a prefix of some kind (most likely '
, since it's only used for pragmas otherwise, and they always begin with lowercase, which clearly distinguish them). ^
and ~
as operators currently has "Haskell bitwise syntax" .^.
and .~.
. Which perhaps should be changed back, making it impossible to use those for type purposes.
Also, local vars should default to allowing nil, only the "boundaries" (parameters and return value of funcs) should be stricter to create "barriers of safety", imo.
If we have explicit mutable, doesn't that imply we also have implicit immutable?
I think bitwise operators are so rarely used that it makes sense to free up ^
and ~
for other purposes.
Do you consider immutable == const, or not?
If we have explicit mutable, doesn't that imply we also have implicit immutable?
Depends on the context. I have some ideas for "code modes" for the future. You can choose stricter code modes, much like "use strict" etc. in some languages, but more choices. For instance "pure funcs by default", "immutable data by default". Naturally it's something that has to be discussed at length so that it turns out right and doesn't become confusing. Perhaps erroring if functions haven't been marked pure in the scope where the demand is put, so that the demand is upheld, and it's clear without looking at the top of the file, etc. It simply becomes a help in not missing explicitly adding demands per function. Anyway, this is out of the scope of this particular issue, I haven't created one or it yet, since there are more important things to get done first. But these are loose ideas for future developments.
I think bitwise operators are so rarely used that it makes sense to free up ^ and ~ for other purposes.
Yeah, that was my thinking when "ofuscating" them. Of course, they can be overridden for other purposes, but: 1. I haven't seen that happen often (at all?) for these two. 2. It's not good practice to override operators for non-obvious uses anyway. So, I think we're in the clear there.
Do you consider immutable == const, or not?
This is a tricky one - depends on what you attribute to the words really. Questions worth considering: should the symbol be re-assignable, or set-once? Only "contents" immutable? Etc.
I think the C/C++ way is unnecessarily complicated, causing more confusion then help.
The best way imo is all-in mutable and all-in immutable: "immutable-whatever-it's-called" means the symbol is set-once for the context and immutable all the way. If mutable, the symbol can also be re-assigned. This means there's a safety as to the meaning of a symbol in a context if chosen to be "locked". Further assigns has to be done to new symbols. In final binary code all intermediary variables are optimized away by LLVM, so there's no cost in stack usage.
Perhaps:
a = 4 -- mutable
b! = 4 -- immutable const
Alternatively:
a String -- mutable
b String! -- immutable const
Still need a type-prefix, if local vars, otherwise it's a(String)
... And ?
/!
has strong connotations relating to nilable/non-nilable so I don't think it's wise to confuse with mutability/immutability.
?
has connotations, but !
does not:
a String -- not implicitly nilable
b String? -- implicitly nilable
a? = nil -- explicit nil assignment
c 'String! -- immutable const String
d '*!? -- immutable const nilable
I see no contradiction.
One has to wonder, however....precisely what value does a const variable have before assignment? And how can you then assign it?
For that matter, what value does a non-implicitly-nilable variable have before assignment? ;)
Just thinking, all "throws" (well "raise"s) methods use exclamation mark, that's the non-nil connotation I was thinking about.
I like the idea of suffixing those concepts though, instead of changing '
- great idea!
An immutable would have no value until assigned - it will not be allowed to be used before set. It can definitely be allowed to be declared before setting, but it could as well be required to be set in one expression too, since you can simply use a conditional expression block to assign it. So I think that would be the cleanest and clearest solution.
Same then for required explicitly nilable.
Just thinking, all "throws" (well "raise"s) methods use exclamation mark
I don't see what exceptions have to do with it. Isn't it just that the method doesn't return a value?
fn ->!
4
say fn -- nil
it will not be allowed to be used before set
Difficult to enforce:
a 'String -- non-nil
a = "Nope" if false
say a -- error?
callback = ~> a -- error here?
callback.() -- error here?
(1..10).each (n) ~>
a = n if n > 8
say a -- error?
I don't see what exceptions have to do with it.
I was referring to stdlib-functions, sorry or the confusion, you raised yet a point to think about though.
say a -- error?
Yep. Error. a
can be String | Nil
at that point (or to be precise: it can only be Nil at that point), which is not allowed according to the restriction.
What about the other lines? The callback, for instance? Do they error also? Specifically, does the error occur when the callback is declared, or when it is called?
Yes, that's why it should probably be required that it's assigned in one expression (whether branched or not), ie the incomplete branch assign is not allowed to begin with. In any event it should be an error immediately when referenced before being guaranteed non-nil.
Took the liberty of changing the title slightly, have mistaken the issue when searching a couple of times.
I suggest a
!
suffix for variables and parameters, meaning "never infer a nil type":In this example, the function is inferred as
fn(x : String | Int)
, and the nil call is rejected.