Stream: helpdesk (published)

Topic: Not abusing import X as Y


view this post on Zulip Luke (Apr 27 2021 at 15:17):

I'm currently loving the ability to finally get a little autocomplete sanity going in my Jupyter notebooks by doing things like import Parquet as pq; <run cell>; pq.<tab>. However, I still spend most of my coding time in Python, and I recognize this approach might not be considered very elegant by Julia users. If it's just a matter of aesthetics, I don't mind being inelegant. My gripe with much of the pushback I've seen against Python-style import renaming is that while the compiler and type system know which function to call and what package it's in, I don't, and it's often helpful to my understanding of my own code see those little reminders of where functions come from (since where they come from informs me about what they do).

My concern, though, is that I'm a Julia newbie and there are good beyond-aesthetic technical reasons to not abuse import X as Y that I don't understand. Is there something I'm probably missing here?

Also, if it's generally actually okay to stylistically avoid naked using statements and always do import PackageName as pn instead for explicitness without losing too much conciseness, is it better Julia style to use uppercase (import Parquet as PQ) or lowercase (import Parquet as pq)? I have been using lowercase because everything is lowercase in Python, but I'm now wondering if uppercase is better, since Julia uses CamelCase for module names.

Also, are there others out there who find the tab autocomplete and explicitness helpful? Are you Python programmers too, or is this maybe something that goes beyond comfort with a certain syntactical style?

view this post on Zulip Luke (Apr 27 2021 at 15:31):

Also I'm curious if folks have other tactics for autocomplete, code explicitness, and enabling easier removal of unused packages that I feel I rely on import X as Y for.

One thought that comes to mind is Jupyter autocomplete could also be done with using PackageName; PackageName.<tab> and then after selecting a function_name one can delete the PackageName.part. If one is willing to be strict with oneself, one can add an explicit using PackageName: function_name import every time a new function is used, and later then delete the naked using PackageName line before committing the code, I guess, too.

view this post on Zulip Jakob Nybo Nissen (Apr 29 2021 at 15:03):

I was a Python programmer. One of the problems with copying Pythons idioms over to Julia is that the Julian mechanism to distinguish public from private data is whether it's exported or not, and you can only tell which it is if you do using.
In general, when it comes to autocomplete, I think Julia needs more work, but I'm pretty certain the ideal solution isn't a hierarchy of namespaces that you access with dot-syntax. Instead, I hope to have more intelligent tab-completion which calls methodswith and such with the correct types. At least if your concern is findability - "what can I do with this type?" or "which functions are in this package?".
Wrt. explicitness, then yes, in package code, I think import X as Y is alright, but I would only actually use it if I know there is a risk of confusion, else I would just import the things explitly and individually.

view this post on Zulip Luke (Apr 29 2021 at 18:38):

Jakob Nybo Nissen said:

the Julian mechanism to distinguish public from private data is whether it's exported or not, and you can only tell which it is if you do using.

Ah! This makes so much sense. I guess in general I'm just used to things being clear from the way they're written (like the _internal_function vs. stable_public_api_function convention) in Python, and this leaves me struggling with Julia's habit of making these things clear by how they function (like whether something's exported and available when you are using it).

In general, when it comes to autocomplete, I think Julia needs more work, but I'm pretty certain the ideal solution isn't a hierarchy of namespaces that you access with dot-syntax. Instead, I hope to have more intelligent tab-completion which calls methodswith and such with the correct types. At least if your concern is findability - "what can I do with this type?" or "which functions are in this package?".

I agree that Julia should be able to do a lot, but my concern here is that it's hard to figure out the initial text that can be autocompleted when structs themselves don't possess functions and imports don't keep module names/aliases. It's easy to see how one can type pkg.<tab> to convey the idea "I want to see the things inside pkg as a dropdown", but how do you create the same user experience for triggering methodswith? Maybe select the name of a struct and hit tab to find functions that take it as a parameter? I think there is a pretty painful UX sacrifice made by Julia avoiding syntax that explicitly conveys one-to-one struct-function and module-function relationships that exist in abundance when we write code.

Wrt. explicitness, then yes, in package code, I think import X as Y is alright, but I would only actually use it if I know there is a risk of confusion, else I would just import the things explitly and individually.

I think the hard part here is constantly trying to make judgement calls about the risk of confusion. I yearn for a simple solution like in Python where I can just always use absolute imports and rely on aliases to keep my code clean and concise.

I think you're right that ultimately individual imports is probably the best style. I think the VS Code plugin already lets you ctrl-click your way from a function name back to an explicit individual import, so it's not hard to find where things come from. What's tough is actually writing the code in this paradigm. Perhaps there's a middle ground for avoiding naked using statements... something like initially heavily using aliasing, and then refactoring to explicit imports. Maybe I could even write a little utility to automatically convert aliased imports to explicit imports...

view this post on Zulip Luke (Apr 29 2021 at 18:40):

Anyhow, thank you for taking the time to share your thoughts and advice, Jakob, this was very helpful to me!

view this post on Zulip Jakob Nybo Nissen (Apr 30 2021 at 19:39):

Exactly, we should have methodswith autocompletion. Ideally, that would be even better than Python's because it would be insensitive to whether it's the first or second argument.

view this post on Zulip Zachary P Christensen (Apr 30 2021 at 22:05):

I love the new syntax and I've mostly just read python code to learn things in the past, so maybe there is some natural beauty to it. My personal approach to public vs private functions has been M._fxn is private. M.fxn is usually documented and any breaking changes to that will be reflected in semver, but it isn't the purpose of the package and most people don't need it. Then I export a limited set of methods (usually those presented in the README). I know it is a bit subjective but I think it makes it easy to lazily discover internals through M.<tab> without having to search through docs whilst not polluting the Main.

view this post on Zulip Luke (May 01 2021 at 03:17):

Zachary P Christensen said:

I love the new syntax and I've mostly just read python code to learn things in the past, so maybe there is some natural beauty to it. My personal approach to public vs private functions has been M._fxn is private. M.fxn is usually documented and any breaking changes to that will be reflected in semver, but it isn't the purpose of the package and most people don't need it. Then I export a limited set of methods (usually those presented in the README). I know it is a bit subjective but I think it makes it easy to lazily discover internals through M.<tab> without having to search through docs whilst not polluting the Main.

If I'm understanding you correctly, you explicitly mark "be careful this function may change suddenly irrespective of the semver" functions with an underscore in Julia so that regardless of how a user imports/explores your module, they're still able to see the warning in the first character of the function name? If so, this sounds really smart, and I'll have to start getting in the habit of that, too!

More broadly, I think this idea underscores how there seems to be so little cost to just doing things "simply" by Python convention in Julia, even though tools like methodswith and dynamic dispatch are more powerful tools that handle situations which Python conventions don't.

Bluntly put, right now Julia doesn't really have good autocomplete or auto "where did this function get imported from?", and even if it did, I don't know that I've really seen a clear argument as to why using the Python conventions is bad. If I import X as Y and title things _private_function(), it might make for easy to read, easy to write, effective Julia. And it's hard to see that even if Julia gets really slick autocomplete that code written in this style would do any real harm. I'm not saying I should lazily export nothing and expect others to also import X as Y my modules, but the more I think about it, the more it just feels okay to "overuse" import aliasing and Python private function naming convention in Julia even though the language comes with more complicated programmatic tools that could theoretically be used instead.

view this post on Zulip Mason Protter (May 04 2021 at 02:16):

Yeah, I don't think there's anything wrong with that approach


Last updated: Oct 02 2023 at 04:34 UTC