nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.44k stars 1.47k forks source link

mixin value causes typechecker to fail #7310

Open krux02 opened 6 years ago

krux02 commented 6 years ago
let theEnvironment = "default-env"

proc foobar_internal(env: string; arg: int): void =
  echo "form ", env, " called foobar with arg: ", arg

template foobar(arg: int): void =
  mixin theEnvironment
  foobar_internal(theEnvironment, arg)

foobar(1)
proc main(): void =
  let theEnvironment = "local-env"
  foobar(2)
main()

compilation error:

nim c -r /tmp/scratch.nim
Hint: used config file '/home/arne/proj/nim/Nim/config/nim.cfg' [Conf]
Hint: system [Processing]
Hint: scratch [Processing]
scratch.nim(11, 7) Error: type mismatch: got <string, int literal(1)>
but expected one of: 
proc foobar_internal(env: string; arg: int): void
  first type mismatch at position: 1
  required type: string
  but expression 'theEnvironment' is of type: None

expression: foobar_internal(theEnvironment, 1)

In the error it tells me that the first argument is of type string, but then later it tells it is of type None. That cannot be right.

andreaferretti commented 6 years ago

I am sure something is not right, but what does it even mean to mixin a string? mixin is usually used with procedure symbols to allow overloading (changing symbols from closed to open)

Araq commented 6 years ago

I think the compiler is right (though the error message is bad). mixin does not mean "look up in instantiation scope", it means "consider both current and instantiation scope" and so it picks up both symbols at the same time giving "theEnvironment| theEnvironment" the type None.

krux02 commented 6 years ago

@andreaferretti I am trying to do something like environment variables in unix. Or dynamic scoping like in emacs lisp. I just give you an example of this use case so you might understand it a bit better.

Take the random module for an example with it's random number generator. You want a simple to use rand function that just gives you a random number without setup. But you also might want to control the instance of a random number generator. With my pattern it would work like the following:

let a = rand()  # default global random number generator
let b = rand()  # default global random number generator
let randomenv = #[ init random number generator ]#
let c = rand() # now randomenv shadows the default random number generator
let d = rand() # again random number from randomenv

A second example where this might be useful is for memory allocation. Normally when you have a library that you call and you know it allocats a lot of junk temporary memory the garbage collector would be required to collect it all. With this pattern it would be possible to redirect the memory allocation to a memory pool for the library call and then just reset the memory pool.

let memoryenv = createMemoryPool(size = megabyte(32))
var accu = 0
for i in 0 ..< 1000:
  let myResult = libraryCallWithLotsOfTemporaryMemoryAllocation()
  memoryenv.reset  # Everything that still holds a pointer into the data of `memoryenv` is invalid
  accu += myResult

@Araq would there be something like look up in instanciation scope? Because when do the same pattern with just macros, I have no problems:

import macros

let theEnvironment = "default-env"

proc foobar_internal(env: string; arg: int): void =
  echo "form ", env, " called foobar with arg: ", arg

macro foobar(arg: int): typed =
  result = newCall(bindSym"foobar_internal", ident"theEnvironment", arg)

foobar(1)
proc main(): void =
  let theEnvironment = "local-env"
  foobar(2)
main()

# output: 
#
#   form default-env called foobar with arg: 1
#   form local-env called foobar with arg: 2
andreaferretti commented 6 years ago

@krux02 I understand what you want to do, but what mixin does is a different thing :-)

Scala has implicit parameters for that - they are both a blessing and a curse

krux02 commented 6 years ago

I know implicit parameters in Scala. Even though they bind by Type and not by name, they would do the trick here.

Araq commented 6 years ago

Use a .dirty template and maybe bind foobar_internal.

krux02 commented 6 years ago

Yes a dirty template works. Still I don't like the dirty template, because it is dirty 🙃. Yea I mean it binds all identifiers in instanciation scope and I can't decide which identifiers should come from that scope.

If there would be an instanciation scope mixin/bind, then also other use cases of the dirty pragma could be removed from the Nim code base.

andreaferretti commented 6 years ago

My suggestion would be to avoid dynamic variables like the plague, but i'ts your choice... :-D

krux02 commented 6 years ago

@andreaferretti yea are probably right. Though they are useful. And this is probably not the way to implement them.

krux02 commented 6 years ago

@Araq I just tried out the dirty pragma and it does not work:

# filename: "mymodule.nim"
let theEnvironment = "default-env"

proc foobar_internal(env: string; arg: int): void =
  echo "form ", env, " called foobar with arg: ", arg

template foobar*(arg: int): void {.dirty.} =
  bind foobar_internal
  foobar_internal(theEnvironment, arg)

# filename: "scratch.nim"
import mymodule

foobar(1)
proc main(): void =
  let theEnvironment = "local-env"
  foobar(2)
main()

error:

Hint: system [Processing]
Hint: scratch [Processing]
Hint: mymodule [Processing]
mymodule.nim(1, 5) Hint: 'theEnvironment' is declared but not used [XDeclaredButNotUsed]
scratch.nim(3, 7) template/generic instantiation from here
mymodule.nim(8, 19) Error: undeclared identifier: 'theEnvironment'

when I leave out the bind statement it has this error: mymodule.nim(8, 3) Error: undeclared identifier: 'foobar_internal'

Araq commented 6 years ago

Huh, strange...

krux02 commented 6 years ago

I don't think it's strange at all. The dirty praga does resolve symbols at instanciation context. The problem is foobar_iternal is not exported and therefore in instanciation context it can't be resolved.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. If you think it is still a valid issue, write a comment below; otherwise it will be closed. Thank you for your contributions.