Thursday, February 23, 2012

Overriding core functions in Dart

An interesting question came up on the Dart mailing list today, asking how to essentially overload a core function, avoiding a name collision.

For example, how to handle this?

class Printer {
                       // print() comes from dart:core
  print(String msg) => print(msg);
}

The dart:core top level function print() is available and defined, however what do you do if you want to name a method print() ?

Thanks to Sam McCall, there are a few options:


Function consolePrint = print; 
class Printer { 
  print(m) => consolePrint(m); 


and

consolePrint(m) => print(m); 
class Printer { 
  print(m) => consolePrint(m); 



Thanks for the question and answer about Dart!

IndexedDB in Dart

WARNING: As of 2012-02-27, and after further hacking, there are two blocking issues preventing IndexedDB from working properly. You can't delete an object by a key, and retrieved objects aren't actually objects.

IndexedDB is the new way to store key and indexed objects for offline retrieval in web apps. Learn how to use IndexedDB in your Dart application.

(Warning: this code isn't pretty, and many bugs were filed. This should get cleaned up soon. Regardless, it works! Also, you'll need the bleeding_edge branch of code, as of 2012-02-23.)

Why offline data storage?

Use client side local data storage to reduce bandwidth bills, decrease latency, increase UI performance, and allow your application to run offline.

Local data storage options

There are three options for storing local data for your web apps.

Local Storage (aka Web storage) is a very simple key/value store, however it can only store strings and is a blocking/synchronous API. Local Storage is better than cookies, but try to avoid it. Local Storage does have good cross browser coverage.

WebSQL is a SQLite database in the browser, supported by Webkit based browsers. WebSQL is powerful and fairly well understood, but will not be implemented in Firefox or Internet Explorer, and the spec is in limbo.

IndexedDB is on a standards track, and does have Chrome, Firefox, and (soon) Internet Explorer implementations. IndexedDB is the future of offline, local object storage for your web apps.

Ideally, you should use something like Lawnchair as an interface for object storage, and let the library implementation choose the best storage available. Lawnchair isn't ported to Dart... yet... so we'll look at direct IndexedDB code.

IndexedDB Reference

IndexedDB is a bit of a moving target. I used a combination of the Simple Todo List Using HTML5 IndexedDB from HTML5 Rocks and the MDN reference docs for IndexedDB. Of course, I also consulted the Dart API Docs.

The Bugs

During this exercise, I encountered and filed the following bugs:
Even with all the bugs above, you can still run IndexedDB in Dart. A big thanks to the Dart engineers that quickly fixed the blocking bugs.

The Code

IndexedDB currently works best with dart:dom. I used types so the Dart Editor helped me out with code completion and early error checks.

If you're not familiar with IndexedDB, this will look verbose to you. The API has been designed to use callbacks whenever possible to help ensure the main UI thread of your web app isn't blocked on slow IO calls.

-----------------------------------------------

#import('dart:dom', prefix:'dom');
#import('dart:html');

String VERSION = "1";
String TODOS_STORE = 'todos';

initDb(db) {
  if (VERSION != db.version) {
    dom.IDBVersionChangeRequest versionChange = db.setVersion(VERSION);
    versionChange.addEventListener('success', (e) {
      print("Set version");
      db.createObjectStore(TODOS_STORE);
      displayItems(db);
    });
    versionChange.addEventListener('error', (e) {
      print("Could not set version: $e");
    });
  } else {
    print("DB is at version ${db.version}");
    displayItems(db);
  }
}

displayItems(dom.IDBDatabase db) {
  dom.IDBTransaction txn = db.transaction(TODOS_STORE, dom.IDBTransaction.READ_ONLY);
  dom.IDBObjectStore objectStore = txn.objectStore(TODOS_STORE);
  dom.IDBRequest cursorRequest = objectStore.openCursor();
  cursorRequest.addEventListener("success", (e) {
    dom.IDBCursor cursor = e.target.result;
    if (cursor != null) {
      renderItem(cursor.value);
      cursor.continueFunction();
    }
  });
  cursorRequest.addEventListener('error', (e) {
    print("Could not open cursor: $e");
  });
}

renderItem(value) {
  var ul = document.query('#items');
  var li = new Element.tag("li");
  li.text = value;
  ul.elements.add(li);
}

addItem(db) {
  var msg = "Stuff ${new Date.now().value}";
  dom.IDBTransaction txn = db.transaction(TODOS_STORE, dom.IDBTransaction.READ_WRITE);
  dom.IDBObjectStore objectStore = txn.objectStore(TODOS_STORE);
  dom.IDBRequest addRequest = objectStore.put(msg, new Date.now().value);
  addRequest.addEventListener("success", (e) {
    renderItem(msg);
  });
  addRequest.addEventListener("error", (e) => print("Could not add: $e"));
}

registerButtons(db) {
  var add = document.query("#add");
  add.on.click.add((e) {
    addItem(db);
  });
}

// Chrome only for now,
// see bug http://code.google.com/p/chromium/issues/detail?id=108223
void main() {
  dom.IDBRequest request = dom.window.webkitIndexedDB.open('todo');
  request.addEventListener('success', (e) {
    print("Opened DB");
    dom.IDBDatabase db = e.target.result;
    // the following is Chrome only for now :(
    initDb(db);
    registerButtons(db);
  });
  request.addEventListener('error', (e) {
    print('Could not open db: $e');
  });
}

-----------------------------------------------

The above code only works in Chrome right now, as unfortunately Chrome hasn't caught up with the spec yet. With just a tiny bit of tweaking, you'll be able to get this to work in Firefox, too.

You can fork this code from the original Gist.

Summary

IndexedDB is a key/object store with support for indexes, and it is the eventual replacement for WebSQL database for complex object storage requirements. The API is heavily callback driven, ensuring the main browser UI thread isn't blocked by expensive I/O calls.

IndexedDB (at least, creating a database, reading entries, and adding entries) now works with Dart when compiled to JavaScript! Though we're still waiting for direct Dartium support, you can begin to add IndexedDB to your modern web apps.

Tuesday, February 21, 2012

Pretty print Dart collections

Thanks to a recent commit from Josh Bloch, you can now pretty print your Dart collections. This makes debugging and scripts much easier to write.

Example


main() {
  var list = [1,2,3];
  print("List: $list");


  var map = {"hello": "world", "1": 1};
  print("Map: $map");


  var set = new Set();
  set.add("foo");
  set.add("bar");
  print("Set: $set");
}

The VM's old 'n busted results:


List: [1, 2, 3, ]
Map: Instance of 'LinkedHashMapImplementation'
Set: Instance of 'HashSetImplementation<Dynamic>'


The VM's new hotness results:


List: [1, 2, 3]
Map: {hello: world, 1: 1}
Set: {bar, foo}

Recursion

That's pretty, but what about something tough? How does it handle recursive references?

list.add(list);
print("Recursive list: $list");

Old 'n busted:

[I never let it stop running, totally busted]

New hotness:

Recursive list: [1, 2, 3, [...]]

You'll need the very latest source from the bleed_edge branch to see this in action. It won't be long until this lands in trunk, though.

Enjoy!

Friday, February 17, 2012

Enabling type checks for Dart

Intro

Dart is an optionally typed language, which means it helps you if you add static types, but doesn't get in the way if you don't. Dart is designed to allow you to scale from simple scripts to large, complex applications, and optional types are a key feature for this vision.

Because types are optional, Dart programs must have the same runtime semantics with or without type statements. If the type statements are ignored during runtime, what good are they?

Think of type statements more like documentation or annotations for your program. Once added, these types are a terse way for you to express your intent to both your fellow humans and your fellow machines. Using the type annotations to express "x is a number" is a great way to clearly indicate what x is so other developers know how to use it. Compilers can use this annotation to warn you if you do something unnatural with x.

Example

Consider the following interface:

interface Util {
  add(a, b);
}

It's very hard to tell what the purpose of the add() method is. What type of arguments does it take?

You might think that adding comments is a good way to indicate your intent:

interface Util {
  /**
   * a is a number
   * b is a number
   */
  add(a, b);
}

Now your fellow humans have a better understanding of what to pass to add(). Unfortunately, the platform can't read those comments, so the compiler and editor can't help other developers with hints, code completion, and early warning.

So we recommend using types in method signatures and interfaces (the "surface area" of your code):

interface Util {
  num add(num a, num b);
}

Now it's clear that add() takes two numbers and return a number. No hidden comments necessary to convey this information, and both humans and machines benefit!

Benefiting from types

Dart programs run in two different modes: production mode and checked mode. In production mode, types annotations are ignored. However, in checked mode, the type annotations spring to life and provide helpful feedback.

To turn on checked mode, following these instructions:


  • for the VM: --enable_type_checks --enable_asserts
  • for Dartium:  DART_FLAGS='--enable_type_checks --enable_asserts' path/chrome
  • for frog: --enable_type_checks
You can find the VM and frog in the SDK. Dartium, which is Chromium with an embedded Dart VM, is available as a binary download.

For the test, I'll use this simple script:

num add(num a, num b) => a + b;

main() {
  print(add("hello", "world"));
}

If you run this in production mode, the program will complete and will print "helloworld". Not exactly what you want, nor what the original developer intended for add().

Note: soon, you won't be able to concatenate Strings with +. The example above is just illustrative.

However, running in checked mode will display this:

Unhandled exception:
'/Users/sethladd/tmp/types/types.dart': Failed type check: line 1 pos 13: type 'OneByteString' is not assignable to type 'num' of 'a'.
 0. Function: '::add' url: '/Users/sethladd/tmp/types/types.dart' line:1 col:13
 1. Function: '::main' url: '/Users/sethladd/tmp/types/types.dart' line:4 col:11


When production mode?

You might be wondering, why should you ever turn on production mode? When is the right time for production mode?

As the name implies, production mode is for running your apps in production. The main benefit is possible performance improvements, due to disabled type assertions.

Summary

Dart's optional typing helps you to scale your program from simple scripts to large complex applications. The types can be considered annotations, helping express your intent to both humans and machines.

Turning on checked mode will enable the type assertions and checks, which help with early warnings if the system detects problems from type mismatches. You can run Dartium, frog (the Dart to JavaScript compiler), and the virtual machine in checked mode.

We highly encourage you to use checked mode during any and all development. Only when you ship to production should you use production mode, where there are possible performance improvements because types checks are disabled.

Thursday, February 16, 2012

Classes in Dart, Part One

(This is part 12 in an ongoing series on Dart. See part 11, Booleans in Dart.)

Intro

Dart is a familiar object oriented programming language, with classes, single inheritance, and interfaces. You can build classes and instantiate objects pretty much like how you would expect, with a few caveats and pleasant surprises. This post is the first in a series of posts that will explore classes in Dart.

If you think you need to use classes when programming in Dart, think again. Dart is a scripting language, which implies you can write "short and sweet" scripts and programs with ease. Dart functions very well with only, well, er,  functions, alleviating the requirement to build classes. In fact, I've made it through eleven posts here in this series without talking about classes. You can get far with just functions in Dart.

Dart is built to be scalable, helping you as your program grows in scope. You may start with a collection of functions, but as complexity increases, eventually you'll want to organize your code into classes.

Class

Straight from the language spec:
A class has constructors,  instance members and static members. The instance members of a class are its instance methods, getters, setters and instance variables. The static members of a class are its static methods, getters, setters and static variables. Every class has a single superclass except class Object which has no superclass.
 Translated, the above says:
  • Dart's classes have single inheritance
  • Dart's classes have constructors
  • Dart's classes have instance members like methods, getters, setters, and variables
  • Dart's classes can also have static methods, getters, setters, and variables
Single inheritance means each class has a single superclass. A class may only inherit from one other class.

Constructors are special methods on a class that initialize a new instance of a class. Constructors don't return anything. Dart supports traditional constructors (methods named the same as the class) and "named constructors". We will see examples of both shortly.

Instance members like instance methods and instance variables can manipulate the state of the object, and define the state of an object, respectively. An "instance" or "object instance" or "object" is a real, true instantiation of the class. Your normal interaction with classes is to instantiate, or create, a new instance of the class. This new instantiation is an object.

Static members, on the other hand, are methods and state of the class itself. Sometimes, it's helpful to define special behavior that doesn't require an object instance.

Hopefully this all sounds familiar. If you are new to class based object oriented languages, try to think about classes as blueprints for the things in your program. Each thing, or object, has state (its properties) and behavior or abilities (its methods).

Fields

A simple Dart class is defined like this:


class InputField {
  int maxLength;
  String name;
}

This class, named InputField, has two fields: maxLength and name. These fields define the state of the object, or what data it can hold.

The default value of fields and variables is null. You may only assign a const (aka compile-time constant) value to a field initialized in the body of a class. For example:


class InputField {
  int maxSize = 20; // OK! a literal num is a constant
  String name;
}

You may not use a non-const value like this:

class InputField {
  int maxSize = 20;
  String name;

  // ERROR: expecting const expression
  Date initializeAt = new Date(); 
}

Dart is designed to support consistent initialization of objects. One cannot observe an object before its initialization is complete. To achieve that, the preferred way to initialize instance variables in Dart is via the constructor's initializer, where the parameters to the constructor are available and access to this is restricted. Initializing variables at the point of declaration was added as a convenience for simple cases.

NOTE: there is a discussion to simplify the syntax to allow you to set a non-const value to a field in the body of a class (like Date initializeAt = new Date()), by transparently moving the initialization down into the constructor. Stand by for more news on this.

An implicit getter is generated for each field. If the field is non-final, an implicit setter is also generated for each field.

For example, if you add a field like int maxSize, the following will be automatically added to the class:
  • int get maxSize() => this.maxSize;
  • void set maxSize(int x) => this.maxSize = x;
Warning: it is a compile time error to manually declare a getter or setter with the same name as a field or method!

Create an object, accessing fields

Once you have a class defined, you can create new instances:

InputField field = new InputField();

Setting and getting fields, via the automatically provided setters and getters, is easy:

field.maxSize = 30;
field.name = 'first-name';
print(field.name); // 'first-name'




This, of course, means that you can't create truly private fields for a class, because any field you define will receive a corresponding getting and (unless the field is final) a setter.

We have not yet discussed the visibility rules for Dart yet, because we have not introduced libraries (which create the boundaries for any private fields or methods.)

Constructors

As you saw in the code above, if you do not specify a constructor, a default constructor will be provided.

You can also specify your own constructors. For example:

class InputField {
  int maxSize;
  String name;

  InputField(this.maxLength, this.name);
}

This constructor that takes two parameters, and uses a cool Dart syntax shortcut to set the field values by matching the names of the parameters to the fields.

The above example constructor is equivalent to:

  InputField(maxSize, name) {
    this.maxSize = maxSize;
    this.name = name;
  }

But of course, that's the long way to do it. You can also mix the shorthand with a full constructor like this:

  InputField(this.maxSize, this.name) {
    renderComponent();
  }

If you define a constructor, no default zero-parameter constructor will be provided. This means, given this class definition...

class InputField {
  int maxSize;
  String name;

  InputField(this.maxLength, this.name);
}


... you will be unable to create a new object like this:

var field = new InputField(); // COMPILE TIME ERROR: no matching constructor

Dart does not allow for constructor overloading, which means you cannot have two constructors of the same name defined for the same class. Luckily, thanks to Dart's optional method arguments, this is less of a problem than you might think.

For example, using both optional arguments and default argument values:

class InputField {
  int maxSize;
  String name;
  
  InputField([this.type, this.maxSize = 20]);
}

The following instantiations will all work:

var field = new InputField();                 // works!
field.name;    // null

var field = new InputField(maxSize: 40);      // works!
field.maxSize; // 40

var field = new InputField('first-name', 20); // works!
field.name;    // first-name
field.maxSize; // 20

Named constructors

Another way to work around the lack of constructor overloading, as well as provide more clarity to your users of your class, is to use Dart's cool named constructors.

class InputField {
  int maxSize;
  String name;
  
  InputField([this.name, this.maxSize = 20]);
  
  InputField.Small(this.name) {
    this.maxSize = 10;
  }
}

You can instantiate an object using a named constructor like this:

InputField field = new InputField.Small('middle-initial');

Summary

Dart is a class based object oriented programming language. Classes have fields defined in the body of the class, and the fields can be initialized with compile-time constants or by values passed in from the constructor. Dart will generate a getter for each field, and if the field is non-null, a setter will also be generated.

If you do not specify a constructor, a default constructor will be provided for you. You cannot overload the constructor, but you can use either named constructors or optional arguments to provide options for initialization.

There is a lot more to cover with classes, this just skims the surface! We'll continue with classes in our next post.

Next Steps

Read more of my Dart posts, try Dart in your browser, browse the API docs, or file an issue request. Dart is getting ready, please send us your feedback!

Tuesday, February 7, 2012

Booleans in Dart

(This is part 11 in an ongoing series about Dart. Read part 10, Numbers in Dart.)

Intro

This post will cover what is true, and false, in Dart. If you are coming from JavaScript, hopefully this short explanation about booleans in Dart will come as welcome relief.

Dart has a formal boolean type, named bool. There are only two objects of type bool: true and false.

Examples:

bool registered = false;
var verified    = true;

True

The only value that is true is the boolean value true. Unlike JavaScript, other values such as 1 or non-null object are not treated as true.

False

In a boolean context, everything that is not true is converted to false. JavaScript has six other falsey values such as empty string, zero, null object, undefined, or NaN. In Dart, if an object is not the boolean value true, it is evaluated as false.

Boolean conversion

What happens when you write the following code?

var name = 'Bob';
if (name) {
  print("you have a name!");
}

In JavaScript, the above will print "you have a name!" because name is a non-null object. However, in Dart, the above will not print because name is converted to false due to boolean conversion.

The conversion rules to boolean are quite simple. The spec outlines the logic as:


(bool v){
    assert(v != null);
    return v === true;
}(o)

In other words, that which is not true is false. If the value is not equal to and of the same type as true, then the value is converted to false.

This means that the following code will work in JavaScript but will not work in Dart:

// this won't print in Dart
if (1) {
  print("1 is converted to false in Dart, as is everything that is not the value true");
}

Compensating

Coming from JavaScript, you may be wondering how to check for conditions such as empty string or zero. The long form checks will still work.

Check for empty string:

var fullName = '';
if (fullName.isEmpty()) {
  print("Please enter a full name");
}

Checking for zero:

var hitPoints = 0;
if (hitPoints == 0) {
  print("Uh oh! Looks like you died.");
}

Checking for null:

var unicorn = null;
if (unicorn == null) {
  print("You didn't wish hard enough. Wish harder.");
}

Checking for NaN:

var iMeantToDoThis = 0 / 0;
if (iMeantToDoThis.isNaN()) {
  print("it's all on purpose");
}

JavaScript puzzler

Dart also differs from JavaScript with respect to its treatment of boolean expressions.

In JavaScript, boolean expressions will resolve to the value of the object that stops the evaluation. For instance:

var yourName = 'Bob';
var myName = '';
var someName = yourName && myName;
console.log(someName);
// prints '' (empty string!)

Both yourName and myName will be evaluated, and because yourName is truthy the next value must be evaluated. myName is an empty string, which is falsey, but that doesn't matter to the value of someName. The last value that is evaluated is returned, so someName is set to an empty string.

Dart answer

Dart simplifies the behavior, because the result of a boolean expression is a boolean. Therefore, the following Dart code reads more logically.


main() {
  var firstName = 'Bob';
  var middleName = '';
  var name = firstName && middleName;
  print(name); // false, because boolean conversion turns
               // firstName into false
  print(name is bool); // true!
}

Assigning default values

Dart's simplified boolean handling also means a few tricks from JavaScript or Ruby don't work.

For example, assigning a default value in Ruby is as simple as:

# ruby code which assigns 25 to hit_points if it was null
hit_points ||= 25

Assigning a default value in JavaScript looks like:

// works in JavaScript
var hitPoints = hitPoints || 25;

However, because Dart boolean expressions return a boolean, and not the last value, we need to write:

var hitPoints = (hitPoints != null) ? hitPoints : 25;

This is a bit easier in Dart with functions, as default values are supported with function arguments:

grabADrink([size='pint']) {
  print(size);
}

grabADrink(); // prints 'pint'

Summary

Dart has a true boolean type named bool, with only two values: true and false (and, I suppose, null). Due to Dart's boolean conversion rules, all values except true are converted to false. Dart's boolean expressions return the actual boolean result, not the last encountered value.

Next Steps

Read more of my Dart posts, try Dart in your browser, browse the API docs, or file an issue request. You may also continue to Part 12: Classes in Dart, Part One.

Dart is getting ready, please send us your feedback!

Saturday, February 4, 2012

Numbers in Dart

(This is part 10 in an ongoing series about Dart. Check out part 9, a Dart SDK quick tour.)

Intro

It's time to look at numbers in Dart. Wait, where are you going? Come back, numbers are cool! This will be interesting, for reals. I can at least promise this will be easy and quick, for numbers in Dart aren't complicated. If you're compiling to JavaScript, though, there are a few edge cases (bugs?) so I encourage you to not skip this episode.

The Basics

Like everything else in Dart, numbers are objects. In fact, numbers are specified as interfaces.

Numbers come in two flavors in Dart: integers of arbitrary size and decimal 64 bit doubles as specified by the IEEE 754 standard. Both int and double are subinterfaces of num.
num is the supertype, int and double extend num.
The num interface defines the basics like +, -, /, and *. Num is where you'll also find abs(), ceil(), and floor(), among other methods.

Int

Integers are numbers without a decimal point. For example:

  var x = 1;
  print(x);
  print(x is num); // true in both Dart VM and JS
  print(x is int); // true in both Dart VM and JS

You may also specify hexadecimals with the 0x prefix.

  var hex = 0xDEADBEEF;
  print(hex); // 873735928559
  print(hex is num); // true in both Dart VM and JS
  print(hex is int); // true in both Dart VM and JS
  print(hex is! double); // true in Dart VM, FALSE when compiled to JavaScript

Alert: the last line in the above sample, the result is different between the Dart VM and Dart compiled to JavaScript. I've opened a bug to see if this is by design.

Integers can be of arbitrary precision. For example:

  var bigInt = 346538465234590347592847298346243459756895347698465298346583746592374652378462347569234765834765947569827346583465243765923847659234765928347659567398475647495873984572947593470294387093493456870849216348723763945678236420938467345762304958724596873045876234572037862934765294365243652548673456705673465273465246734506873456729457623845623456234650457693475603768922346728346256;
  print(bigInt); // correct in Dart VM, Infinity when compiled to JavaScript
  print(bigInt is num); // true in both Dart VM and JS
  print(bigInt is int); // true in both Dart VM and JS

While the above will work in the Dart VM, when compiled to JavaScript, the large number becomes Infinity.

Alert: JavaScript only supports +/- 9007199254740992 for integers and (at least in V8) 1.7976931348623157e+308 for Number.MAX_VALUE. I've opened a bug to discuss this, as the above code functions differently in Dart VM and JavaScript.

You might see Dart code that explicitly adds a + prefix to the number, though it isn't necessary.

  var positive = +12345;
  print(positive); // 12345
  print(positive is num); // true
  print(positive is int); // true

Double

If the number includes a decimal, it is a 64 bit double that follows IEEE 754 standard.

A simple example:

  var y = 1.1;
  print(y);
  print(y is num); // true in both Dart VM and JS
  print(y is double); // true in both Dart VM and JS

Exponent notation is supported:

  var exponents = 1.42e5;
  print(exponents); // prints 142000.0 in Dart VM, 142000 when compiled to JavaScript
  print(exponents is num); // true in both Dart VM and JS
  print(exponents is! int); // true in Dart VM, FALSE when compiled to JS
  print(exponents is double); // true in both Dart VM and JS

Here again you can see slight differences between what Dart VM and JavaScript will do. The generated JavaScript code looks like this:

  // generated JavaScript from Frog, as of 2012-02-04
  var exponents = (142000.0);
  print(exponents);
  print((typeof(exponents) == 'number'));
  print((typeof(exponents) != 'number')); // this is FALSE, see corresponding Dart code above
  print((typeof(exponents) == 'number'));

NaN

Dividing zero by zero will result in "not a number" in Dart, similar to JavaScript but unlike Java.

The num interface provides a simple check for isNaN().

  var nan = 0 / 0;
  print(nan.isNaN()); // true in both Dart VM and JS

Converting to and from a String

A common requirement is to turn a number into a String, or a String into a number.

Parsing a String to an int uses the Math class:

  var one = Math.parseInt("1");
  print(one);  // 1
  print(one is int);  // true

Parsing a String into a double is similar:

  var onePointOne = Math.parseDouble("1.1");
  print(onePointOne); // 1.1
  print(onePointOne is double); // true

Attempting to format a string that isn't a number will throw a BadNumberFormatException.

  try {
    var gook = Math.parseInt("foo");
  } catch(BadNumberFormatException e) {
    print(e); // BadNumberFormatException: 'foo'
  }
  

Going the other way, converting numbers to Strings, is also very easy thanks to toString().

  String oneAsString = 1.toString();
  print(oneAsString); // 1
  print(oneAsString is String);

For doubles, toStringAsFixed(int fractionDigits) will truncate the decimal digits:

  print(3.14159.toStringAsFixed(2)); // 3.14

Misc methods

A potentially unfamiliar change is the methods like ceil() and abs() are found on the num interface, and not in Math.

  // absolute value is a method on num
  print((-4).abs() == 4);
  
  // ceil and floor are also methods on num
  print(3.25.ceil() == 4.0);
  print(3.25.floor() == 3.0);
  
  // round will round up if .5 or greater, otherwise will round down
  print(3.25.round() == 3);
  print(3.89.round() == 4);
  print(3.5.round() == 4);
  print(3.49.round() == 3);
  
  // truncates the decimal digits
  print(3.24532.truncate() == 3.0);
  
  // convert a double to an int
  print(3.24.toInt() == 3);

Bit manipulating

The num interface also specifies the traditional bit shifting, and'ing, and or'ing. Some simple examples:

  print(3<<1 == 6); // 0011 << 1 == 0110
  print(3>>1 == 1); // 0011 >> 1 == 0001
  
  print((3|4) == 7); // 0011 | 0100 == 0111

Summary

Dart supplies a num interface with two subinterfaces: int and double. Integers are arbitrary precision, meaning they have arbitrary size. Doubles are 64 bit decimals that follow IEEE 754 spec.

Generally speaking, numbers function the same across native Dart and Dart compiled to JavaScript, but there are some subtle differences. For example, watch out for very large numbers and checking the difference between int and double in JavaScript (currently everything is checked against number in JavaScript from the Frog compiler.)

Next Steps

Read more of my Dart posts, try Dart in your browser, browse the API docs, or file an issue request. Dart is getting ready, please send us your feedback!

Disclaimer

I'm probably required to say that the views expressed in this blog are my own, and do not necessarily reflect those of my employer.