tomhrr / dale

Lisp-flavoured C
BSD 3-Clause "New" or "Revised" License
1.03k stars 48 forks source link

Namespace problems #130

Closed porky11 closed 6 years ago

porky11 commented 8 years ago

I want to redefine an existing function, which calls the original function and does some additional behaviour. For example redefine + as a macro which does some implicit casting on it's arguments before calling the original + function (or macro). Another use case may be, printing debug messages before and/or after calling a function. Normally I could do this by not using the namespace the original function is implemented in, but since +, some other functions and the core forms are implemented in no namespace, I don't think, there is a way to call the original version differently. Especially for debugging it would be useful not having to rename the function, because you may want to use the original function after debugging, and have to rename every use of the function, everytime you enable or disable debugging. This could be solved by having some core or default namespace where the core functions are in. Then the (core fn …) core form could be written as (core.fn …), too.

Another problem: When I don't use a namespace because I implement a new version of this function, I also have to reimplement the other functions of this namespace. So it would be nice to have more control about which functions (macros etc.) are used from this namespace (like only using special names, using all names except some names, or just using the whole namespace, maybe even unusing namespaces).

A different approach to name-problems would be the ability to import something under different names (with some prefix), which would make namespaces unneccessary and so you don't have to write something like this at top of each file:

(using-namespace std.macros
(using-namespace std.concepts
…

It's clear anyway when you import something, which defines functions in a namespace you also want to use them. See how import works in stanza (http://lbstanza.org/reference.html#anchor39)

Problems for implementing this may be a big change of the core, and it's not sure how to handle macroexpansions. I think, if they expand to extern functions or macros, they would have to be renamed sometimes, and sometimes not. (For example when you add a prefix to all binary + functions, and implement a new debug + function, the + for multiple arguments should expand to the debug +, and some macros, for example a macro expanding to a for loop may want to expand to a prefix-for loop if for is imported with prefix "prefix-")

tomhrr commented 8 years ago

On Wed, Sep 28, 2016 at 08:08:20AM -0700, Fabio Krapohl wrote:

Normally I could do this by not using the namespace the original function is implemented in, but since +, some other functions and the core forms are implemented in no namespace, I don't think, there is a way to call the original version differently.

This wasn't documented, but it's possible to do this similarly to how it's done in C++, by prefixing the name with the namespace separator. See https://raw.githubusercontent.com/tomhrr/dale/master/t/src/root-ns.dt.

Another problem: When I don't use a namespace because I implement a new version of this function, I also have to reimplement the other functions of this namespace. So it would be nice to have more control about which functions (macros etc.) are used from this namespace (like only using special names, using all names except some names, or just using the whole namespace, maybe even unusing namespaces).

A different approach to name-problems would be the ability to import something under different names (with some prefix), which would make namespaces unneccessary and so you don't have to write something like this at top of each file:

(using-namespace std.macros (using-namespace std.concepts

It's clear anyway when you import something, which defines functions in a namespace you also want to use them. See how import works in stanza (http://lbstanza.org/reference.html#anchor39)

I think this sort of feature gives the author too much flexibility. I'd much rather that there be just one way to qualify a given reference. That allows for consistency across the language, rather than having to check the import prefixes in each file and keep that in mind while reading. However, it may be easier to demonstrate why this is problematic with an example program.

Putting aside prefixing of forms, it is possible to import a subset of the forms from a given module by including those forms in a list after the module name, not that this helps with namespaces in general. See e.g. https://raw.githubusercontent.com/tomhrr/dale/master/t/src/dtm-intover-user.dt.

Problems for implementing this may be a big change of the core, and it's not sure how to handle macroexpansions. I think, if they expand to extern functions or macros, they would have to be renamed sometimes, and sometimes not. (For example when you add a prefix to all binary + functions, and implement a new debug + function, the + for multiple arguments should expand to the debug +, and some macros, for example a macro expanding to a for loop may want to expand to a prefix-for loop if for is imported with prefix "prefix-")

Regarding macroexpansions, I hadn't thought about that, and it looks like a dealbreaker to me. Either there would have to be multiple ways of referring to the same binding (by prefixed form and by the current namespace-qualified version), or there would have to be something pretty clever in the macro-processing code to account for the names being imported with prefixes.

porky11 commented 8 years ago

This wasn't documented, but it's possible to do this similarly to how it's done in C++, by prefixing the name with the namespace separator. See https://raw.githubusercontent.com/tomhrr/dale/master/t/src/root-ns.dt.

This does not allow to let the new version of + be untyped, becuase dispatch would call the typed version then. See this:

(import cstdio)
(import macros)

(namespace test
(using-namespace std.macros
  (def + (macro intern (a b)
    (qq do
      (printf "calling +\n")
      (def x (var auto \ (.+ a b)))
      (printf "called +\n")
      (return x)))))
)
(def main (fn extern-c int (void)
  (printf "%d\n" (+ 1 2))
  (using-namespace test
    (printf "%d\n" (+ 1 2)))
  0))

The ability to only import specific symbols from a module doesn't seem useful, since modules used by modules are imported implicitely. Example: Module a imports module B a few symbols from module C, module B imports module C, so module A imports whole module C implicitely. Importing macros is difficult too, because you have to import all forms in the expansions, Something does not work correctly: (import cstdio (fprintf)) also imports printf and macros.

tomhrr commented 6 years ago

Thanks, the problems from the last comment have now been fixed.