Some thoughts for consideration, but not a proposed architecture.
As you recognise, the functional model and the ‘object’ model organize data and code differently. Both can encapsulate code and data, both can represent forms of polymorphism, object orientation favours inheritance whereas the functional model favours composition.
Introspection
In Elixir (erlang, BEAM, OTP) the fundamentals exist:
- For modules,
module.module_info/0
(and module.module_info/1
) can be used to identify the functions that are exports and much more. For example:
iex> Calendar.module_info
[
module: Calendar,
exports: [
__info__: 1,
get_time_zone_database: 0,
put_time_zone_database: 1,
strftime: 2,
strftime: 3,
truncate: 2,
behaviour_info: 1,
module_info: 0,
module_info: 1,
compatible_calendars?: 2
],
attributes: [vsn: [64017337862377723512439896651098344616]],
compile: [
version: '8.0',
options: [:no_spawn_compiler_process, :from_core, :no_core_prepare,
:no_auto_import],
source: '/home/build/elixir/lib/elixir/lib/calendar.ex'
],
md5: <<48, 41, 74, 113, 137, 7, 188, 147, 27, 101, 152, 222, 121, 121, 204,
168>>
]
- You can also leverage the info/1 function that is added to each module at compile time
Encapsulation
Elixir provides behaviours and protocols to support a form of polymorphism. Behaviour
is a way to enforce a consistent interface to a module and therefore can be used to define different concrete implementations for an abstract concept. In Elixir, for example, the Calendar behaviour defines the functions required to be implemented in any module that supports the Calendar
behaviour. You can see if a module implements a behaviour:
iex> Calendar.ISO.module_info(:attributes)
[vsn: [261504321433274647483801775536816486412], behaviour: [Calendar]]
You can also find the callbacks defined for a behaviour with the following although this is probably of limited use since if you know the behaviour then you know the callbacks. And since you can’t really introspect the parameters or identify the semantics of the functions there’s not really anywhere to go.
iex> Calendar.behaviour_info :callbacks
[
year_of_era: 1,
valid_time?: 4,
valid_date?: 3,
time_to_string: 4,
...
]
Protocols are commonly defined to present a standard functional interface to data types (including, and very commonly, structs). The Inspect protocol is probably the most well known but its easy to implement your own protocol and implement it for the types you require.
Application to extensions
In considering extensions, behaviours and protocols would likely form the basis of the architecture. Introspection of a given module can be used to determine if it implements a given behaviour.
-
Module loading is, as @hauleth noted, a matter of having the BEAM file in the code load path. To be more deliberate you can use Code.load_file/1
.
-
System-wide discovery of which modules implement a given behaviour is less straight forward and this article and the accompanying library behaviour-relfection may give you additional ideas.
With the tools above you could try a naive extension approach by:
- Load a user-supplied BEAM file and capture its module name from the file name
- Check if the module implements the expected extension behaviour
- Register the module into your core application callback mechanism (this part up to you)