Wake Tutorial
Installing
first you need to install wake, or you can just read along and install it later.
A first program
Begin by cloning the git project seed with the command git clone http://github.com/MichaelRFairhurst/wake-project-seed.git
. It will create a project with the following directory structure:
~/projects └──YourFirstProject/ ├──bin/ │ ├──obj/ │ │ └──{compiled files here} │ └──tables/ │ └──{stlib tables and your tables here} ├──lib/ │ ├──obj/ │ │ └──{3rd party library objects here} │ └──tables/ │ └──{3rd party library tables here} ├──src/ └──test/
All you need to do is run make
from this directory to build and test your code. Test files go in the test directory and source files go in src. So lets start writing code!
import Printer;
every Main is:
needs Printer;
main() {
Printer.printLine("Hello World!");
}
This should all look quite familiar. We import the standard library's Printer
for use in our code. We then create a class named Main
, and that class will need a Printer
. This is both a type name and a variable name, and a private property that will exist on every instance of the Main
class.
The code every Main is:
may look strange, but its simply a class definition. In addition to requiring a Printer
, we give it one method, main()
which returns nothing, and simply tells our Printer
to print "Hello"
.
You can run make
to compile this file, and then node bin/myprogram
to run it.
But this is just the easy stuff. What if we wanted to actually inject something ouselves?
Custom injections
Lets create a new class which holds the dependency on the Printer
, and can greet more than just the world. Lets call this class, Hello
.
Hello.wk
import Printer;
every Hello is:
needs Printer, Text:HelloText;
hello()
Printer.printLine("Hello " + Text + "!");
}
Once again we import Printer, and once again we have a class definition - every Hello is:
. However, this time we need more than just a Printer
, we also need a Text:HelloText
. This is an injected Text identified by a custom name. We could have called it Text:WhatAGreatTutorial
instead if we'd wanted to.
The important part of our HelloText
is that its a Text
. This tells the compiler that we have a private property named Text
and of type Text
, and allows us to use it in our method hello()
.
We then use java-style string concatenation to greet whatever our HelloText
is.
Now we have some rewriting to do in Main
.
import Printer;
import Hello;
every Main is:
provides Hello,
Printer,
Text:HelloText <- "dependency injection!";
main() {
var Hello from this;
Hello.hello();
}
If we had said needs Hello
instead of provides Hello
, we wouldn't have been able to inject it with our custom Text:HelloText
. Instead we must be able to provide it.
The other stuff we provide here is stuff needed by the class Hello. If we forgot to include it, the compiler would tell us to. We're going to provide a plain old Printer
, but we specify exactly what we want for Text:HelloText
.
In main()
we say var Hello from this;
, which creates a Hello using our Main object, and assigns it to a variable named Hello
. Then we can say Hello.hello()
to call methods on it.
Using Provision Arguments
Rather than greet just one thing, lets greet several. Obviously, we're going to run into some maintainability issues if we can't create these Hello's on the fly, so lets handle both looping and provion arguments at the same time.
Main.wkimport Printer;
import Hello;
every Main is:
provides Hello <- Hello(Printer, ?Text),
Printer;
main() {
var Text[] = ["World", "Wakers", "Seahawks", "Aliens"];
foreach(Text[]) {
var Hello(Text) from this;
Hello.hello();
}
}
We changed our code in povides Hello
to a specific class with a specific injection. We first give it a plain old Printer
from ourselves, and then we give it ?Text
, which means our provision for Hello
has a single Text
argument. We still have to be able to provide Printer
.
Next we create a list of Text
s containing some groups of people we'd like to say hello to, which will simply be named Text[]
.
The foreach
is special. It knows that we have Text[]
inside it, so it can automatically create a variable named Text
for us inside. Its like magic!
Finally, we actually call our provision with our argument by saying Hello(Text) from this
, once again using the var
syntactic sugar to save it as a variable named Hello
.
What Next?
The tutorial will leave you here. There is a plethora of docummented features, as well as a small standard library. You can take this knowledge to write libraries and share them, maybe even with us for integration with the standard libaries. Any questions, you can ask me via email at michaerfairhurst@gmail.com. There are also some lingering issues that have been documented here for if you run into anything strange.
Known Issues
These issues have the potential to crash/corrupt a running program. Some are pretty standard use cases - sorry! And others are pretty obscure/obviously wrong at compile-time.
Nested Optionals
Another thing I had not considered when implementing optionals is that "null" is an idequate runtime representation of a true optional type system. What can optional types do that nulls can't? Nest, that's what!
every Main is:
nestedOptionalsInteractingWithExists() {
var Num? = nothing;
var Num?? nested = Num;
if Num exists { } // this check will fail as it should
if nested exists { } // this check should succeed, and won't!
}
Basically, if you consider a language without optional types, if you had a function that searched through a list and returned either the last item or null, this API is inadequate for a list where the last element is null. The fix isn't too difficult, but optional types need to be stored in recursive wrappers, and unboxed when used within an "exists" check.
Generic Properties
When a generic class has a generic type served as a public property, it isn't paramterized the way methods are. The name doesn't change and the type doesn't change either.
wakeevery Generic{T} is:
with public T?;
typeErrors(Generic{Num}) {
var Num? = Generic.Num; // Doesn't compile: unknown property Num
var Num? = Generic.T; // Doesn't compile: expected: Num?, actual: T?
var T? wat = Generic.T; // Successfully compiles: WAT
}
This is easy to fix, just low priority. Ping me for a, most likely, same-day fix.
Overlapping Generics
I knew about this issue I'm just a bit scared to really take it on in full. If you have two different types in a generic, you can declare & distinguish two methods by overloading. However, when you later use it with the two types matching, one method or another will quietly overwrite the other.
wakeevery Generic{A, B} is:
needs Printer;
overlappingMethod(A) { Printer.printLine("MethodA"); }
overlappingMethod(B) { Printer.printLine("MethodB"); }
oneMethodIsLost(Generic{Num, Num}) {
Generic.overlappingMethod(5); // prints B...I think...
}
Missing Features + Workarounds
These missing functionalities should not crash or corrupt any programs, but they may get in the way of your designs. Sorry!
Parent Method Calls
This is a semantic analysis and parsing problem. I simply need to look for parent.aMethod() calls and treat them specially. Haven't done it yet. Also, I'm partially waiting on this one because I am pondering two-way-inheritance. There's an easy workaround, though!
wakeevery ParentClass is:
myMethod() {
// this is inaccessible from Childclass.myMethod() !!
// call parent_myMethod from here
}
parent_myMethod() { } // since this method IS accessible from both classes
every ChildClass is:
myMethod() {
parent.myMethod(); // Won't compile :(
parent_myMethod(); // so create this method on ParentClass and have fun!
}
Common Object Class
Its useful to have everything (and I mean everything) extend a single class, usually Object. A good example is database queries. It won't be hard to do, but its not done yet.
wakeevery FetchUsersQuery is:
needs PDO;
getAllUsers(Object[] values) {
return PDO.prepare("SELECT * FROM user WHERE username = ? AND email = ?", values).find();
}
Exists on some L-Values
For properties on external objects, and potentially other places, you can't use exists because it's very complex to add the value to a scope sym table. You must store it in an intermediate variable, sorry.
wakeevery Link is:
with public Link? next;
cannotCheckExistanceDownChain() {
if next exists { // OK!
if next.next exists { }// No bueno!!
Link? farther = next.next; // OK!
if farther exists {} // OK!
}
}
Namespaces
Yeah, just only have so much time. This one will be easy though.
"Nothing" as an Argument
This may be solved more generally eventually. Actually, its closer than ever now that generics have "casing" signatures that get filled in later. But basically, the compiler only finds methods by their signatures rather than their names. Since "nothing" is its own type, it never matches a method signaturee. Luckily you can cast it and achieve the same end.
wakeevery MethodCallWithNothng is:
getAllUsersWith(Text? firstame, Text? lastname) { ... }
callGetAllUsersWithNothing() {
getAllUsersWith("ted", nothing); // Won't compile! The compiler can't find the method
getAllUsersWith("ted", (Text?) nothing); // Now the compiler can find the method :)
}
Any big O and little O geeks out there should lend me hand on this one to create a fast binary tree or something. The only way that comes to mind for truly solving this problem would be O of n to the n, I think.
Generic Provisions
Not sure exactly how to put this one. Its a pretty big hole in wake. I'll list the things you can't do, because otherwise its tough to describe.
wakeevery GenericProvider{T} is:
provides T, // won't compile. How can we provide T when T could require any sort of subsequent types?!
GenericProvider, // This should compile, since all types of GenericProvider have one type of need
GenericProvider{Num} <- SomeSubclassOfGenericProvider; // It should be possible to do this too, not sure how best to make this happen.
useAProvider() {
T from SomeProvider; // Won't compile, for the same reason as java won't let you do "new T()". Likely will never fix this...its broken for very good reasons.
}
Generic Inheritance
This just needs to happen, and its actually not so intimidating a feature. Also may hold up stdlib creation where generic inheritance is crazy.
wakeevery Set{T} (a Collection{T}) is:
Generic Methods
This is painfully obvious in Asserts. However its quite a difficult item to do...it requires some sweet type inference.
wakeevery Asserts is:
{T extends Printable} that(T)Equals($T) {
if(T != $T) ...
}
Also syntax is ugly. What can ya do.
Generic Ranges
At the moment all generics operate on any and all types. That is to say, a Generic{T} can be created with absolutely any type. It should be able to set a lower and upper limit. Don't worry, I anticipated this. Shouldn't be too bad once I get around to it.
wakeevery Generic{P from Printer, L to Printer} is:
myMethod(P, L) {
P.printLine("not possible until 'P from Printer' is allowed.");
L = P; // Possible since the upper limit of L is a subclass of the lower limit of P :)
}
Switch/Case
Actually, even though it currently parses switch/case like java, I want to revamp it. I have some good ideas but I'm a bit afraid of them, so they're on hold.
Abstract Provisions
I wasn't entirely sure how typing for providers was going to work. There's still room for a great idea to make providers feel more dynamic. However, in the meantime, this uncertainty clouded my eyes so I didn't think of abstract provisions.
Essentially, since all classes are interfaces, and pure interface classes simply make all their methods abstract, there needs to be a way to say a class "provides SomeClass" without actually providing anything.
wakeevery MyInterface is:
provides AClass; // Won't compile: need to provide AClass's dependencies.
// Or, inheritingfrom MyInterface gives you behavior you didn't realize
myAbstractMethod();
anotherAbstractMethod();
Reflection
Actually shouldn't be too bad to implement. But its not done yet, and its a big undertaking.
Enums
Somebody who loves the feature-rich enums of java, and who understands Enum<E extends Enum<E>> should tell me all of the things I need do and not do to make powerful enums.
Recursive Imports
If two classes use each other, just stick them in one file and you're good. If you split them up into two different files you're screwed!