Noop

From Bauman National Library
This page was last modified on 21 May 2016, at 23:28.
Noop
First appeared 2009
Typing discipline static
License Apache License 2.0
Website https://code.google.com/p/noop

Noop (pronounced noh-awp, like the machine instruction) is a new Google`s language experiment that attempts to blend the best lessons of languages old and new. Noop is initially targeted to run on the Java Virtual Machine.

What is the status of Noop?

Right now, we are in an early design and development phase. You can't code anything interesting in Noop yet.

Why another language?

Experience has been that developers often create code that's hard to test and maintain, without realizing it. On a large software project, this can create problems later on for the whole team. In analyzing this problem, we found that the root cause in many cases was language features - like globally visible state, misused subclassing, obligatory and redundant boilerplate, and API's that are easily misused. Noop seeks to apply the wealth of lessons of language development over the past 20 years and optimize on cleanliness, testability, ease-of-modification, and readability. Rather than innovating on language features, Noop attempts to innovate on the developer's experience.

Who's behind Noop?

Noop is a side-project from a collection of like-minded developers and contributers, which hail from several companies, including (but not limited to) Google.

Noop is opinionated

The developers of Noop feel pretty strongly about good and bad practices in software development. Noop will support or deter these as best as possible. For example:

Noop says Yes to Noop says No to
Dependency injection built into the language Any statics whatsoever
Testability - a seam between every pair of classes Implementation inheritance (subclassing)
Immutability Primitive types (everything's an object)
Syntax geared entirely towards readable code Unnecessary boilerplate code
Executable documentation that's never out-of-date
Properties, strong typing, and sensible modern standard library

Why Noop?

Dependency Injection changed the way we write software. Guice and PicoContainer are an important part of many well-written applications today.

Automated testing, especially Unit Testing, is also a crucial part of building reliable software that you can feel confident about supporting and changing over its lifetime. Any decent software shop should be writing some tests, the best ones are test-driven and have good code coverage.

Noop is a new language that will run on the Java Virtual Machine, and in source form will look similar to Java. The goal is to build dependency injection and testability into the language from the beginning, rather than rely on third-party libraries as other languages do.

There are three proposed ways to use your Noop source files:

  • Java translator: produces Java source. Allows you to use Noop without converting your codebase, but not all runtime features of the language are provided.
  • Interpreter: reads and evaluates the Noop code through an interpreter. Slower, but will have a command-line interface
  • Compiled: to Java bytecode.

Initial efforts will be focused on an interpreter and Java translator, to allow early experimentation with Noop. We want people building applications, not ruminating about language concepts.

Fundamentals

  • No primitive types, everything is an Object.
  • Strong typing
  • No optional syntax: semicolons and parenthesis required
  • Has properties
  • No facility for statics
  • Executable/compiled documentation as much as possible
  • Classes have retained metadata
  • There is always a seam between any pair of classes, for testing

Readability

  • Always be consistent
  • Don't use shortcuts. These make it quicker to write, but harder to read, especially for a code reviewer who is not expert in the language but knows C++ or Java well.

Good stdlib

  • Pick best implementations from other languages
  • Use JodaTime for Date/Time apis
  • Use util.concurrent for concurrency
  • Expose Google collections
  • Introspection done objective-c style (easy, few lines of boilerplate)

Noop references

We are assuming that newable objects are mainly data or value objects (or entity objects) that may have additional convenience calculations attached to them. These convenience calculations may be dependent upon the presence of optional internal delegates. There are many situations where an object reference may be null. If, for example, the object uses an optional delegate, how does a value object perform calculations when an optional delegate objects are not present? Usually developers add conditional code to check for null and perform alternate calculations when optional items are missing. We propose supporting two key language features:

1. An objective-c approach to null references 2. Null Object Pattern within NOOP.

By doing so, much null-related conditional logic can be replaced with polymorphism.

Invocation against null-references

When a variable reference is assigned to the null value, we allow calling methods upon the reference, similar to behaviour found in Objective-C. The default behaviour of a method with no return type is a no-op. The default behaviour of a method with a return value is to return null:

 Foo foo = null;
 foo.doFoo();  // Does not throw NullPointerException, performs a no-op by default
 assertTrue(null == foo.getFoo()); // Does not throw NPE, performs a no-op and returns null by default

Chaining of methods results in a chain of this no-op behaviour.

BoogaResult result = foo.doBar().processBlah().boogabooga();

This would not throw an exception, but would, if any of the above returned null (or if foo was null) would result in null being assigned to result.

Null Object Pattern

We allow the developer to override the behavior for a null reference based on its type. For example, given a type Foo:

 interface FooInput {
   void increment();
 }

 interface FooResult { ... }

 interface Foo {
   void doFoo(FooInput input);
   FooResult getFoo();
 }

 null Foo {
   void doFoo(FooInput input) {
  input.increment();
   }

   FooResult getFoo() {
     return new FooResult(5);
   }
 }

Perhaps such defaults could be provided at a Module level, through injection bindings:

 null Foo(FooResult default) {
   void doFoo(FooInput input) {
  input.increment();
   }
   FooResult getFoo() {
     return default;
   }
 }

In the following example, a ShoppingCart object groups LineItems representing products on a purchase order. Each line item points to its product, a Purchaseable, its base price, a Money instance, and an optional Discount, which calculates the final price of the product after the applied discount. Rather than include conditional code within LineItem that worries about what to do when a creator creates a LineItem without specifying a Discount, we provide a smart null implementation of Discount:

 interface ShoppingCart {
   void addLineItem(LineItem item);
 }

 interface Purchasable { ... }

 interface Currency { ... }

 interface Money {
   Currency getCurrency();
   long getAmount();
 }

 interface Date { ... }

 interface Discount {
   Money apply(Money basePrice, Date date);
 }

 null Discount {
   public Money apply(Money basePrice, Date date) {
     return basePrice;
   }  
 }

 createable LineItem {
  readable Purchasable product;
  readable Money basePrice;
  optional readable writeable Discount discount;
  readable virtual Money finalPrice {
     get {
       return discount.apply(basePrice, Date.now());
     }
   }
 }

Usage:

 ShoppingCart shoppingCart = ...;

 Purchasable catamaran = ...;
 Money catamaranBasePrice = ...;
 Discount catamaranDiscount = ...;
 LineItem catamaranPurchase = new LineItem(catamaran, catamaranBasePrice, catamaranDiscount);

 assertTrue(catamaranPurchase.finalPrice < catamaranPurchase.basePrice);

 Purchasable carousel = ...;
 Money carouselBasePrice = ...;
 LineItem carouselPurchase = new LineItem(carousel, carouselBasePrice); // No discounts here

 assertTrue(carouselPurchase.finalPrice == carouselPurchase.basePrice);

 shoppingCart.addLineItem(catamaranPurchase);
 shoppingCart.addLineItem(carouselPurchase);

 Money grandTotal = shoppingCart.total();

Exceptions

Checked exceptions cause a lot of problems, so Noop only has unchecked exceptions. Coders often use exceptions for expected error states. An example:

 // in Java
 FileOutputStream out;
 try {
   out = new FileOutputStream(new File("/tmp/foo"));
 } catch (FileNotFoundException e) {
   // handle it
 }
 out.readline();

Using exceptions to check for "expected" or "likely" error conditions is usually not an appropriate use of exceptions. Creating the exception requires capturing a stack trace, and the try-catch block interrupts the control flow and creates the ugly scope, in order to provide a reference to the exception. In this example, it requires declaration of the variable out in a null state so it must be mutable.

This Java code could check for the file existence before trying to read from the file.

File file = new File("/tmp/foo");
if (file.exists()) {
  FileOutputStream out = new FileOutputStream(file);
  out.readline();
} else {
  // handle it
}

But, this requires the File API to do some work twice. Some API's are especially bad for this sort of work, like having to run two database calls instead of one just to support this conditional.

This proposal allows a way to hand an error back from a single API call in a more declarative way than with an unchecked exception, without the scoping issues and stack trace creation in an exception.

Details

In the spirit of C's error out variables, some methods may declare that they return an optional error in addition to the returned value. This allows the caller to handle the error in a normal control flow.

Here is an example with another common case, parsing a number from a String. A failure to parse is an expected error.

class Int() {
  Int, Error parse(String s) {...}
}

Int a, Error e = new Int().parse("13a");
if (e.isError()) {
  // handle error
} else {
  // a must be a good int
}

We can return some sentinel value for a in this case, like zero. This has some advantages if the caller doesn't care to check this for an error, ie. there is some other validation of the value.

The caller should be able to ignore the error, also.

Int a, _ = new Int().parse("100");

Class parameters and properties

Introduction

Boilerplate code is evil. Java, sadly, in order to follow recommended best practices around isolating access to the internals of an object have adopted the "bean" approach. Adding first-class language support for lazy declaration of mutators and accessors would allow the proper encapsulation to occur without requiring "just in case" boilerplate code, with the result that calling code need have no knowledge of whether it is obtaining or setting a property value via acessors/mutators or directly from the field.

Ancestry of the approach

Accessors and mutators should be declared specifically within the language as a first class language element. Propose a similar approach to C# in this regard. In fact, the property syntax (roughly) of C# (with minor modifications) would be the only means by which to create an instance variable

Basics - declaration, storage, and use

Properties will be declared similarly to instance variable in traditional languages, but setters and getters will be declared similarly to C# properties. The most basic example is:

public class Foo {
    public Bar bar;
}

The semantics of this declaration are that a public property named foo is declared with type Foo, and that no special access or mutation logic is present. Such a property is accessed the way variables of the same scope (public) are traditionally accessed in Java or C#. i.e.:

     Foo foo = new Foo();
     foo.bar = new Bar();  // (sets foo's "bar" property)
     Bar bar = foo.bar; // (gets foo's "bar" property)

The implications of this are that there is a set and get logic which mediates access to the raw value. But where is the raw value?

The underlying storage can be accessed directly only within the class itself, by means of a dereferencing symbol @. In the case of Foo above, one would access the storage with @bar. However, @bar would have no meaning outside of the class itself. One would not be able to invoke foo.@bar to circumvent the property's get/set logic. Being private to the instance, the raw storage created this way would also not be accessible by any children or other entities.

A declaration like this:

    public Foo foo {
        set {
            @foo = foo;
        }
        get {
            return @foo;
        }
    }

A property with no storage could be created as in the following example, which delegates to a service which was injected.

public class(SomeService service) { 
    public virtual Foo foo {
        set {
            @service.processFoo(foo);
        }
        get {
            return @service.fetchFoo();
        }
    }
}

Because the above is virtual, no storage is created for foo and so the following code would break.

public class(SomeService service) {     public virtual Foo foo {
        set { @service.processFoo(foo); }
        get { return @service.fetchFoo(); }
    }

    public Void doSomethingOnFoo() {
        // this line is valid, as it accesses via the property
 foo.doSomething(); 
        // no such raw value for foo exists, so this breaks compilation
        @foo.doSomething();
    }
}

Testing

  • test keyword allows compact syntax for declaring nested test suites with tests as the leaves. No need to use classes and methods to declare test suites and tests. See ProposalForTestingApi.
  • Allow test blocks within production code, so the tests are right next to the code-under-test. Probably want to strip that from non-debug/non-test compiles.
  • tests have some "friend" relationship with the class under test, to allow whitebox test of private methods
  • Every instantiation goes through the injector, so there's always a seam between every pair of classes

Public API

  • need some way to enforce public API separate from visibility of types and methods. Maybe don't need "private" at all
  • Android has an interesting approach: create interface jars which have the implementations stripped from public classes and don't contain private stuff at all.

Literals

Lots of useful literals!

  • String is a type literal
  • "Hello" is a string literal
  • """line1\nline2""" is a multi-line string literal
  • /.*/ is a pattern literal
  • { "a": "b", "c": "d" } is a hash literal (should evaluate to most specific type possible)
  • [1,2] is an array literal
  • `http://google.com` is a URI literal (like Fan, is it useful?)

References

  1. Official Site Google Noop
  2. wiki Noop