The first Nimony progress thread became too long to. This thread is the 2nd progress report for Nimony.
Recently this program compiled (and produced the correct output):
import std / [syncio]
type
BinaryTree[T] = ref object
le, ri: nil BinaryTree[T]
data: T
proc newNode*[T](data: sink T): BinaryTree[T] = BinaryTree[T](data: data)
proc append*[Ty: Comparable](root: var nil BinaryTree[Ty], n: BinaryTree[Ty]) =
# insert a node into the tree
if root == nil:
root = n
else:
var it = root
while it != nil:
var c = cmp(n.data, it.data)
if c < 0:
if it.le == nil:
it.le = n
return
it = it.le
else:
if it.ri == nil:
it.ri = n
return
it = it.ri
proc append*[Ty](root: var BinaryTree[Ty], data: sink Ty) =
append(root, newNode(data))
type
Stringable = concept
proc `$`(x: Self): string
proc toString[T: Stringable](n: nil BinaryTree[T]; result: var string) =
if n == nil: return
result.add $n.data
toString n.le, result
toString n.ri, result
proc `$`*[T](n: BinaryTree[T]): string =
result = ""
toString n, result
proc main =
var x = newNode("abc")
x.append "def"
echo $x
main()
Feel free to continue the discussion about what syntax to use for nil ref T, ref T not nil, unchecked ref T here.
var x: ref Obj of Nilable
var y: ref Obj of NonNil
Neat to see the progress :-)
Regarding the not nil syntax, I like "not nil ref T", but I don't like "nil ref T" as much. To me "nil ref T" reads as "this is nil", rather than "this may be nil". It's not a big issue, but if we are looking for an alternative, what about using "not nil" for refs that cannot be nil, but use something else scuh as "maybe" or "nillable" for those that can? That is:
var x: not nil ref RootObj # Non-nil
var y1: nillable ref RootObj # Nillable option 1
var y2: maybe ref RootObj # Nillable option 2
this has the benefit of making them quite different, making them easy to distinguish. Of course some might think that them being so different is a negative rather than a positive. It also has the negative of requiring the addition of a new keyword.le, ri: nil BinaryTree[T]
Does that mean nimony will default to not nil? In such case I'd love BinaryTree[T] or nil syntax to opt out. It's syntactically similar to existing BinrayTree[T] not nil, which I find pretty natural. I'd also appreciate some global flag to disable not-nil-by-default behavior for at least a year, so that we have time to migrate. Otherwise I'm very excited about this (cough breaking) change! :)
I think it would be nice to have a clearer visual distinction between the different kinds of references, since it's a lot of “fun” to read the full type descriptor to see what it really is (especially since they are currently in opposite sides of each other). As an example, you could use the good old ! and ? symbols after the ref keyword. This would be grammatically simple, as well as more visually clear.
For example:
type UncheckedRef[T] = ref T
type NillableRef[T] = ref? T # instead of `nil ref T`
type NotNilRef[T] = ref! T # instead of `ref T not nil`
But I understand that this may be unwanted, since elsewhere the language almost never uses any symbols for such things.
Does that mean nimony will default to not nil?
Yes but the plan is to have a switch like {.refs: unchecked|notnil.} for a migration period.
In such case I'd love BinaryTree[T] or nil syntax to opt out. It's syntactically similar to existing BinrayTree[T] not nil, which I find pretty natural.
I find or nil too similar to not nil. When skimming code this is quite unclear IMO.
I like the or nil version, maybe this looks a bit more distinct:
type NillableRef[T] = ref T or nil
type NotNilRef[T] = ref T is val
And a couple more variants:
type NillableRef[T] = nilref T # short for Nillable Reference
type NotNilRef[T] = valref T # short for Value Reference (always points to value)
type NillableRef[T] = opt ref T
type NotNilRef[T] = val ref T
I like the simplicity of nil not nil prefix. @Saffage version looks very readable too :
type UncheckedRef[T] = ref T
type NillableRef[T] = ref? T # instead of `nil ref T`
type NotNilRef[T] = ref! T # instead of `ref T not nil`
Progress: The compiler optimizes the closure heap allocations away in certain cases. We can make this technique part of the spec as it means capturing var T parameters can be allowed if the heap allocation can be avoided.
In other words:
proc outer(s: var string) =
proc inner =
s.add "abc"
s.add "def"
inner()
becomes possible.
The plan keeps changing. I am working on continuation-passing-style as the foundation for async+multi-threading. Then we can develop a standard library that is async-ready from the beginning, skipping multiple development iterations.
Once we have more of a library, I think it's ready for a first release. I'm still optimistic for autumn this year, but who knows.
Progress: The simplest "passive" proc works now:
import std / [syncio]
proc passiveProc(x: string) {.passive.} =
echo x
passiveProc("abc")
A .passive proc is one that is turned into its CPS representation. Usually this is called an async proc but for now "passive" is the working title. (It's a better name than "async" anyway.)