Scala is a very good and powerful programming
language. But something is missing, something... something like this:
In this note I will focus on the prototype
library, which will increase the potential of component programming in Scala.
Here will be a lot of practice and a few theory, since I think it will be more
clearer, and write in English it’s really hard for me :) But if you are feel an acute shortage of
theories and reading in Russian, then look to this note.
Thanks to the Scala flexibility, turned out
to make the syntax which not very creepy, but I had to use a little of black magic, so if
you want to try applet-examples, you need to allow them work.
Another component
programming
Component
programming is a paradigm that
says "let's assemble the programs from the prepared blocs -
components". This paradigm was invented a long time ago (in 60-ies) and,
for this time been a lot of implementation, such as COM or JavaBeans. But
almost all existing implementations says
"components is large and thick, complex and static entity, use them
only to pack huge pieces of programs", and I think It’s really wrong way.
I would like to have a set of small and
dynamic components for simply design the programs of them, and see the results
in real time. Something like OOP objects, but much less the monolithic and with
less chaotic structure, friendly to visualization and to realtime management.
Something like a Lego-programming.
Intuition
The proposed paradigm is a mix of component-oriented
programming, connection-oriented
programming, reactive
and agent-oriented
programming. This approach suggests one-off writing a code, i.e. if the
component for any reason ceased to satisfy the current requirements, rewrite
him it is a good way.
Well, let's do something!
Assembly & components
To get started, we need to: First - create a
new Scala-project, read the license
agreement, download
and add the jar-library. Secondly - create some Scala-object with main method,
which will be used to run an assembly, and import there seven classes and
traits:
package e_hello import skidbladnir.{Assembly,Base,Compo,Handle,Interface,Mono,Multi} object Test {def main(args: Array[String]): Unit = {/*TODO*/}}
Next, we need to define and create the
assembly. The assembly is an object where you will create and launch the first
components, and where stores the information about the current status and
structure of the connections of components:
... object Test {def main(args: Array[String]): Unit = {val a = new SimpleAssembly}} //Class Assembly contains methods for creating and connecting components, and control him behavior class SimpleAssembly extends Assembly { //Open the visualization window visualization //TODO //Says to a thread - run assembly and wait for the completion of its work, additionally there is methods go and console gowait //Free resources and complete the program (System.exit(0)) end }
Next, we define a
very simple component:
//Class Base contains methods and fields for the support the base-component, additionally there is a class Compo class Hello extends Base { //Any component may have a main-function, which will be executed in separate thread //after execution gowait, go or console, additionally there is a loop-function waitloop-function main(()=>{ println("Hi!") //Thread.sleep() sleep(10000) //The destruction of the component. (!) The program ends when all of the components will be destroyed selfdestruction }) }
Ok, let's create an instance
of the component and see what happens:
... visualization //Create a component named hello new Hello named "hello" gowait ...
Run applet: (!)To see the output enable
Java-console.
Interfaces & connections
The interface is a
special entity consisting of two halves-objects (Am and Pm), each of which consists of two
parts: exports - exported fields and methods, imports - imported fields and methods.
Here's a template to define the type of interface:
object <Type_name> extends Interface { class Am extends Mono {val imports:Pm = null <exports>} class Pm extends Mono {val imports:Am = null <exports>} }
The components can be
connected if implement the interface half. When components connect, the exports
of one component is available to another through the imports field.
Graphically, it looks like this:
Let's make a simple
definition of the type of interface for component Timer:
object ITimer extends Interface { class Am extends Mono {val imports:Pm = null} class Pm extends Mono {val imports:Am = null //Timer have one method to import. def tick(tn:Int) = {}} }
The interface implementation has the following
format:
protected val <interface_name> = <connector(plug or jack)>(new <Interface_type>.<Am/Pm>{ <exports_implementation>}
Let's define a couple of components that
implement the interface ITimer:
class Timer extends Base { //Var private var tn = 9 //Interface implementation, since the first half (Am) export nothing, there is no implementation of export protected val iTime = plug(new ITimer.Am) //Loop is like main, but is invoke in cycle so long as returns true loop(()=>{ sleep(1000) //The invoke of the imported method iTime.imports.tick(tn) tn -= 1 true }) } class Main extends Base { //Interface with implementation the exported method tick protected val iTime = jack(new ITimer.Pm{ override def tick(tn:Int) = { if(tn == 0){println("BOOM"); selfdestruction}else{println("tick No " + tn)}}}) }
Create and connect
the two components:
... visualization new Main named "main" new Timer named "timer" //Connect function. "iTime" from "main" connect "timer" //This is a short version for the interface half with the same name, the full version is: //"<interface>" from "<component>" connect "<interface>" from "<component>" gowait ...
Run applet: (!)To see the output enable
Java-console.
Multiinterfaces
Often required to give access to one
component from several other component, are used for this multiinterfaces,
which type definition has next format:
object <Type_name> extends Interface { class Am extends <Multi/Mono> {<val imports = Map[Handle, Pm]()/val imports:Pm = null> <exports>} class Pm extends <Multi/Mono> {<val imports = Map[Handle, Am]()/val imports:Am = null> <exports>} }
Let's define a first
useful component. It will be a component Timepiece, which every half-second be
report the current time. Current time may be need to several components, so we
will use multiinterface:
object ITime extends Interface { //In this case imports is a list of the connected components class Am extends Multi {val imports = Map[Handle, Pm]() //Timepiece will export field time that stores the current time, //which be accessible to all of the connected components var time:Long = 0} class Pm extends Mono {val imports:Am = null //Timepiece will import the method tick from all connected components def tick() = {}} }
Properly define a Timepiece:
class Timepiece extends Base { //Interfaces //For multiinterfaces need to use multijack or multiplug protected val iTime = multijack(new ITime.Am) //Что такое multiplug //Loop loop(()=>{ //Usage the exported field time iTime.time = System.currentTimeMillis() //Invoke a method tick from all connected components iTime.imports.foreach(e => {e._2.tick()}) sleep(500) true }) }
And a couple of
components that will be use Timepiece:
class Main extends Base { //Interfaces protected val iTime = plug(new ITime.Pm{ override def tick() = { println("system: " + imports.time)}}) //Main main(()=>{ sleep(15000) selfdestruction }) } class Show extends Base { //Interfaces protected val iTime = plug(new ITime.Pm{ override def tick() = { println("time: " + new SimpleDateFormat("yyyy.MM.dd 'at' HH:mm:ss z").format(imports.time))}}) }
Now gather it all
together and see what happens:
... visualization new Main named "main" new Show named "show" new Timepiece named "timepiece" "iTime" from "main" connect "timepiece" "iTime" from "show" connect "timepiece" gowait ...
Examples
Ok, let's do a little the real applications!
Watch
Earlier, we used only the Bases, now let's try trait Compo. In Compo (unlike Base) one of the
interfaces is main and it’s called root-interface,
this interface is always connected to some else component, and him
disconnection is equivalent to commit selfdestruction.
Why it is needed I will explain later. Yet let's do little bit Compo-nents that
will be useful to us in the future.
The first component is a GUI Frame, for
placing visual objects:
class BorderFrame extends JFrame with Compo { //Self-assembly ... panel setLayout new BorderLayout //Root-interface protected val iFrame = root(new IFrame.Am{...}) //Interfaces protected val iNorth:IWidget.Pm = multijack(new IWidget.Pm{...}, //Each interface implementation (except root) can have a functions, //which be called when this interface will connect and disconnect //Parameter - component handle connection = (h)=>{...}, //Parameters - component handle, cause of the disconnection disconnection = (h,i)=>{...}) ... }
Test component:
class Hello extends JLabel with Compo { //Self-assembly setText("Hello world!") setPreferredSize(new Dimension(200,20)) //Interfaces protected val iWidget = root(new IWidget.Am{override val widget:Compo = compo}) //compo - field how contains the pointer to the current component, for use in nested classes }
Assembly and run:
... visualization //Base which will be attached Compo new Main named "main" //When creating Compo, must be specified the interface which to be connect root new BorderFrame connected "iFrame" from "main" named "frame" new Hello connected "iCenter" from "frame" named "hello" gowait() ...
Run applet:
Next to this project
will do clock face:
Run applet:
Now make three small component: Button, Zoomer and Alarm.
Together it is an alarm clock that will be ring after
15 seconds the work:
Run applet:
Now we have to make the time picker, but
first let's talk about the variety of components and about why we need
root-interfaces. There are three types of components:
*. Bases
- have no root, and always static.
*.
Static Compo - components that creates in the assembly and having a name.
*.
Dynamic Compo - components creates by other components and do not have a name.
Time picker will be is the same.
Figuratively you can imagine that the static
component is the skeleton of the application, while the dynamic is its meat.
The components are organized into trees where the root is the Base next
followed static Compo and in the end is dynamic Compo. Visually it looks like
this:
Base and
static Compo creates are only at the start of the assembly, and existents all
the time work of the program, therefore the destruction of at least one of them
destroys all the rest and assembly work will be completed. Dynamic Compo - can
be created and destroyed any time and their destruction leads only to destroy
the containing branch. Static Compo differs only the fact that been created in
the assembly.
Ok, let's define our time picker component:
class TimePicker extends JDialog(new JFrame()) with Compo{ //Self-assembly ... //Interfaces protected val iTimePicker = root(new ITimePicker.Am) //Each component may have constructor and/or deconstructor. //In Compo constructor and deconstructor are the functions connection/disconnection for the root interfase. constructor((h)=>{...}) deconstructor((h,i)=>{...}) ... }
And extend the previously defined clock face
with new interface iMouseEvent:
class AClock extends Clock { //Interfaces protected val iMouseEvent = multijack(new IMouseEvent.Am) ... }
Finally define
component that connects everything together, and contains code that creates dynamic Compo TimePicker:
class Knot extends Compo { //Interfaces protected val iClockEvent:IMouseEvent.Pm = root(new IMouseEvent.Pm{ override def event(e:MouseEvent) = { if(e.getID() == MouseEvent.MOUSE_PRESSED && iTimePicker.imports == null){ ... //Compo TimePicker creats as a connected to the interface iTimePicker from Knot new TimePicker joined "iTimePicker"}}}) protected val iTimePicker = jack(new ITimePicker.Pm{...}) ... }
Run applet:
Edit and Notes
More of applications with reusable
components:
Simple
editor:
Run applet:
The
program for notes with reminders:
Run applet:
If you
notice in the examples I’m not write components are fully, I do wrap already
existing classes, it is a good way to fast compo-creating.
Work with console
The console gives you
the extension of possibility for control assembly in real-time. To display the
console, use the keyword console instead go or gowait, for example:
... visualization new Main named "main" ... console end ...
Run applet
to play with console(like in demo video):
And so on
You can continue indefinitely, adding and /
or replacing components more and more evolve the application and the components
library, without a fear of the rise of complexity and confusions. Thanks to
visualize you can quickly determine the appearance of problems, and due to the
low confusion of structure, you can easily solve the problem by replacing
components.
Gratitude
Thanks to the people
whose work I used:
Jakob Fischer - font;
JUNG-team - graph framework;
BeanShell-team - JConsole;
and
the rest.
Download
skidbladnir.jar(~1.4Mb) - library;
PS
This prototype is
only a pale likeness of that it is possible to do with this paradigm, and it is
very crude. Before make it really useful much remains to be work out (in
particular logic and multi-threading model, as well as integration with the
Scala). But I hope you get the basic idea.
No comments:
Post a Comment