Something like this is very repetitive:
import html
template := html.Tag{name: 'html', children: [
html.Tag{name: 'head', children: [
html.Tag{name: 'meta', props: { 'http-equiv': 'Content-Type', 'content': 'text/html;charset=UTF-8' }},
html.Tag{name: 'meta', props: { 'name': 'referrer', 'content': 'origin-when-cross-origin'}},
html.Tag{name: 'title', text: 'Vex SQLite Test'},
html.Tag{name: 'style', text: '
body {
width: 36rem;
margin: 0 auto;
font-size: 1.4rem;
font-family: Palatino, "Palatino Linotype", Georgia, "Lucida Bright",
Cambria, Tahoma, Verdana, Arial, sans-serif;
color: #0C3C26;
}
'}
]},
html.Tag{name: 'body', children: body}
]}
Now, I know what the manual says on this subject:
Note that you have to specify the module every time you call an external function. This may seem verbose at first, but it makes code much more readable and easier to understand, since it's always clear which function from which module is being called. Especially in large code bases.
I completely agree with this - when you make individual calls to external functions, specifying the module-name explicitly does make the code more readable.
But for DSL-style APIs such as this one for HTML (or another UI library, a parser combinator library, or many other things) where repeated calls are made to the same external function, the repetitive html.Tag calls quickly become less helpful, both reading and writing, to the point of becoming just noise.
My suggestion would be explicit member imports, like this:
import Tag from html
template := Tag{name: 'html', children: [
Tag{name: 'head', children: [
Tag{name: 'meta', props: { 'http-equiv': 'Content-Type', 'content': 'text/html;charset=UTF-8' }},
Tag{name: 'meta', props: { 'name': 'referrer', 'content': 'origin-when-cross-origin'}},
Tag{name: 'title', text: 'Vex SQLite Test'},
Tag{name: 'style', text: '
body {
width: 36rem;
margin: 0 auto;
font-size: 1.4rem;
font-family: Palatino, "Palatino Linotype", Georgia, "Lucida Bright",
Cambria, Tahoma, Verdana, Arial, sans-serif;
color: #0C3C26;
}
'}
]},
Tag{name: 'body', children: body}
]}
In a sense, this is just as clear and explicit - in the original example, you have to see in the import html statement to understand how the html.Tag symbols get resolved; and similarly, with member imports, you have to see import Tag from html to understand how the Tag symbol gets resolved. The relationships between the imports and the symbols aren't really different - the only difference is what you're importing.
To be clear, I don't agree with what e.g. C# does in this regard - defaulting to wildcard imports is extremely confusing and makes some C# code basically unreadable, if not for an IDE where you can hover over every statement to view the resolved names. I don't like that, and I am not suggesting wildcard imports, e.g. no import * from html, which would make the resolution of local symbols ambiguous to the compiler, as well as to a person reading the code.
There's a big difference between having symbol imports declared locally in the file (as with existing imports) versus having symbols in your local file declared externally in a different source-file.
The proposed imports are static - as long as we stay away from dynamic imports, I don't think a feature like this complicates things or breaks the simplicity of the language?
I see your point and I agree. Your proposal is very careful and avoids the horrors of wildcard imports.
I think instead of import x from y, we can also use Go style aliases:
alias Tag = html.Tag
Scratch that, doing it via imports is better.
I could also see this being useful in V UI code: not having to repeat ui. a thousand times.
import ui.{button, textbox}
button(....)
textbox(...)
It's still clear which module these functions are from.
What happens if ui has submodules, named button and textbox ?
What will import ui.{button,textbox} do?
What happens if
uihas _submodules_, namedbuttonandtextbox?
What willimport ui.{button,textbox}do?
Importing submodules already works and creates a local symbol, similar to what I'm proposing - this already works:
import net.http
req := http.Request{ /*...*/ }
No reason this syntax shouldn't work for submodules, is there?
import net.{http, urllib}
For that matter, if someone prefers enumerating imports, no reason this shouldn't work too:
import ui.button
import ui.textbox
This is a little more verbose, but it diffs better under source-control.
I don't see this in the docs (?) but I could swear I've also seen block import statements in code somewhere?
import {
ui.button
ui.textbox
}
If that's possible, that would be my personal preference - more git-friendly but not too verbose. (Considering how much verbosity this feature can help you remove from the body of a source-file, I'd lean towards the most readable syntax rather than the shortest.)
Block imports were removed so that there's only one way :) easier to grep as well
If you have module foo, submodule foo.xxx, then defining a function foo.xxx should not be allowed.
If you have module
foo, submodulefoo.xxx, then defining a functionfoo.xxxshould not be allowed.
I'd assume that's already that's the case with or without this feature? 馃檪
The issue name only mentions functions; as I understand from the above conversation, types and constants should also be affected?
I currently have a working impl for functions only; will draft-PR once this reply is addressed
Implemented form is import mod {foo, bar}
@AeroStun how will you be doing submodules? Just curious about the syntax.
Haven't bothered with them yet (learned of their existence before I went to bed)
The issue name only mentions functions; as I understand from the above conversation, types and constants should also be affected?
Good point, yeah - it should work for any kind of symbol, I've updated the title. 馃憤
I currently have a working impl for functions only; will draft-PR once this reply is addressed
Very cool! 馃榾
Implemented form is
import mod {foo, bar}
I think personally I would prefer the more verbose form:
import mod.foo
import mod.bar
It's easy to see how this could get tedious for large number of imports - but the same argument could be made for large number of module imports, and the block imports features was removed, so...
@medvednikov what do you think? Blocks vs fully qualified imports? (something else??) 馃
That's the syntax if you have submodules named foo and bar. This idea is to import specific things from individual modules/submodules. So you might have
import mod {foo, bar}
import mod.sub1 {baz, baf}
import mod.sub2 {other1, other2}
@JalonSolov but you can't have a submodule and any other symbol with the same qualified name - so you can think of this feature as extending the existing feature to support all symbols, not just modules.
So in your example:
import mod.foo
import mod.bar
import mod.sub1.baz
import mod.sub1.baf
import mod.sub2.other1
import mod.sub2.other2
The problem with import mod.sym, is that figuring out whether sym is a symbol or a submodule requires looking at the filesystem (at least for an external parser, like a build-system trying to figure out the deps-tree).
I find much simpler the idea of knowing what is a module/submodule and what is a symbol directly from the syntax.
Please do not change submodules.
I have no objection to import mod.sub2 {fn1, type2}. It will boil down to just a simple lookup in the parser most likely.
I do not like import net.{http, urllib} - it would make searching for what imports what difficult, and will introduce ambiguities for parsing.
import mod.{sym_a, sym_b} introduces little ambiguity and very little difficulty; the innermost full module name would just end in either whitespace (when no symbol is globally imported), or .{.
That said, I do find better to well separate the modules from the symbols, with a whitespace
import mod.sub_mod
import mod {fn1, fn2}
@AeroStun and @spytheman solid arguments, makes sense. 馃憤
Most helpful comment
import mod.sub_modimport mod {fn1, fn2}