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 export
ing 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”).