-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Natural
+ friends is bad; {.requires: cond.}
+ friends should be used insetead and checked with --checks:on
#270
Comments
So help us make DrNim production ready. Come on... |
Too expensive, let's better make DrNim a reality. |
I think we should re-open this RFC because range (Natural, Positive etc) just isn't good enough in practice and we need something better, and this RFC is one such option. We can discuss its flaws and improve it though.
proc fn(a: cint): int = 1
when defined case1:
proc fn(a: Natural): int = 2
else:
proc fn(a: int): int = 2
echo fn(1)
I don't see why, you'd be able to disable such checks like all the other checks, via eg, for os.sleep PR nim-lang/Nim#17149, it would be simply: proc sleep(t: int) {.requires t>=0.} => self documenting, and doesn't mess with the type system nor sigmatch |
The major problem with this proposal is that it seems to do these checks at runtime. I'm nowadays quite convinced that these checks should be proven correct by the compiler but should not cause changes in runtime behaviour. Otherwise you end up with new failure modes that others expect to be catchable in practice ("omg, don't make my server crash") |
take
proc delete*[T](x: var seq[T], i: Natural) with this RFC, you'd instead only have a visible failure mode, no hidden failure mode: proc delete*[T](x: var seq[T], i: int) {.requires: i >= 0 and i < x.len.} In practice, you often cannot prove at CT that the condition
proc fun(s: var seq[int], i: int) =
assert i >= 0 and i < s.len # redundant code, but needed to make `requires` computable at CT
delete(s, i) Note that this proposal still allows some checks to be optimized away at RT if compiler can determine that (based on other checks, assert's, range checks etc) the condition holds. There are 3 states:
And when checks are disabled, this analysis isn't performed. Benefits:
noteeven when a RT check can't be omitted, it's still useful for further static analysis, eg: proc delete*[T](x: var seq[T], i: int) {.requires: i >= 0 and i < x.len.}
proc fun(s: var seq[int], i: int) =
delete(s, i) # RT check: i >= 0 and i < s.len
# now the compiler can in theory simplify the next check as:
delete(s, i+1) # RT check: i+1 < s.len
# or in other cases, determine that a check would be guaranteed to succeed based on previous requries/assert's |
That's what I propose yes. It would be mitigated by the fact that an illformed |
Related: |
--checks:on
--checks:on
--checks:on
{.requires: cond.}
+ friends should be checked with --checks:on
{.requires: cond.}
+ friends should be checked with --checks:on
Natural
+ friends is bad; and {.requires: cond.}
+ friends should be checked with --checks:on
Natural
+ friends is bad; and {.requires: cond.}
+ friends should be checked with --checks:on
Natural
+ friends is bad; {.requires: cond.}
+ friends should be used insetead and checked with --checks:on
Can be made a lightweight DrNim, that does not depend on the Z3 thingy?, I am NOT saying to trash the Z3, just make the thing work, so people use it and contribute back. Otherwise is not a real alternative to |
yet another example showing proc main(a: openArray[Natural]) = discard # error
main(@[1,2]) (refs nim-lang/Nim#15790 (comment))
@Araq I don't see how this would ever work. With this suggestion, if you have N clients of # with this RFC:
delete(a, getIndexes()) # the checks are done inside with `delete(a: JsonNode, b: seq[int]) {.requires: cond.}`
# with your proposal:
when compileOption("assertions"):
let b = getIndexes()
for bi in b: assert bi > 0
delete(a, b)
else:
delete(a, getIndexes()) this is bad for many reasons:
User code (and in particular users of API's) has less context than the compiler, the compiler instead should be the one deciding to optimize out tests, but that optimization is not even needed by this RFC and can come later gradually; all that's needed is to honor the .requires checks when A key advantage being it doesn't affect the type system, unlike Natural + friends. |
proc main(a: openArray[Natural]) = discard # What error ?
main(@[1.Natural, 2.Natural]) Even with the wrong code, the error exists BEFORE entering the body of the function. Imagine DrNim works out-of-the-box, If the input data is unknown, and only exists at run-time, how does DrNim checks that ?, How well does DrNim runs on Embedded hardware?, (I am asking, not confirming) |
Literally DBC. See Ada, used for serious stuff. We need a DBC DSL on stdlib, can be used with and without DrNim, Imagine this is wrong, then Ada/Spark is used as toy programming language for thrown away scripts and not for serious purposes. 🤷 |
Well, encoding invariants in types is still very useful. So in an ideal world, the definitions become: type
Natural = int {.invariant: value >= 0.}
Positive = int {.invariant: value > 0.}
|
here's another motivational example, see https://github.com/nim-lang/Nim/pull/17625/files?diff=split&w=1#r606847437 before this RFC: proc getPointer*(x: Any): pointer =
## Retrieves the pointer value out of `x`. `x` needs to be of kind
## `akString`, `akCString`, `akProc`, `akRef`, `akPtr`,
## `akPointer` or `akSequence`.
assert x.rawType.kind in pointerLike
result = cast[ppointer](x.value)[] after this RFC: proc getPointer*(x: Any): pointer {.requires: x.rawType.kind in pointerLike.} =
## Retrieves the pointer value out of `x`.
result = cast[ppointer](x.value)[] => self documenting, DRY, and helps static analyzers examples like this are pervasive. (note that now that nim-lang/Nim#17054 was merged such pragmas would be rendered in docs) |
The problem is not the pragma, the problem is how to prove them valid ?. |
with this RFC:
note that the runtime checks can be performed without any static analyzer implemented; the static analyzer can improve over time to detect more errors / elide more checks gradually. |
I think it's too early to look into dynamic checks when we're that close to working static checks. |
I don't see why we can't do both? The dynamic checks would only be enabled when |
No, the analysis is off: If it cannot be checked statically, there should be a mechanism like: template enforce(x) =
{.assume: x.}
assert(x)
To turn what is beyond the compiler's capabilities into runtime checks explicitly. |
in nim-lang/Nim@8ee0771#commitcomment-38239130 araq argued "return types must not be Natural" (this came up recently here: nim-lang/fusion#18 (comment)), and instead suggested using
{.ensures: cond.}
for that.{.ensures: cond.}
for return and{.requires: cond}
for params is indeed more flexible thanNatural
(since you can express any condition), and it doesn't conflate checking with the type system (cf his example in nim-lang/Nim@8ee0771#commitcomment-38254164).However,
ensures, requires
is ATM only used bydrnim
(which incidentally seems broken see nim-lang/Nim#15639), which makes the annotation a whole lot less useful.proposal
--ensureChecks
,--requiresChecks
,--invariantChecks
(--assumeChecks
wouldn't make sense) which would make regular nim test for those--checks:on
would enable those{.push requiresChecks: on.} ... {.pop.}
nim c main
by default turns on--ensureChecks
and--requiresChecks
by default but not--invariantChecks
(loop invariant runs on every loop iteration and would slow down considerably)links
note
yet another example why
Natural
is usually a bad idea: it affects the type system; whereas.requires
doesn't (a transparent abstraction).The text was updated successfully, but these errors were encountered: