Motivation: In c++ I often used namespaces to group symbols within a header file. The intention being to ensure code using these symbols remains easy to read in regard which symbol is called. Omitting the namespace might be convenient for writing code but makes reading it harder. If needed, the namespace can explicitly be renamed by a user (or made non-required).
In Nim, I know {.pure.} for enums, which comes quite close to what I want. But I think this works only for enums, not other kinds of symbols (procs, variables, constants, types, ...).
Customizing the import statement works only for the file with that import statement. When exporting those symbols, the requirement for that namespace is lost. (At least as far as I know.)
Occasionally, I saw Nim code with prefixes in each symbol's name to group them together. As far as I know, there is no (reasonable) way to change that prefix as a user of that code. Also I find it a lot harder to read thisIsTheNamespaceAndSomewhereInTheMiddleTheSymbolsNameBegins then thisIsTheNamespace.AndSomewhereInTheMiddleTheSymbolsNameBegins.
A small toy example for a package "somePackage" with these two files
# helpers.nim
let onlyWithNamespacePlease* = "hello"
# somePackage.nim
from helpers import nil
# issue#1: no idea how to export `helpers` while preserving the need for a namespace
export helpers
When using somePackage
import somePackage
echo helpers.onlyWithNamespacePlease # namespace is required
#issue#2: must not compile since required namespace is omitted
#echo onlyWithNamespacePlease
In case it helps, the c++ equivalent with the three desired behaviors (require namespace by default, allow renaming namespaces, make namespace non-required)
//helpers.hpp
namespace helpers {
std::string const onlyWithNamespacePlease = "hello";
}
//somePackage.hpp
#include helpers.hpp
// using somePackage
#include somePackage.hpp
// namespace is required
std::cout << helpers.onlyWithNamespacePlease << std::end;
// rename namespace
namespace renamedNamespace = helpers;
std::cout << renamedNamespace.onlyWithNamespacePlease << std::end;
// make namespace optional
using namespace helpers
std::cout << onlyWithNamespacePlease << std::end;
Finally the question: What is the Nim way to get similar results?
Why would you want to force your preference to use a namespace on your library's users?
Nim doesn't like namespaces because they make UFCS impossible. You can rewrite join(words) as words.join, but how would you rewrite strutils.join(words)? words.(strutils.join)? That's hideous.
Tuples maybe?
let myNameSpace = (
val1: 1,
proc1: proc = echo "hello world"
)
myNameSpace.proc1()
echo myNameSpace.val1
The simplest way of doing this is to make your "namespace" an empty type and use it as an argument:
type myNamespace = object
proc test(_: typedesc[myNamespace], argument: string) =
echo argument
myNamespace.test("Hello world")
This forces you to use myNamespace to call the procedure, and if you don't export that type you don't have any way of calling your procedure. As a bonus that will all be resolved on compiletime so it won't add any overhead. That beings said it's generally not recommended to do this in Nim. When you find yourself fighting a language it is often a sign that you're coming in with some preconceived notion of the best way of doing something to which the language disagrees. I'd say try to use Nim as Nim was intended, then if this is still something you feel is a problem you can implement a solution for it.
As an exercise for the reader it would also be completely feasible to use a small pragma macro to add that first type, or write a small macro that applied this to every procedure in a block. This would help readability.
Thanks for these ideas. Alas both seem somewhat hacky, I was hoping for something more established. Also, both require modifying each occurrence of a symbol and/or don't work for every kind of symbol.
I might have over-generalized my question in the hope of getting a general answer. If namespaces are mostly considered useless in Nim, I don't see the point of emulating namespaces in a unconventional way. That would only add confusion and only few of the benefits I was looking for.
My question was originally based on one particular situation, which in other languages would have been solvable with namespaces and their manipulation. What would be the nim-as-intended way to solve this?
# stable.nim
# symbols that also exist in the other subpackage
proc hello*() = echo "hello"
# symbols that currently only exist in this subpackage
proc yetAnotherHello*() = echo "whats up"
# experimental.nim
# symbols that also exist in the other subpackage
proc hello*() = echo "hi"
# symbols that currently only exist in this subpackage
proc alternateHello*() = echo "heyho"
# package.nim
import stable
import experimental
export stable
export experimental
# usingPackage.nim
import package
stable.hello()
experimental.hello()
alternateHello() # from experimental
yetAnotherHello() # from stable
Assume I can only change package.nim, the other files could well be written by someone else (and have other priorities than catering to my situation).
My issues with the code are:
export experimental except hello