Try Wake Now


A fast, expressive, typesafe language that
gives you testability from the ground up.

A Modern Programming Language

Wake is a brand new programming language aimed at solving modern problems, such as code testability, expressive typesafety, and language interoperability.

Throughout the docs, we'll show you how we've made the language testable, flexible, readable, concise, and safe.


Quick Code Introduction

We'll compare Wake to both java and javascript. You'll see friendly classes, clean code, and intuitive type safety.

or you can jump straight in:

Wake

Code as concise as javascript, with inheritance and typesafety and more
Wake (481 bytes)
every RecordValidator is:

    needs SessionHolder,

    Bool -- validate(Record[]) {
        return Record[].any({ r => !validate(r) });
    }

    Bool -- validate(Record) {
        var Account[]?
                = Record.lastRevision.?User.?getAccounts();

        if Account[] exists {
            return Account[].any({
                a => SessionHolder.hasAccount(a)
            });
        }

        return Record.Revisions[].filter({
            r => usesLimits(r)
        }).length < 3;
    }

    Bool -- usesLimits(Revision) {
        return SessionHolder.hasUser(Revision.User);
    }

Javascript / Java

Elegant and concise, but unintiuitive, unsafe, with pitfalls all around
Javascript (739 bytes)
var RecordValidator = function(sessionHolder) {

    this.validateRecords = function(records) {
        for(var i = 0; i < records.length; i++) {
            if(!validate(records[i])) {
                return false;
            }
        }
        return true;
    }

    this.validateRecord = function(record) {
        if(record.lastRevision !== undefined
            && record.lastRevision.user != undefined
        ) {
            var accounts
                = record.lastRevision.user.getAccounts();
            for(var i = 0; i < accounts.length; i++) {
                if(!sessionHolder.hasAccount(accounts[i])) {
                    return false;
                }
            }
            return true;
        }

        var that = this;
        return record.revisions.map(function(r) {
            return that.usesLimits(r);
        });
    }

    this.usesLimits = function(revision) {
        return sessionHolder.hasUser(revision.user);
    }

}

New ideas in Wake

We've both created and borrowed some cutting edge ideas.

Provisions

testable flexible

Use provisions to keep a testable and extensible seam between every class.

This makes testability and flexibility a language feature.

Type Inference

concise

Keep the expressive, clean look of dynamic languages, with a safe type system.

This saves you time writing, reading, and refactoring code.

Constructor Properties

testable concise

Reduce boiler-plate code by making constructor arguments exactly like properties.

This makes best practices like Aspect-Oriented-Programming intuitive.

Two-way polymorphism

testable flexible

Eventually will add this.

This creates testable inheritance by increasing expressive object design.

Compilation targets as a language feature

flexible

A goal of Wake is to compile into anything -- .net, java, javascript, and assembly

This makes Wake libraries usable in every one of your projects.

Closures

flexible concise

Wake is part of a small class of strongly-typed, object oriented languages with functional coloring.

This adds concise, elegant, rapid-development to your scalable enterprise apps.

What Wake did without

Any poor feature in a language can prevent the addition of a powerful one. We made some hard choices to create a strong cocktail of ideas.

What we cut Why we cut it What it enabled
Static methods
Using a static method from a procedure hard-codes implementation rather than interface, decreasing flexibility and testability
FIXED You can (and usually should) now use exact type names as variable names, meaning more clarity and fewer keystrokes.
The new keyword
new mixes application APIs with implementations, and has been replaced with 'provisions.'
FIXED Constructors directly translate to properties since they are not functions.
Interface/extension distinction
A common best practice is to create an interface for every class, even before its used, resulting in junk code and extra boilerplate.
FIXED In Wake you can use any class as an interface, making everything driven by APIs without any additional code.
Abstract classes
Abstract classes are a combination of an interface and a class, or a means of locking a class to static methods.
FIXED We let the compiler tell you if a class is incomplete, not vice versa, without any management on your part.
Primitive types
Primitive types are fast, but without methods they require external behavior to manipulate them.
FIXED The compiler handles autoboxing primitives into objects - Num, Bool, Text, etc - and lets you code without managing the conversion.
Cryptic keywords
Abstract, virtual, volatile, and other cryptic keywords from the 1970s convey very little about what they actually do.
FIXED New programmers will find every keyword to be simple, and meaningful, from Text to capable to needs.

Try Wake Online

We handled installing it for you, and with javascript as a compile target, you can run it all right in your browser

Try Wake Online


if, while, and for statements concise readable

New to 0.2.1! If you are using the 0.2.0 compiler, parenthesis are required.

Wake's syntax does not require parenthesis around the conditions of if statements or while statements. For loops still require parens but are still considered to be in flux. In loops, continue and break are both supported.

Note that there is currently no switch/case support, and that there are two other important constructs in Wake you'll need to know which aren't covered here: exists statements and foreach loops.


Wake
if i < 3  { ... }
else { ... }
if i < 5  then return true;
else return false;

while i < 3  { ... }
while i < 6  do ...;

Making The Most Of Wake Variables

Variables in Wake come in three forms, all with the goal of meaningful names in minimal keystrokes.

1. Typenames as variables concise readable

Since we got rid of static methods, we can save you from repeating yourself meaninglessly in domain-driven code.

Wake
every Person is:

    needs Int;

    Int -- getId() {
        return Int;
    }

    Bool -- isSameAs(Person) {
        return getId() == Person.getId();
    }
Java
class Person {

    private int id;

    public Person(int myid) {
        id = myid;
    }

    public int getId() {
        return id;
    }

    public bool isSameAs(Person person) {
        return getId() == person.getId();
    }
}

2. Aliasing readable safe

In our previous example, calling a person's ID Int is a bit ambiguous. When a type isn't meaningful for a context, use aliases.

To aid the compiler, we must write aliases in lowercase, to hint that its not a type. But luckily, it turns out that the same idea is useful to humans as well. It also allows us to write aliases before or after our type to make our code more literate.

Wake
every Person is:

    needs Int id;

    Int -- getId() {
        return id
    }

    setIdThenSave(newid Int, Bool recursively) {
        ...
    }
Java
class Person {

    private int id;

    public Person(int myid) {
        id = myid;
    }

    public int getId() {
        return id;
    }

    public void setIdThenSave(int id, bool recursively) {
        //...
    }
}

3. Shadowing concise safe

Following from the idea that the most meaningful name for a variable often is its type, we have a problem with places like 'setter' methods. The new value is of the same type as the old value, but they must be distinguished.

Instead of creating meaningless aliases as we arguably did with newid, we can add $ to preserve type information while also creating a unique instance

Wake
every IntContainer is:

    needs Int;

    Int -- setToNotGreaterThan($Int) {
        if Int > $Int then Int = $Int;
    }

Declaring variables concise safe

All these variable names are usable in declarations when preceded with :, or when in properties

Wake
every DeclarationExample is:

    with Int here = 4;
    with some Int = 4;
    with $Int = 4;
    with public Int too = 5;

    needs another Int, $$Int, Int again;

    declareThem() {
        var local Int = 4;
        var inferredVar = 4;
        var second Int = 4;
        var $$$Int = 5;
        var Int = 3;
    }

Available Primitives readable

Supporting JavaScript creates many tricky situations around overflow and performance. For the time being, overflow will be treated as undefined behavior as it is in C++

The primitives Num, Int, Text, Char, and Bool are all available, and all have literals for use.

See the standard library for methods you can call on these primitives.

Wake
every PrimitivesExample is:

    myMethod() {
        var Int = 123;
        var Num = 123.0;
        var Text = "Hello";;
        var Text singlequoted = 'Hello';;
        var Char = " ";
        var Char singlequoted = ' ';;
        var Text ofLengthOne = ' 's;;
        var Char newline = \n;;
        var Char singlequote = \';;
        var Char doublequote = \";;
    }

Optional Members and Methods typesafe

To easily look into a data structure with optional types, you can use the .? operator to get members and call methods when the subject exists.

Remember, optionals can be nested as in Int??. You can use .? on a nested optional, and it will unwrap as far as it needs. Remember than nested optionals carry more meaning than a plain optional, and be wary of discounting the reason it was nested before you accessed it with .?.
Wake
every OptionalTypeUsage is:

    Text? -- getNameOf(Person?) {
        return Person.?name;
        return Person.?getName();
    }

Using Lists typesafe

For ease of use, the type returned by T[x] is a T, and not a T?. For this reason, getting an unassigned list index will throw an exception so that the type system is happy. Safe accesses return T? and do not throw, and are covered after this.

Lists (called Arrays in many other languages) are a way of storing sets of a particular type. They are incredibly useful.

Wake
every ListUsage is:

    methodWithListArgument(Int[]) {
        Int[0] = 5;
        var $Int[] = [];
        var $$Int[] = [1, 2, 3];
        var combined Int[][] = [Int[], $Int[], $$Int[]];
    }
You will get an error if you try to declare Int[] in the same scope as Int[][], and for good reason.

Unless lists of lists in Wake to look like Int[][0] or Int[0][], we must face an ambiguity problem when Int[] and Int[][] share a scope. Since Int[0] is the correct syntax for both of these variables, the compiler forces you to rename one or the other. This is not the case for Int[] and Int.


Iterating over Lists concise readable

This feature is new to v0.2.1. If you are using a prior version, parenthesis around your iteration value are required, and the do keyword should not be used.

Since Wake blurs the line between variables and types, we can boast the smallest foreach loop of any language.

Wake
every PersonDatabase is:

    delete(Person[]) {
        foreach Person[] do Person.delete();
		// or
        foreach Person[] {
            Person.delete();
        }
    }
This works as you would expect for shadowed variables, where $Person[] becomes $Person. And it also works for expressions, where foreach db.getPeople() lets you iterate over Person, since the compiler knows your db returns Person[].

Wake also supports giving custom names to the items you iterate over, and will soon support getting the index at the same time.

Wake
every PersonDatabase is:

    delete(Person[]) {
        foreach $Person in Person[] do $Person.delete();
        foreach person in Person[] do person.delete();
        foreach Person[] at Int do Person.greet(Person[Int]);
        foreach person in Person[] at Int do person.greet(Person[Int]);
    }

Typesafe Lists readable typesafe

Of course, all lists in Wake are already typesafe. However, when writing in Wake you can choose to get an item out of an array as an optional, avoiding the often-convenient-but-sometimes-nasty UndefinedIndexException. The flipside of this is that you then must check that the item exists before you can use it.

This pairs especially well with the .? operator, allowing you to say, pass a notification easily with Listener[?0].?fire(Message); safely operating only when the Listener exists.

every ListUsage is:

    method(Int[]) {
        var Int? = Int[?0];
        if Int exists {
            ...
        }
    }

Operators in Wake concise typesafe

Operator precedence has been copied from Java. The new ones here are links to their usage descriptions.

Supported operators (in no particular order): +, -, =, |, ||, &&, /, *, .?, [ ], ^, [? ], ., !, <<, >>, >, <, :=, +=, -=, /=, *=, <=, >=, !=, ==, ~, %, %%, %%%.

Why does Wake support both = and :=? Both do the same thing, but = does not return a value. That means you cannot use = in the condition of an if-statement or a subexpression. To ensure you really intended = and not ==, you must opt-in by using :=, which does return a value. This also stands out visually for others who later read your code.

Note that ^ is bitwise XOR and not an exponent.

Notes on Modulo

The % operator is almost always the correct operator to use; %% and %%% are only available for optimizations and completeness. Curious readers read on, and learn why %% and %%% can occasionally be useful.

Modulo is an interesting operator to support in terms of language interoperability. Programming languages are split into two camps when performing modulo on negative numbers. It is often considered more mathematically accurate to keep the sign of the divisor, however it can be faster to keep the sign of the dividend. However, if using the sign of the divisor is the "native" modulo, often using the sign of the dividend is actually slower.

In Wake we wanted the modulo operator to work the same everywhere, while still allowing for fast code. The solution we came up with is to have the three modulos, %, %%, and %%%. By default, we determined behavior needed to be standard across all language targets, making the more accurate approach ideal. Therefore the behavior of % is to keep the sign of the divisor. You are unlikely to ever have to worry about modulo beyond these great defaults.

If you are not dealing with negative numbers in your modulo, we give you the opt-in potential to use %%. This chooses the fastest modulo available for the platform you are on. Since modulo is often used for tight, performant code, we made this operator to guarantee speed over consistency.

Lastly, the %%% operator is there to complete the picture, using the sign of the dividend. Perhaps you have code that relies on the negative behavior of %% on a dividend-native language, and want to use it on a divisor-native language. Or perhaps you wish consistency and only care about performance on a dividend-native language. Its available for the sake of completeness and control.

Classes in Wake concise readable

Wake is a truly Object Oriented language, and should come with few surprises here.

In Wake, everything is an object. A class declaration begins with the keyword every as seen below. Within that definition can be properties, dependencies, a constructor, methods, and provisions.

Wake
every MyClass is:
    with public Text name;
    with Text ssn;

    needs Int id, Printer then {
        // constructor body
    }

    provides Text:URL <- 'http://www.wakelang.com';

    Text -- getSSNLastFour() {
        // ...
    }

Dependencies concise testable

When an object is constructed, first the dependencies are all resolved by means of provisions. This means that you can simply say your object needs Printer, and you will always have one.

Wake
every FileStream is:
    needs Text filename, public OutStream;

Properties flexible

At this point the object has its dependencies met, and each property can use the needs in their initialization. Then the constructor is called.

Wake
every IncrementingProperties is:
    needs Int starter, Printer {
        Printer.print(sum);
    }

    with Int plusone = starter + 1;
    with Int plustwo = starter + 2;

Inheritance testable flexible readable

Every class can be inherited with or without keeping behavior, with the keywords a/an, and capable. Usually in small projects the point of inheritance is to copy the code from that class, however, it is not always desirable. If you use capable, you must rewrite the methods of your child class. These actions are called extending vs implementing.

An additional value to implementing classes with capable instead of extending classes with a/an, is that you can implement multiple classes even though you can only extend one class.

An object which extends or implements another class can be used as that subclass elsewhere in the code.

Wake
every BaseClass is:

    myMethod(Printer) {
        Printer.print("BaseClass");
    }

every SubClass (a BaseClass, capable Printer) is:

    needs Printer;

    print(Text) {
       Printer.print("SubClass printing: " + Text);
    }

    useThisAsPrinter() {
        myMethod(this);
    }

Wake's Module System concise readable

Wake does not yet support auto-imports for all classes within a package, like in Java. This is because cyclical imports are not quite just yet handled.

Wake uses the keywords module and import to break code into modules. Modules are optional when you create your own code, but must be accurately specified during an import.

module example.module;

import other.module.SomeClass;

every ModuleExample is:
    needs SomeClass;

Modules must be lowercase. Once a class is imported from another (or the same) module, you can then refer to that class by its unqualified classname.

When this code is compiled, a table file will be created for this class and written to the location ${tabledir}/example.module/ModuleExample.table.

If you are using the project seed, files are expected to exist in the src/${modulename} directory. In this case, the file should exist in the location src/other.module.SomeClass.wk.

Modules can be shared by handing out the directories bin/wakeobj/{module} and bin/waketable/{module}. If you are importing others' code with the wake project seed, you can make directories lib/{module}/obj and lib/{module}/table to begin using the third party wake sources in your project.


Valid Methods in Wake concise readable

Methods in Wake can look strange at first. The changes are simple and make your APIs read like sentences.

In java, a detailed method signature may look like:

addExaminerToContractWithMedicalCompany(Examiner examiner, MedicalCompany company)

We decided we can do better. In Wake you could rewrite that method as:

add(Examiner)ToContractWith(MedicalCompany)

Here are some examples:

Wake
every MethodExample is:
    ReturnType -- methodWithReturn() {}
    methodWithNoReturnValue() {}
    abstractMethodWithNoImplementation();
    methodWithArgument(Int) {}
    methodWithArgument(this Int) {}
    methodWithArgument(Int here) {}
    methodWith(Int)ArgumentAnd(Text)Argument() {}

Invoking Methods readable

Invoking methods (that is, calling a method's code on a particular object and argument set) looks almost exactly like defining one.

Wake
every MethodInvoker is:

    methodWithArgument(Bool) {
        methodWithArgument(!Bool);
    }

Method Overloading readable concise

Method overloading can create more confusion than it prevents. As a general rule, avoid using overloading and aliases together for the clearest code.

Method overloading exists in C++ and java. A method can accept differently typed objects, and have different behaviors for each. Used properly, it creates shorter method names that focus on behavior rather than implementation.

Wake
every CrossTypeComparator is:
    Bool -- compare( Text )And( $Text ) {
        //...
    }

    Bool -- compare( Int )And( $Int ) {
        return Int < $Int;
    }

Generics flexible typesafe

Like Java, C#, and other languages, Wake supports generic types.

Generics are required for a powerful static type system. Unlike Wake, Java and C# did not have generics in early versions. As such, the type safety and performance of arrays in these languages was thrown out to accomodate missing flexibility. Wake had generics from version one and does not suffer these drawbacks.
The Wake syntax uses curly braces instead of angle brackets for generics. This simple difference speeds up compilation and makes parsing easier for independent programs, as it is a 100% context-free grammar.
These examples use function types that are not yet implemented.
Wake
every CalculationCache{T} is:
    with T? = nothing;
    needs T -- fn() calculation;
    T -- getValue() {
        if T == nothing {
            T = calculation();
        }
        return T;
    }

The type information of a generic is deffered until the moment where its used. For those unfamiliar with generics, using one would look like this.

Wake
every Squarer is:
    needs Int;
    provides CalculationCache{Int};
    with CalculationCache{Int} <(Int -- fn() { return Int * Int; }) this;
    Int -- get() {
        return CalculationCache.getValue();
    }

Generic Methods flexible typesafe concise

While generic methods don't create any features that couldn't be accomplished with generic classes, they reduce boilerplate in certain one-shot operations that have generic qualities. For instance, making your own function that checks a list's length.

every ListAsserts is:

    {T} that(T[])HasLength(Int) {...}

    {T} that(T[])Matches(T[]) {...}

Calling this generic method looks just like normal: ListAsserts.that([5,6])HasLength(2). Note that you can have multiple comma separated generic types. Also note that in the second example there are two usages of T; these must match at call time or the compiler will reject your program.


Provisions flexible testable typesafe

Testability was a huge goal of Wake's design. We were inspired by dependency injection frameworks to make a faster, typesafe means of binding dependencies to behavior. What we came up with is provisions.

What in other languages may be a static method, or a call to new another object, or a helper function call, instead is fetched from a provider. Here we look at how.

Wake
every UsesProvider is:

    needs Provider, DependentClass:Special then {
        var Printer from Provider;
        (Printer from Provider).print(Text);
    }

Defining Plain Provisions concise typesafe

A plain provision simply marks that your class can provide a type. The compiler will ensure that you can provide all of that type's dependencies as well.

Wake
every Provider is:

    provides Printer, OtherClass;

Providing Subtypes flexible testable typesafe

The point of provisions is to make all dependencies or created objects be replacable with subtypes, at the creator's discretion. This is called Inversion Of Control. You can provide mock objects for testing, easily swap logging methods, and more.

Wake
every Provider is:

    provides Printer <- DisabledPrinter;

Specialized Provisions typesafe flexible

Sometimes a classname is not detailed enough to know your dependencies will be properly provided. Using what we call Specialties, you can provide and require specific objects.

Wake
every DBProvider is:

    provides
        User,
        DBConnection:Default;

every User is:

    needs DBConnection:Default;

Primitive Provisions typesafe

Primitive provisions cannot be plain, since the compiler can't tell you what Int to provide. Additionally, they must be specified, to ensure you get the right value. These are the equivalent of constants in Wake, and bind directly to a primitive value.

Wake
every PrimitiveProvider is:

    provides Text:Username <- "MyUser", Int:Port <- 3306;

Constructor Provisions flexible

We call this constructor provision since it most closely resembles constructors in languages like Java. However, it is merely a way of matching provisions to specific dependencies.

Wake
every Provider is:
    provides
        Int:Port <- 3307,
        Text:Username <- "Test",
        DBConnection:Test <- DBConnection(Text:Username, Int:Port),
        User <- TestUser(DBConnection:Test);

If a class with needs extends another class with needs, constructor provisions require that you supply all of these. When doing this, supply the childclass needs first, and then the parent class needs, in that order. And don't worry, the compiler will make sure you don't mess up.

Provision Arguments flexible typesafe testable

Not all object needs should be satisfied by the dependency injection in Wake. We stil want to be able to pass in values when we call a provision. Providers can be coded up to do this.

Wake
every Person in:
    needs Text firstname, Text lastname;

every PersonProvider is:
    provides Person <- Person(?Text, ?Text);

    usePersonProvision() {
        var Person('Bobby', 'Ratsnest') from this;
        var second Person("Bobby", "Ratsnest") from this;
        var Person three("Bobby", "Ratsnest") from this;
    }
The syntax ?Text has two purposes. Firstly, it documents the need types on the API where you will look for them: the provider you are using. Secondly, it allows you to ask for a more specific type than the object you're providing actually requires. i.e., when providing an object which only needs a Printer, you could ask for a CachingPrinter instead.

For classes that inherit needs, follow the instructions in the Constructor Provisions section on which order to specify them.

Behavioral Provisions flexible

Provisions can be treated as a code body with a return type where necessary. Note that the end result must be a provision; however it can be used to configure objects or provide different objects conditionally, or create a singleton.

Wake
every Provider is:

    needs Session then {
        Session.loadFile(Text:SessionFile < this);
    }

    provides
        Text:SessionFile,
        Session <- { return Session; };

You can also have arguments to your behavioral provisions, so that you can mock out provisions with arguments, and/or perform your own unique operations required.

Wake
every Provider is:
    needs BabyMaker;
    provides Child <- (Person dad, Person mom) {
        return BabyMaker.makeBabyWithFather(dad)AndMother(mom);
    };

Closures concise flexible

Wake has day-one support for closures (also known as lambdas and/or anonymous functions). Unlike javascript, in Wake closures do not change the context of this.

Wake
every ListSorter is:

    sort(Int[]) {
        Int[].each({ Int -> if Int == ... { ... } });
        Int[].sort({ Int, $Int => Int < $Int });
    }

As Types readable typesafe

Closures passed into functions or declared as variables require an alias, and look very consistent with other Wake syntax. From there they are usable like any variables.

Wake
every HigherOrderFunctionUtil is:

    useMysteryNumber(fn(Int) mysteryFn) {
        mysteryFn(10);
    }

    Bool -- compare(Bool, $Bool, Bool -- fn(Bool, Bool) comparator) {
        return comparator(Bool, $Bool);
    }

    Bool -- compareAliasFirst(Bool, $Bool, comparator Bool -- fn(Bool, Bool)) {
        return comparator(Bool, $Bool);
    }

Type Inference concise typesafe flexible

Wake's closures will infer their return types, and use their context to guess the type of untype arguments.

Wake
every TypeInferenceExample is:

    examples() {
        Int[].each({ ->
            return true;
        });

        Int[].sort({ a, b => a < b });
    }

Self Executing flexible

While functional programming takes the principle that "everything is an expression", Wake does not. What this means is that some constructs, such as loops, do not have values. This means you can't loop within an expression.

By using self executing closures, you can embed statements within an expression. Do note, however, that this is often a code smell. There is nothing 'special' about self executing closures, from a language perspective, except that it requires parenthesis in the grammar.

Self executing functions within javascript are also often a sort of hack to create isolate scopes. This is not required in Wake, making self execution less common.

Wake
every SelfExecutingFunctionExample is:

    myMethod() {
         5 + ({ ->
                 var Int[] = [1, 2, 5];
                 //...
                 return 10;
            })();
	}

Keeping This Context concise typesafe

In Javascript, this represents the object owning the function when it was invoked. This is usually regarded as a mistake, and so Wake has rectified it.

In Wake, a closure knows where it was made and all usages of that function represent the context where it was originally created.

Wake
every CacheFactory is:

    with Text[] cachesById;

    putCache(Int, Text) {
        cacheById[Int] = Text;
    }

    Text -- fn(Int) -- getCache() {
        return { Int => cacheById[Int] };
    }

Enclosing Local Vars concise flexible typesafe

Closures capture variables in the scope where they were declared, allowing stateful effects such as iterators and more. Javascript, ruby, and even modern C++ users will be quite familiar with how this works.

Wake
every CountingVisitor is:

    needs Printer;

    setOnVisit(Visitable) {
        var Int = 0;
        Visitable.onVisit = { ->
            Printer.printLine(Int);
            Int = Int + 1;
        }
    }

Ternary Operators readable concise

Wake uses python style ternary operators, which are more readable than the c-style :? approach.

Wake
every MyClass

    Int -- myMethod(Bool) {
        return 4 if Bool else 5;
    };

Annotations readable testable flexible

Wake now has unchecked annotations. We have a plan to typecheck the values you use, in which annotations, on what parts of your source code. But for now, you can freely annotate classes, methods, and needs with values such as Texts, Bools, Ints, and even nothing. This is currently used by our unit test library, but can be used for so so much more.

Wake

@TestClass
@MadeUpAnnotation("hello", false, 123, nothing)
every MyClass

    needs
        @MadeUpAnnotation("hello", false, 123, nothing)
        Printer;

    @Test
    @MadeUpAnnotation("hello", false, 123, nothing)
    testSomething(Asserts) {
        // ...
    };

Unit tests

While there are many ways to write unit tests in Wake, the current standard way is to use wUnit with the mocking framework wockito. If your project is based on the wake project seed, then these are hooked up into your build for you already.

Simply create a wake file inside the test/ directory, and you can use Annotations to mark your tests.

import Asserts;
import SqrRoot;

@TestClass
every SqrRootTest is:

    needs SqrRoot;

    @Test
    testSquareRootOfSixteen(Asserts) {
        Asserts.that(SqrRoot.of(16))Equals(4);
    }

Mocks

If you need to create a new SqrRoot in each of your tests, you can use provisions to accomplish this. Similarly, you can inject mocks via wockito by use of the same feature. Say, for instance, we are testing that our 'Hello World' app actually prints 'Hello World!'. We could run the program and read its output, but that is not scalable, so Wake guarantees you always have the option to use mocks instead.

@TestClass
every MainTest is:

    needs MockProvider mocks;
    with MockPrinter from mocks;
    provides Main, Printer <- { return MockPrinter; };

    @Test
    ~[ test Main.main() prints Hello World ]~(Asserts) {
        mocks.use(Asserts);
        mocks.when(MockPrinter).getWrittenCharacterCount().thenReturn(20);
        var Main from this;
        Main.main();
        mocks.verify(MockPrinter).printLine("Hello World!");
    }

Program Entry Points flexible testable fast

After compiling each Wake file, you can link the files together and choose where the program begins. This means the code you write does not guarantee any particular entry point, which in turn means your test suite can have a different entry point than your app. Without this, you would be unable to test your application's startup in unit tests.

Any class with automatically inferrable dependencies (ie, no primitives, lists, etc) can be your startup class, and any of its methods which take no arguments can be the starting method. These are provided by the compile time flags -c and -m respectively, but default to Main and main().

This means that when you link your Wake files together, the compiler generates a startup routine that wires up the complete object graph required by your main class, and invokes your desired startup method. This makes for a fast, flexible, and testable program startup.