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