We try to put as many breaking changes as reasonable into the upcoming 0.12.0 release. So starting with today the tables.[] accessors now raise the KeyError exception if the key doesn't exist! Use the new getOrDefault procs to get the old behaviour. Compile your code with -d:nimTableGet to see where you used tables.[] and if the code did rely on the old behaviour.
Other breaking changes can be found here: https://github.com/nim-lang/Nim/blob/devel/web/news.txt
Furthermore I will finally change the grammar/parser so that echo $foo is parsed as echo($foo) rather than (echo) $ (foo).
I find this pretty cool:
proc f(x, y: auto): auto =
result = $x & $y
echo f(0, "abc")
echo f("abc", 0)
Notice the second $ instead of the example from the news.txt.
Currently this line fails with internal error (No stack trace available):
assert @[1,2,3][0] == 1
However this line works:
assert (@[1,2,3][0] == 1)
Is this going to change as echo and $ will?I generally agree with these breaking changes, but the problem I see with that is that it leads to some really ugly code now, see below. Can we possibly make a separate DefaultingTable type (or any other name) that will make [...] work as before, where the Table with raise error. Or maybe alternatively think about some defaulting accessor notation [?...]
Was:
var contentLengthHeader = headers["Content-Length"]
if contentLengthHeader != "":
var length = contentLengthHeader.parseint()
Now ugly code:
var contentLengthHeader = headers.getOrDefault"Content-Length"
if contentLengthHeader != "":
var length = contentLengthHeader.parseint()
Well it would be easy to use {} instead of getOrDefault
var contentLengthHeader = headers{"Content-Length"}
if contentLengthHeader != "":
var length = contentLengthHeader.parseint()
But I don't consider getOrDefault that ugly. Let's hear what others have to say, I don't mind changing it to {} either.
why not overload the [] proc with a second param for the default value ?
var contentLengthHeader = headers["Content-Length", ""]
if contentLengthHeader != "":
var length = contentLengthHeader.parseint()
Well in Nim string in the table would be initialized to nil.
import tables
var a = newTable[string, string]()
echo a.getOrDefault("test") # nil
a["test"] &= "1" # throws
echo a
@jyelon if you really wanted you could create your own overloaded [] for the type(s) you need:
import tables
proc `[]`[A](a: var Table[A,string], b: A): var string =
if not hasKey(a, b):
a[b] = ""
tables.`[]`(a, b)
proc `[]`[A](a: Table[A,string], b: A): string =
if hasKey(a, b):
tables.`[]`(a, b)
else:
""
var a = newTable[string, string]()
echo a.getOrDefault("test") # nil
echo a["test"] # empty string
a["test"] &= "1" # "1"
a["test"] &= "1" # "11"
echo a # {test: 11}
Here's some new idea of handling defaults. It introduces Defaulting[T] type wrapper and {} operator wrapping type instance into defaulting type. Curly brackets were chosen arbitrary because they have right precedence and are right associative.
# TL;DR -> skip to Usage example below
## Simple defaulting wrapper.
## Now procs raising on T, may gracefully return default on Defaulting[T].
type
Defaulting*{.borrow.}[T] = distinct T
#<- unfortunatelly borrow here doesn't make other procs to work
# automatically
template `{}`*[T](t: T): Defaulting[T] = Defaulting(t)
## Defaulting wrapper with given default value.
## We may pass default.
type
DefaultingVal*{.borrow: `.`.}[T, D] = distinct tuple[value: T, default: D]
template `{}`*[T, D](t: T, d: D): DefaultingVal[T, D] = DefaultingVal((t, d))
################################
# Now some example with tables:
import tables
# Implement defaulting accessors:
proc `[]`*[A, B](td: Defaulting[TableRef[A, B]], key: A): B =
## Gracefully return default.
# FIXME: make it template! But I cannot do it, because compiler fails then.
TableRef[A, B](td).getOrDefault(key)
template `[]`*[A, B](td: DefaultingVal[TableRef[A, B], B], key: A): B =
## Gracefully return default.
# FIXME: This should use 2 arg version of getOrDefault, but there's none.
# So for now try/except.
try:
td.value[key]
except KeyError:
td.default
#################
# Usage example:
let headers = newTable[string, string]()
headers["Type"] = "Text"
echo headers{}["Type"] #<- "Text"
echo headers{"None"}["Type"] #<- "Text"
echo headers{}["Length"] #<- nil
echo headers{"None"}["Length"] #<- "None"
# We can turn headers into defaulting headers:
let defaultingHeaders = headers{}
echo defaultingHeaders["Type"] #<- "Text"
echo defaultingHeaders["Length"] #<- nil
# Or even defaulting headers with a value:
let defaultingNoneHeaders = headers{"None"}
echo defaultingNoneHeaders["Type"] #<- "Text"
echo defaultingNoneHeaders["Length"] #<- "None"
echo headers["Length"] #<- this will raise KeyError and make program crash!
The Defaulting[T] is just type wrapper, so it generates absolutely no overhead in memory or instructions. Only problem is that I couldn't make [] to be a template (someone help!?), because some strange compiler errors.
DefaultingVal[T, D] is a tuple and has little overhead, comparable of passing extra argument to the accessor method, so still marginal.
And "yet another" proposition of language extension for optional graceful/silent/mild method implementations with some new blessed operator ? (chosen arbitrarily). Idea is that module can provide graceful defaulting versions of some procs/accessors, so when call is denoted with ? then non-raising graceful version is called instead of raising version.
For example:
proc `[]`*[A, B](t: TableRef[A, B], key: A): B {.defaulting.} =
t.getOrDefault(key)
let headers = newTable[string, string]()
headers["Type"] = "Text"
echo headers["Type"]? #<- "Text"
echo headers["Type"]? or "None" #<- "Text"
echo headers["Length"]? #<- nil
echo headers["Length"]? or "None" #<- "None"
# but this notation can be used for some other cases:
var someObject = newBigObject()? or newSmallObject()
if someOperationThatMayFail()? or anotherOperation():
...
if string.isEmpty?: #<- string may be nil, but it will just return 0 for nil
...
if string.isEmpty: #<- string may not be nil, otherwise we get an exception
...
I think this could be very powerful thing to be able to chose between raising or defaulting with simple ? mark. (or any other character or short operator)
If the {.borrow.} pragma was to be extended so as to borrow all procs by default (as I understand Ono's suggestion) then that would certainly be useful in other situations as well. An example is the type Immutable[T] = distinct T discussed a while ago.
With this sort of thing you could easily newtype or "tag" existing types so as to add a variant with special semantics...
Defaulting[T]
Immutable[T]
Tainted[T]
TopSecret[T]
...
@araq I think that tables.rawGet() should be exported!
@ono I suspect that using try/except may even be worse that doing an hasKey() but see above.