Skip to content
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

const should work with ref types #271

Open
timotheecour opened this issue Oct 19, 2020 · 9 comments
Open

const should work with ref types #271

timotheecour opened this issue Oct 19, 2020 · 9 comments

Comments

@timotheecour
Copy link
Member

timotheecour commented Oct 19, 2020

fully implemented in PR nim-lang/Nim#15528, see PR for details, tests, examples, and discussion.
(RFC was requested here: nim-lang/Nim#15528 (comment))

highlights under this PR:

  • const a = expr now works even if expr (recursively) contains some ref type, instead of giving CT error
  • a isn't codegen'd, but its value is pasted in each context where it's used at RT, consistent with current behavior with non-ref types
  • this allows caching complex/expensive CT calculations, simplifies code, and removes some un-necessary restrictions in the language and enables many new use cases
  • import tables; const a = {1: "1", 2: "2"}.newTable now works

precedent in other languages

  • D distinguishes between 2 concepts for CT expressions: enum (manifest constant, not codegen'd but pasted in each runtime context where it's used) and static const (codegen'd in ROM memory, not pasted). This PR makes nim's const work the same as D's enum (which also works with ref types)
@Araq
Copy link
Member

Araq commented Oct 20, 2020

One downside that I can see is this RFC is in direct contrast to #257 (which was accepted).

@Araq
Copy link
Member

Araq commented Oct 26, 2020

If you handle const x = MyRef() like template x(): untyped = MyRef(), what's the gain? People can write the template version too. Programming languages are designed to catch mistakes, if you create a programming language where everything compiles ("this must work, think about generic programming!"), you created a programming language that catches no mistakes.

@timotheecour
Copy link
Member Author

If you handle const x = MyRef() like template x(): untyped = MyRef(), what's the gain? People can write the template version too.

that's not the same at all.

The point is this feature allows caching the result of a potentially expensive computation or something that should be computed just once for correctness.

with nim-lang/Nim#15528 this works:

import std/json

proc fn(file: static string): JsonNode =
  echo "parsing " & file
  const s = staticRead(file)
  result = parseJson(s)

const j = fn("/tmp/myconfig.json") # fn will be called just once

proc main() =
  # CT code can now use `j`, eg:
  const version = j["version"].getStr
  const name = j["name"].getStr

  # RT code can also use `j`, eg:
  echo (version, name)
  echo j.pretty.static
  echo j["name"].getStr.static
  echo j["version"].getStr.static

main()

I don't see how template x(): untyped = MyRef() would help in any way.

I'm using this to great effect in my own patched nim.

@Araq
Copy link
Member

Araq commented Oct 27, 2020

And how do you "cache" it? Previously you claimed it doesn't affect GC safety.

@timotheecour
Copy link
Member Author

@Araq

And how do you "cache" it? Previously you claimed it doesn't affect GC safety.

no contradiction; it's cached in the same way it's cached for non-ref types, after const j = expr is evaluated (once), the result is stored in j'ast as a PNode.

Subsequent const accesses (eg const version = j["version"].getStr) use j directly, while subsequent runtime accesses paste the resulting PNode litteral as if it was inlined in the code, eg:

example 1 (from example above)

echo j["name"].getStr.static
# is same as:
const tmp: string = j["name"].getStr
echo tmp # the `tmp` string gets pasted where it's used, as if the code was: echo "foobar"

example 2 (more complex example):

when true:
  type Foo = ref object
    n: int
    lhs, rhs: Foo

  proc fn(n: int): Foo =
    result = Foo(n: n)
    if n > 1:
      if n mod 2 == 0:
        result.lhs = fn(n div 2)
      if n mod 2 == 1:
        result.rhs = fn((n+1) div 2)

  const j1 = fn(20)

  let j2 = j1
    # same as if we inlined the following
    # let j2 = Foo(n: 20, lhs: Foo(n: 10, lhs: Foo(n: 5, rhs: Foo(n: 3, rhs: Foo(n: 2, lhs: Foo(n: 1))))))
    # `fn` is not re-evaluated here 

@Araq
Copy link
Member

Araq commented Oct 29, 2020

Thanks. It's growing on me...

@Araq
Copy link
Member

Araq commented Nov 13, 2020

It is in direct contrast to #257 (which was accepted) so maybe for #257 we need a .section(readonly) pragma. In fact, .section would be the last missing feature to get us to competitive for embedded programming.

@konsumlamm
Copy link

Maybe I'm missing something, but isn't the point of const not to inline the expression, but to precompute it at compile time and then inline the result or store it in the executable? Or is this more like Rust's lazy_static, which computes it at runtime, but only the first time it's accessed? (In the latter case, that seems inconsistent with how const works for other expressions.)

@timotheecour
Copy link
Member Author

timotheecour commented Jul 21, 2021

const a = foo() doesn't codegen a, only a RT use of a will codegen, eg:

type Foo = ref object
  x1: int # etc; can contain other nested ref types
proc foo(n: int): Foo =
  # potentially expensive computation (potentially non-pure or with CT side effects)  
const a1 = foo(3) # a is not codegen'd but computed at CT; foo(3) is computed just here
const a2 = a1.x1 # foo() is not recomputed here
let a3 = a2 # only a2 is codegen'd here; or, more directly, `let a3 = a1.x1.static`

const a4 = foo(4) # foo(4) computed just here
let a5 = a4 # a5 is codegen'd, using a literal, eg as if `let a5 = Foo(x1: ...)` was written

This simply extends const to work at CT, it otherwise works the same as types that don't contain (nested) ref

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants