V: Import individual symbols

Created on 8 Jul 2020  路  19Comments  路  Source: vlang/v

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?

Feature Request

Most helpful comment

import mod.sub_mod
import mod {fn1, fn2}

All 19 comments

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 ui has _submodules_, named button and textbox ?
What will import 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, submodule foo.xxx, then defining a function foo.xxx should 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. 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jtkirkpatrick picture jtkirkpatrick  路  3Comments

clpo13 picture clpo13  路  3Comments

arg2das picture arg2das  路  3Comments

lobotony picture lobotony  路  3Comments

taojy123 picture taojy123  路  3Comments