paul@810 | 1 | = Program Structure = |
paul@810 | 2 | |
paul@810 | 3 | A program consists of a number of '''modules''' with each module providing its own namespace. Modules can be organised within '''packages''' which define a hierarchical relationship between them. However, the relationship between modules is not automatically exposed within the program: it is more appropriate to consider modules as independent entities that have a particular naming scheme. |
paul@810 | 4 | |
paul@810 | 5 | Within each module a hierarchy of namespaces is provided by '''classes''', with each class potentially containing other classes, and so on. Also appearing within modules and classes are '''functions''', with those appearing within classes being regarded as '''methods'''. |
paul@810 | 6 | |
paul@810 | 7 | Function namespaces are considered separately from module and class namespaces and are not considered part of the general namespace hierarchy, instead merely appearing as objects at the edges of that hierarchy. Functions may also be defined within functions, and such inner functions will be referenced within their parent functions, but no hierarchical relationship will exist between their namespaces. |
paul@810 | 8 | |
paul@810 | 9 | Thus, a program provides a static namespace hierarchy consisting of modules containing classes (containing other classes, and so on) plus functions. Objects residing within namespaces are accessed via '''attributes''' of those namespaces. |
paul@810 | 10 | |
paul@810 | 11 | {{{{#!table |
paul@810 | 12 | {{{#!graphviz |
paul@810 | 13 | //format=svg |
paul@810 | 14 | //transform=notugly |
paul@810 | 15 | digraph program { |
paul@810 | 16 | node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Program structure"]; |
paul@810 | 17 | edge [tooltip="Program structure"]; |
paul@810 | 18 | rankdir=LR; |
paul@810 | 19 | |
paul@810 | 20 | program [label="program",shape=folder,style=filled,fillcolor=cyan]; |
paul@810 | 21 | |
paul@810 | 22 | subgraph { |
paul@810 | 23 | rank=same; |
paul@810 | 24 | moduleM [label="module M",style=filled,fillcolor=gold]; |
paul@810 | 25 | moduleN [label="module N\n(package)",style=filled,fillcolor=gold]; |
paul@810 | 26 | } |
paul@810 | 27 | |
paul@810 | 28 | subgraph { |
paul@810 | 29 | rank=same; |
paul@810 | 30 | classA [label="class A"]; |
paul@810 | 31 | moduleNP [label="module N.P",style=filled,fillcolor=gold]; |
paul@810 | 32 | } |
paul@810 | 33 | |
paul@810 | 34 | subgraph { |
paul@810 | 35 | rank=same; |
paul@810 | 36 | functionF [label="function f",shape=ellipse]; |
paul@810 | 37 | functionG [label="function g",shape=ellipse]; |
paul@810 | 38 | classB [label="class B"]; |
paul@810 | 39 | classC [label="class C"]; |
paul@810 | 40 | } |
paul@810 | 41 | |
paul@810 | 42 | subgraph { |
paul@810 | 43 | rank=same; |
paul@810 | 44 | functionJ [label="function j",shape=ellipse]; |
paul@810 | 45 | functionH [label="function h",shape=ellipse]; |
paul@810 | 46 | functionK [label="function k",shape=ellipse]; |
paul@810 | 47 | } |
paul@810 | 48 | |
paul@810 | 49 | program -> moduleM; |
paul@810 | 50 | program -> moduleN; |
paul@810 | 51 | |
paul@810 | 52 | moduleM -> classA; |
paul@810 | 53 | moduleN -> moduleNP; |
paul@810 | 54 | moduleNP -> classC; |
paul@810 | 55 | |
paul@810 | 56 | classA -> functionF; |
paul@810 | 57 | classA -> functionG; |
paul@810 | 58 | classA -> classB; |
paul@810 | 59 | classB -> functionH; |
paul@810 | 60 | classC -> functionK; |
paul@810 | 61 | |
paul@810 | 62 | functionG -> functionJ [style=dashed]; |
paul@810 | 63 | } |
paul@810 | 64 | }}} |
paul@810 | 65 | || |
paul@810 | 66 | {{{#!python numbers=disable |
paul@810 | 67 | # module M |
paul@810 | 68 | |
paul@810 | 69 | class A: |
paul@810 | 70 | def f(...): pass |
paul@810 | 71 | |
paul@810 | 72 | def g(...): |
paul@810 | 73 | def j(...): pass |
paul@810 | 74 | |
paul@810 | 75 | class B: |
paul@810 | 76 | def h(...): pass |
paul@810 | 77 | |
paul@810 | 78 | # module N |
paul@810 | 79 | |
paul@810 | 80 | ... |
paul@810 | 81 | |
paul@810 | 82 | # module N.P |
paul@810 | 83 | |
paul@810 | 84 | class C: |
paul@810 | 85 | def k(...): pass |
paul@810 | 86 | }}} |
paul@810 | 87 | }}}} |
paul@810 | 88 | |
paul@810 | 89 | == Referencing the Structure == |
paul@810 | 90 | |
paul@810 | 91 | Each part of the structure is catalogued using an '''object path''', indicating its location in the structure hierarchy, and a reference indicating its nature and origin. For example: |
paul@810 | 92 | |
paul@810 | 93 | || '''Object Path''' || '''Reference''' || '''Explanation''' || |
paul@810 | 94 | || `M.A` || `<class>:M.A` || The definition of class `A` in module `M` || |
paul@810 | 95 | || `O.A` || `<class>:M.A` || A reference to `M.A`, a class || |
paul@810 | 96 | || `N.P.C.k` || `<function>:N.P.C.k` || The definition of method `k` in class `C` of module `N.P` || |
paul@810 | 97 | || `O.k` || `<function>:N.P.C.k` || A reference to `N.P.C.k`, a function || |
paul@810 | 98 | || `O.values` || `<instance>:__builtins__.list.list` || An object identified as an instance of class `__builtins__.list.list` || |
paul@810 | 99 | || `__main__.M` || `<module>:M` || A reference to module `M` || |
paul@810 | 100 | || `__main__.counter` || `<var>` || An undetermined object called `counter` in the `__main__` module || |
paul@810 | 101 | |
paul@810 | 102 | The reference therefore expresses the '''kind''' of object (class, function, instance, module or undetermined variable), possibly accompanied by an object type, and also possibly accompanied by an alias indicating where the reference was obtained. |
paul@810 | 103 | |
paul@810 | 104 | == Classes == |
paul@810 | 105 | |
paul@810 | 106 | Classes are regarded as statically-defined objects, meaning that they are only evaluated once and must not be defined within conditional sections or within functions. Base classes must be expressed using readily-identifiable class names, since any potential ambiguity or uncertainty with the identity of base classes could result in a class inheritance hierarchy that is effectively dynamic. |
paul@810 | 107 | |
paul@810 | 108 | When '''instantiated''', classes provide '''instances''' that provide the attributes of each class's namespace plus any '''inherited''' attributes from base classes that would not be provided by the class itself. In addition, instances provide their own instance attributes. |
paul@810 | 109 | |
paul@810 | 110 | == Functions == |
paul@810 | 111 | |
paul@810 | 112 | Functions are regarded as statically-defined objects, but they may be defined either with names within other named functions or as '''lambdas''' (functions without names) within named functions or other lambdas. Thus, unlike classes, functions may be defined once but replicated many times, each time with different accompanying state information. Such state information needs to be provided via default parameters: it is not detected and propagated automatically. Closures are not supported: any state from enclosing scopes must be supplied at the point of definition of a function; it is not acquired from outer functions by name. |
paul@810 | 113 | |
paul@810 | 114 | {{{#!python numbers=disable |
paul@810 | 115 | def outer(a): |
paul@810 | 116 | b = 2 |
paul@810 | 117 | def inner(c, a=a, b=b): |
paul@810 | 118 | # a initialised using default from outer scope |
paul@810 | 119 | # b also initialised using default from outer scope |
paul@810 | 120 | return a, b, c |
paul@810 | 121 | b = 4 |
paul@810 | 122 | |
paul@810 | 123 | outer(1)(3) # returns (1, 2, 3) not (1, 4, 3) |
paul@810 | 124 | }}} |
paul@810 | 125 | |
paul@810 | 126 | === Lambdas === |
paul@810 | 127 | |
paul@810 | 128 | Lambdas are given special names for the purposes of identification within the program structure, being named relative to the scope in which they are defined. For example, the first lambda appearing within module `N` would have an object path of `N.$l0`, and a subsequent lambda within the same scope would have an object path of `N.$l1`. Lambdas may appear within classes and functions, including lambdas themselves. For example: |
paul@810 | 129 | |
paul@810 | 130 | {{{#!python numbers=disable |
paul@810 | 131 | def f(x): |
paul@810 | 132 | return lambda y, x=x: lambda z, x=x, y=y: (x, y, z) |
paul@810 | 133 | }}} |
paul@810 | 134 | |
paul@810 | 135 | Here, within a module `M`, the outer lambda would have the object path `M.f.$l0` whereas the inner lambda would have the object path `M.f.$l0.$l0`. |