diff options
author | chai <chaifix@163.com> | 2021-10-30 11:32:16 +0800 |
---|---|---|
committer | chai <chaifix@163.com> | 2021-10-30 11:32:16 +0800 |
commit | 42ec7286b2d36a9ba22925f816a17cb1cc2aa5ce (patch) | |
tree | 24bc7009457a8d7500f264e89946dc20d069294f /Data/Libraries/Penlight/docs_topics/09-discussion.md | |
parent | 164885fd98d48703bd771f802d79557b7db97431 (diff) |
+ Penlight
Diffstat (limited to 'Data/Libraries/Penlight/docs_topics/09-discussion.md')
-rw-r--r-- | Data/Libraries/Penlight/docs_topics/09-discussion.md | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/Data/Libraries/Penlight/docs_topics/09-discussion.md b/Data/Libraries/Penlight/docs_topics/09-discussion.md new file mode 100644 index 0000000..7942d95 --- /dev/null +++ b/Data/Libraries/Penlight/docs_topics/09-discussion.md @@ -0,0 +1,91 @@ +## Technical Choices + +### Modularity and Granularity + +In an ideal world, a program should only load the libraries it needs. Penlight is +intended to work in situations where an extra 100Kb of bytecode could be a +problem. It is straightforward but tedious to load exactly what you need: + + local data = require 'pl.data' + local List = require 'pl.List' + local array2d = require 'pl.array2d' + local seq = require 'pl.seq' + local utils = require 'pl.utils' + +This is the style that I follow in Penlight itself, so that modules don't mess +with the global environment; also, `stringx.import()` is not used because it will +update the global `string` table. + +But `require 'pl'` is more convenient in scripts; the question is how to ensure +that one doesn't load the whole kitchen sink as the price of convenience. The +strategy is to only load modules when they are referenced. In 'init.lua' (which +is loaded by `require 'pl'`) a metatable is attached to the global table with an +`__index` metamethod. Any unknown name is looked up in the list of modules, and +if found, we require it and make that module globally available. So when +`tablex.deepcompare` is encountered, looking up `tablex` causes 'pl.tablex' to be +required. . + +Modifying the behaviour of the global table has consequences. For instance, there +is the famous module `strict` which comes with Lua itself (perhaps the only +standard Lua module written in Lua itself) which also does this modification so +that global variiables must be defined before use. So the implementation in +'init.lua' allows for a 'not found' hook, which 'pl.strict.lua' uses. Other +libraries may install their own metatables for `_G`, but Penlight will now +forward any unknown name to the `__index` defined by the original metatable. + +But the strategy is worth the effort: the old 'kitchen sink' 'init.lua' would +pull in about 260K of bytecode, whereas now typical programs use about 100K less, +and short scripts even better - for instance, if they were only needing +functionality in `utils`. + +There are some functions which mark their output table with a special metatable, +when it seems particularly appropriate. For instance, `tablex.makeset` creates a +`Set`, and `seq.copy` creates a `List`. But this does not automatically result in +the loading of `pl.Set` and `pl.List`; only if you try to access any of these +methods. In 'utils.lua', there is an exported table called `stdmt`: + + stdmt = { List = {}, Map = {}, Set = {}, MultiMap = {} } + +If you go through 'init.lua', then these plain little 'identity' tables get an +`__index` metamethod which forces the loading of the full functionality. Here is +the code from 'list.lua' which starts the ball rolling for lists: + + List = utils.stdmt.List + List.__index = List + List._name = "List" + List._class = List + +The 'load-on-demand' strategy helps to modularize the library. Especially for +more casual use, `require 'pl'` is a good compromise between convenience and +modularity. + +In this current version, I have generally reduced the amount of trickery +involved. Previously, `Map` was defined in `pl.class`; now it is sensibly defined +in `pl.Map`; `pl.class` only contains the basic class mechanism (and returns that +function.) For consistency, `List` is returned directly by `require 'pl.List'` +(note the uppercase 'L'), Also, the amount of module dependencies in the +non-core libraries like `pl.config` have been reduced. + +### Defining what is Callable + +'utils.lua' exports `function_arg` which is used extensively throughout Penlight. +It defines what is meant by 'callable'. Obviously true functions are immediately +passed back. But what about strings? The first option is that it represents an +operator in 'operator.lua', so that '<' is just an alias for `operator.lt`. + +We then check whether there is a _function factory_ defined for the metatable of +the value. + +(It is true that strings can be made callable, but in practice this turns out to +be a cute but dubious idea, since _all_ strings share the same metatable. A +common programming error is to pass the wrong kind of object to a function, and +it's better to get a nice clean 'attempting to call a string' message rather than +some obscure trace from the bowels of your library.) + +The other module that registers a function factory is `pl.func`. Placeholder +expressions cannot be directly calleable, and so need to be instantiated and +cached in as efficient way as possible. + +(An inconsistency is that `utils.is_callable` does not do this thorough check.) + + |