48 | | |
49 | | == Ideas for Sysel == |
50 | | |
51 | | Notes on features which are almost certain to appear in Sysel in one way another. |
52 | | |
53 | | === Code organization === |
54 | | |
55 | | Sysel shall employ ''packages'' and ''modules''. Together, these two constructs provide full information about organization of the codebase and allow for a certain degree of freedom in how finely the code is partitioned, both in terms of namespace and code volume. |
56 | | |
57 | | ==== Packages ==== |
58 | | |
59 | | Packages provide two main features: a namespace and visibility controls. Packages thus provide a greater level of isolation than mere classes and allow safe composition of code developed by different (uncoordinated) teams. Packages can have a well defined API/ABI and can be delivered in compiled form via libraries. Each package has a name which must be fully qualified. |
60 | | |
61 | | Within a package all symbol references only need to be qualified relative to the package. To reference symbols outside of the current package they must be either imported or the reference must be fully qualified. (TODO: Should we enforce explicit import of all symbols?) Symbols can only be imported individually or in a qualified manner. This ensures that there can be no collisions of symbols from different namespaces (which need not be under the control of the same entity). When importing symbols the symbols being imported must be specified using their fully qualified names. |
62 | | |
63 | | ==== Modules ==== |
64 | | |
65 | | Modules provide a complementary and finer-grained means of decomposition. Usually each source file corresponds to exactly one module. For each module we define its (unqualified) name and fully qualified name of the package it belongs to (which 'anchors' it in the code base). Conversely, each package specifies all modules it consists of. Consequently, for each module we can determine which package it belongs to and for each package we can determine all modules (and thus all symbols) it consists of. |
66 | | |
67 | | As we explained, modules allow the source code to be broken into separate files and at the same time tie it together in a formal manner. When building a package or program, there is thus no need to specify all its source files informally in a makefile. It is sufficient to point the compiler to directories where it should look for source files and tell it which package we want built. |
68 | | |
69 | | Modules do not represent a namespace. Any symbols defined or imported in one module will be accessible (unqualified) in any other module within the same package. Names of global symbols in all modules of a package must therefore be coordinated. Note that due to object-oriented nature of the language there are usually not very many global symbols defined in a package and also packages are assumed to be under the control of a single entity. |
70 | | |
71 | | Definitions of classes can be split across multiple modules (but not packages). Thus large classes can be split accross multiple source files. |
72 | | |
73 | | === Dynamic linking === |
74 | | |
75 | | It should be possible to use, with similar simplicity and the same level of static type checking, not only ''compulsory libraries'', but also ''optional libraries'' and ''plugin libraries''. |
76 | | |
77 | | Compulsory libraries are those required every time the executable is invoked (equivalent to `gcc -lname`). Optional libraries are only loaded once the application touches some symbol from the library. This is a very useful feature that allows building binaries with all optional dependencies enabled, yet the user need not install all these libraries if they do not want to. This helps avoiding ''dependency avalanches''. |
78 | | |
79 | | Plugin libraries are those where multiple libraries can exist written again some common plugin interface. One possibility is to have ''packages'' implement ''package interfaces''. A package could be loaded at run time, a reference to it stored to a variable whose type is the ''package interface'' type. Then it would be possible to refer to symbols within the dynamic package using standard qualified names (e.g. `P.symbol`). This enables full static type checking / interface checking for both the implementor and user of the plugin. |
80 | | |
81 | | === Remote objects === |
82 | | |
83 | | ==== Basics ==== |
84 | | |
85 | | HelenOS IPC is usually employed in an RPC-like style. Remote objects would support asynchronous messaging in the language itself. Remote object classes (and interfaces) form a separate hierarchy of inheritance to the ''local'' classes and interfaces. Remote interfaces are equivalent to IPC interfaces now usually defined in HelenOS in `uspace/lib/c/include/ipc`. They would naturally support (multiple) inheritance. Servers contain remote classes which implement these interfaces. |
86 | | |
87 | | When a client wants to use some service, they are given a reference to a remote object. This reference identifies not only the server which we talk to, but possibly also the individual resource within the server that we are accessing. For a contrived example, a console server might provide the two interfaces: |
88 | | |
89 | | {{{ |
90 | | interface IConsole, remote is |
91 | | fun GetVC(vc_index : int) : IVC; |
92 | | end |
93 | | |
94 | | interface IVC, remote is |
95 | | fun GotoXY(x, y : int); |
96 | | fun Write(s : string); |
97 | | end |
98 | | }}} |
99 | | |
100 | | When we invoke the GetVC() method, the console server will pass us a reference to the remote object implementing the requested VC. Then we can work with this particular VC using that reference: |
101 | | |
102 | | {{{ |
103 | | var Con : IConsole; |
104 | | var VC : IVC; |
105 | | |
106 | | C = NameService.GetConnection("console") as IConsole; |
107 | | VC = C.GetVC(2); |
108 | | VC.GotoXY(10, 10); |
109 | | VC.Write("Hello World!"); |
110 | | }}} |
111 | | |
112 | | Connection creation and termination, as well as transaction management (identifying the objects being worked with) is automatically handled by the language run-time. Also handled automatically is the creation of threads and fibrils within a server. A server can potentially handle any number of parallel requests (though it might be possible to limit this with some quota, if required). Concurrent access to remote objects is possible (and often desired). |
113 | | |
114 | | ==== Remote invocation ==== |
115 | | |
116 | | When a method of a remote object is invoked, the method ID and its parameters are serialized and the resulting message is sent to the server. On the server the method ID and arguments are de-serialized and the implementation of the method is invoked. When the method returns, the return value (and possibly output arguments) are serialized and sent back to the client. At the client the return value(s) are de-serialized and returned to the caller. |
117 | | |
118 | | Some notes: |
119 | | * Multiple threads/fibrils may use the same remote object in parallel without fear of blocking each other (as long as the server is properly implemented) |
120 | | * Stateful services can be implemented by the server handing out state objects (such as open-file object on a file server). |
121 | | |
122 | | ==== Promises ==== |
123 | | |
124 | | [http://en.wikipedia.org/wiki/Futures_and_promises Promises] can be used to express asynchronous behavior and potentially allow for [http://en.wikipedia.org/wiki/Promise_pipelining#Promise_pipelining promise pipelining] (a form of optimization). In our case it would suffice to have a specialized form of promise, one that promises some data to be delivered from a remote object. Promises would be declared using a prefix type operator `future`. |
125 | | |
126 | | As long as the data received from a remote object stays in a type that is `future`, it is handled in an asynchronous fashion. Once the data is converted to a non-future type, the execution blocks until the data is received. |
127 | | |
128 | | Example: |
129 | | |
130 | | {{{ |
131 | | interface IAsyncIO is |
132 | | fun AReadBlock(addr : int) : future Block; |
133 | | end |
134 | | }}} |
135 | | |
136 | | {{{ |
137 | | fun ReadBlocksParallel(start_addr, count : int) : Block[] is |
138 | | var fblock : (future Block)[]; |
139 | | |
140 | | for i in range(0, count) do |
141 | | -- This does not block |
142 | | fblock[i] = AReadBlock(start_addr + i); |
143 | | end |
144 | | |
145 | | -- All reads are now being executed in parallel. |
146 | | |
147 | | -- Each array element is implicitly converted from future Block to Block. |
148 | | -- This blocks until all data has been received. |
149 | | return fblock; |
150 | | end |
151 | | }}} |
152 | | |
153 | | === String language specification === |
154 | | |
155 | | It has been suggested by Pavel Rimsky that very often string literals in a program contain data in some machine readable language (e.g. format strings, SQL statements) or references to external resources. It might be useful to be able to somehow specify this in the program, so that external tools could recognize and work with these for purposes such as syntax checking, refactoring, etc. |
156 | | |
157 | | Note: That means ''identifying'' the language the string contains. Defining any ''properties'' (e.g. syntax) of the language the string contains is out of scope! |
158 | | |
159 | | One typical example here is a formatting function. The format string argument is in a well-defined language. Here it would be useful to specify language of the formal argument. With any use of such function the compilation tools could try to verify the real argument. Similarly we might to specify language for a member variable. |
160 | | |
161 | | A different approach is specifying language of a string literal in situ. This is reminiscent of (X)HTML which allows embedding pieces of code written in different languages (e.g. CSS, ECMAscript) while specifying the external language using its MIME type, or language constructs such as "extern C". |
162 | | |
163 | | Both approaches could be combined. |
164 | | |
165 | | TODO: Consider where language annotations would be useful and how they should be realized lexically and syntactically. (Must look pretty!) |
166 | | |
167 | | === Miscellaneous ideas === |
168 | | |
169 | | These ideas are considered for inclusion (but need not be included). They need elaborating. |
170 | | |
171 | | ==== Member pointers ==== |
172 | | |
173 | | Delegates identify the object instance and method to be called (but not the arguments). Conversely, member pointers identify the method to be called, but not the arguments and not the object instance (It can be invoked on any object which is instace of a given class). This feature comes from C++. |
174 | | |
175 | | ==== True inner classes ==== |
176 | | |
177 | | A true inner class is non-static in the sense that any instance of this class implicitly contains a reference to some instance of the outer class. Thus the inner class is constructed in non-static context (in context of an object instance) and the outer object can be referenced via a keyword. |
178 | | |
179 | | ==== Output function arguments ==== |
180 | | |
181 | | Semantically equivalent to additional return values of a function. A simple way to return multiple values (especially since Sysel does not have tuples). |
182 | | |
183 | | ==== Built-in associative arrays ==== |
184 | | |
185 | | Maps and sets are so commonly used and so immensely useful that it might be worth incorporating into the langauge core. This could bring greater ease of use and optimization opportunities. |