These things occur so naturally in Scheme that I couldn't help but understand. After mastering the concept, I could then go back into my Pascal class and master the code. The point here is that concepts like these are universal in computer science. After you understand them, then you can learn the rules of any language in order to encode them. But it doesn't matter how well you have mastered the syntax of a language if you don't understand the meaning of what you are typing or the reason why it works.
In the last section we added the SISC Scheme interpreter libraries to our NetBeans Platform based Scheme IDE.
In this section we'll see how to invoke the SISC interpreter. We will define a generic interpreter interface (publishing our first API), and in the next section we'll see how to actually implement it. By doing so we'll learn about NetBeans Platform's Lookup system (the best thing after sliced bread), NetBeans threading (Oh, my! I should have learned about this before!) and the Output Window (the Console).
And, of course, we'll be having fun all the way through. Let's go!
Let's get started.
I think I'll add another module into the Scheme Module Suite. I like making modules responsable for very specific things. We have now a module for recognizing Scheme file types, another module that adds editing capabilities and another module that wraps the SISC library. This new module will be responsible for evaluating Scheme expressions using the SISC Scheme Intepreter (using the SISC Library Wrapper).
But, hold down just a minute. What about if we want to change the current interpreter and use, say, JScheme or guile instead of SISC? Shall we go and change everything? That wouldn't be too modular, would it?
If we were using Java then this could be done easily: we would define a generic interpreter interface and, aftewards, define different implementations, allowing people to change implementations easily without the rest of classes noticing, using a Abstract Factory Pattern.
But can we do this with the NetBeans Platform? And if so, is this difficult to do?
The answer is that it is extremely easy to do, and that the solution relies on the Service Provider mechanism defined in the Jar File Specification (yes, I told you that the NetBeans Community loves following standards) and in the NetBeans Platform Lookup technology.
So instead of creating a single module responsible for invoking the SISC Scheme Interpreter we will be creating two modules: a generic module that defines what a Scheme interpreter looks like, defining a Public API (something similar to a java interface) and another that contains the actual SISC interpreter, the implementation of the interface.
To clarify the current structure of our Scheme Module Suite I've created a sort of UML package diagram (but with modules, not packages) in Figure 47, “An overview of the current modules”.
But how many modules is this guy creating? | |
---|---|
You may be thinking that I add too many modules, and developing with the NetBeans Platform makes you build tons and tons of modules. Well, the fact is that this is a Good Thing. Having modules have single responsibilities follows the DeMarco's Single Responsibility Principle. |
So let's create this Scheme Interpreter Module, responsible for defining a public API that we'll use to evaluate Scheme expressions.
Creating a module is so easy that I won't repeat all the steps here (but see Section 6.1, “Creating another module for the editor...” if you need help on creating modules).
I will call this the "Scheme Interpreter" module and I'll specify a code base name of "net.antonioshome.scheme.interpreter": "net.antonioshome" (my domain) + ".scheme" (the project) + ".interpreter" (the module). Yes, I'm following the conventions that I used in previous sections.
Once the module is created let's see what this generic Scheme API should look like, and let's then make it public.
So let's define an API for evaluating Scheme expressions. [10] This API should be as generic as possible (so that we can plug-in different Scheme interpreters afterwards) and, of course, should have no dependencies with the actual implementation.
Let's review some requirements first, so as to define a good API.
The very first requirement for our API is that it is asynchronous, so that we can schedule Scheme expressions for evaluation in a worker thread. This will serve us for learning how to do threading with the NetBeans Platform (which is both easy and powerful, as we'll see later on).
So the Scheme expressions will be evaluated in worker thread. Cute. But we need to know what the result of the evaluation is, so we'll need to develop some sort of callback mechanism, that informs us what the result of the evaluation is (and what the errors are, if any).
We'll also need to keep it simple. I'd like to evaluate simple expressions (stored as simple Strings) and to evaluate files containing Scheme source code. So we'll need to methods: one for evaluating Strings and other for evaluating files.
But what if our Scheme source code is stored somewhere else but on a file? What about if our Scheme source code is stored in a web server miles away?
Mmmm. Maybe it's a better idea to add a method for evaluating Readers (after all a new StringReader( aString ) builds a Reader from a String) and another for evaluating Files (setting the current working directory to the directory where the file is stored). That way we'll be able to evaluate both stuff we read from a network connection and stuff stored in our local filesystem.
I'd also like to evaluate an expression and, later on, evaluate another in the same environment. So if I evaluate a "(define a 10)" expression and later on I evaluate a "(display a)" expression (in another thread, possibly), the second one refers to the value of "a" defined in the prior expression.
But to make things more complicated, I won't introduce environments now, but later on.
Yes, this is not a typo! Not introducing them will make things more complicated.
Why, you ask?
Well, think of it before reading any further.
Ready?
By introducing environments later on I will be changing a Public API. And that's asking for trouble, because I will be forcing the rest of modules to adapt to my changes. I will be forced to change all modules implementing the "Scheme Interpreter" interface!
And that's the reason why making things simpler now is making things harder later: if you define a Public API think twice all the use cases. After all you don't want to propagate changes in lots of modules, right?
Think twice before defining a Public API! | |
---|---|
If you change a Public API you'll have to propagate that change all over the place, to other modules using it. That's why the NetBeans Community has specific processes for API Reviews, and builds use cases for those Public APIs. Making sure a Public API is solid and that it covers all possible use cases is saving time (and money) in the future. |
So to fullfil all those requirements I propose the following API for invoking any Scheme interpreter I can think of:
public interface SchemeInterpreter { public String getName(); public void eval( SchemeCallback aCallback, java.io.Reader someExpressions ); public void eval( SchemeCallback aCallback, java.io.File aFile ); public interface SchemeCallback { public void onSuccess( String aResultingExpression ); public void onFailure( String anErrorMessage ); } }
And this will be our Public API for Scheme Interpreters. I've added a "getName()" method to return the name of the interpreter. All the rest should be obvious: two methods for evaluating expressions in Strings and Readers; and a callback interface with two methods: one to be invoked when the expression is evaluated correctly, and another one invoked when the expression generates an error.
And, of course, we should add some more Javadoc (since this is a Public API). Public APIs are Public, and must have good Javadoc (not included for clarity).
Now that our generic Scheme Interpreter interface is ready let's see how to make it public.
So we add the SchemeInterpreter interface to our module. To make the API Public we select the module (in the Projects folder), right click on it and choose Properties in the popup menu. The Properties dialog pops up.
Finally we select "API versioning" option on the left of the dialog. A list of all packages in our module is presented to us in the "Public Packages" list. We select ours (net.antonioshome.scheme.interpreter) and click on it to make it public.
I like setting a major release version (and an implementation version) whenever I define a Public API. This way I may be able to make changes in the Public API (which is painful) in case of need.
Once our Scheme Interpreter Public API is ready we are ready to start building an implementation of it (using SISC).
Of course we'll have to integrate the implementation with the SISC Library Wrapper, and we'll have also to see how to do threading (and show progress) using the NetBeans Platform. Keep tuned for that.
[10] The NetBeans Community has special procedures for defining new APIs (that include some formal review processes, for instance), but I won't be following them here. After all this whole thing is just an experiment and we won't cause any harm to the rest of the Community ;-)
blog comments powered by Disqus