Confusion of concepts
I think a large part of your difficulties comes from a confusion of concepts. Some of the methodologies that are practical for other languages are sort of a hinderance in Julia. They need to be adapted somewhat to avoid “the square peg in a round hole” situation.
A few helpful mappings:
- “Namespace” or “Named global scope” → Julia “module”
- “Software module” → Julia “package”
- (though a simple namespace (Julia “module”) might be sufficient in certain cases).
- Loading code into memory (might include “module”-s) → Julia
include() statement.
- (typical application: assemble code from multiple files).
- Loading Julia “package” into memory → Julia
import or using statement.
- (in turn, packages often use include() to assemble code from multiple files).
dispatch, responsibility, relevance…
Correct: Typically, “software modules” are used to enforce “responsibility”. You encapsulate some functionnality within a software module to guarantee proper functionality wilst providing a simple/effective interface for your user.
Typically, you would use a Julia “package” to do this function, though a simple Julia “module” might be sufficient (depending on the circumstances).
At best, Julia packages & modules offer soft enforcement of responsibility. Nothing is really hidden or off limits from the outside world as long as you use the path.to.the_housing_module.and_varible_name = some_new_value.
Probably as a consequence of this lack of information hiding, Julia packages or modules are rarely used for this purpose. Instead, Julia developpers tend to simply exercise caution when writing a feature for their package/module. For example, a communication package might “provide” a messaging queue with the appropriate enque(::MyQueue) and dequeue(::MyQueue) functions, and a bunch of low-level facilities to implement these features. They might also provide user-facing facilities (ex: pack(::MyMessage)/unpack(::MyMessage)), and base objects (MyMessage, MyQueue, …) that are needed for the implementation. Since there is no real information hiding in Julia, it tends to be most practical to place everything in a single Julia “module” or “package” (ex: MySecureMessaging). There is no real need to split a “software unit” like a messaging queue into its own (sub-) “module”.
So, I guess “responsibility” in Julia is more about explicitly documenting your interface & intended use to the user. For example, you might want to prefix functions not meant to be called directly by the user with “_”. Some people rely on what functions/variables (Julia symbols) are export-ed from a module, but I often ommit exporting functions to avoid name collisions (a problem you have already noted). In other words, I often provide user-facing functions that are never get export-ed.
What Julia’s dispatch system does differently than in most languages is allow the same function name to be re-used for different permutations of argument types. This avoids having to write long functions names like addIntInt(x::Int, y::Int), addIntFloat(x::Int, y::Float64), …
And yes: dispatch is no longer the domain of classes as it is in most OO-languages I’m aware of. In Julia, a more flexible dispatch system is available for all functions, and resolved for different (potentially complex) permutations of their argument types.
Mapping of object-level dispatch from most OO paradigms:
-
a_circle.draw() → draw(a_circle)
IMO, it is actually more natural to write Julia’s draw(a_circle) than using a_circle.draw() in most cases anyhow.
Relevance: That sounds about right: Julia “modules” (namespaces) are a good way to organize things by relevance. I will elaborate a bit in the following section.
Why use Julia “modules” (namespaces)?
It might be hard requirement:
- Julia “modules” are required to wrap the contents of each Julia “package” of the same name.
About packages:
- Packages are mostly a way to bundle-up software into units that can be shared amongst projects.
- You don’t technically need to bundle things up into a package if your code is project-specific (needed only for one project).
Otherwise, I find “modules” (namespaces) can be useful to logically separate code. In this case, “modules” act sort of like folders, to de-clutter the “user-facing” namespace, and avoid name collisions:
module MySortingPackage
module BubbleSort
#Supporting functions here
sort!(data) = ... #Entry point of algorithm
end
module QuickSort
#Supporting functions here
sort!(data) = ... #Entry point of algorithm
end
bubblesort!(data) = BubbleSort.sort!(data)
quicksort!(data) = QuickSort.sort!(data)
export bubblesort!, quicksort!
end #MySortingPackage
But if you don’t actually have high risk of name collisions (ex: not that many supporting functions), most people just put everything in a single namespace (Julia “module”).