2014-05-26

Equality and Dart

A month ago I asked a question on my blog about a piece of Dart code.

For the below class, what are at least two things wrong with the implementation of operator ==.

class Foo {
  int _a;
  int _b;

  int get a => _a;
  int get b => _b;

  ...

  bool operator ==(Foo other) {
    return other._a == this._a && other._b == this._b;
  }
}

A number of guesses were posted to the G+ discussion.

Context: designing types for reuse is tough

If Foo is only used within one library – so it should be named _Foo – the existing implementation might be totally fine.

But if Foo is…

  • Used a lot within a single library or package
  • Part of a code base with a lot of developers
  • Part of a public package
  • Serves as a base class

…then you have to think a lot harder about its implementation.

Special Equality

The parameter to operator == should always be Object.

Don’t believe me? Paste the code below into try.dartlang.org. I’ll wait.

void main() {
  var items = [0, 1, new Bar(5), 6, 7];
  print(items.contains(6));
}

class Bar {
  final int value;
  Bar(this.value);
  bool operator ==(Bar other) => value == other.value;
}

operator == is used in Iterable for contains, where, and many other methods. Map also uses == for its final check after finding matching hash codes. If there is any chance that an instance of your class will have equality checked with any other type – either directly or via very common Dart classes – you should not make assumptions about the argument to == except that it’s not null. Dart takes care of the null check for you.

This is easy to fix:

class Bar {
  final int value;
  Bar(this.value);
  bool operator ==(Object other) => 
    other is Bar && value == other.value;
}

General Equality

Just like relativistic physics, the special case is pretty straight forward. The general case is a bear.

I read that Special Relativity took Einstein 8 weeks where General Relativity took him 8 years, but I digress.

The special case is a black-and-white comparison: is the operand the same type as this or not. The general case introduces a pile of gray.

Dart has implicit interfaces. This means anyone can implement your Foo or Bar class.

Let’s say we fixed our original example for the special case:

class Foo {
  int _a;
  int _b;

  int get a => _a;
  int get b => _b;

  bool operator ==(Object other) {
    return other is Foo && other._a == this._a && other._b == this._b;
  }
}

What if someone implements Foo in another library.

import 'package:foolib/foolib.dart';

class MyFoo implements Foo {
  int get a => 41;
  int get b => 42;
}

Try doing if (foo == myFoo).

You’ll get a runtime error along the lines of MyFoo does not have instance property _a.

General Equality: simple fix

The simple fix is, well, simple. Avoid using private members of objects you don’t completely control. In the case of == that means any private members on other.

This problem goes beyond operator == and extends to any method anywhere in your library.

Consider the static distance method below.

class MyPoint {
  num _x, num _y;
  num get x => _x;
  num get y => _y;
  static double distance(MyPoint a, MyPoint b) {
    // should you access _x or _y here?
  }
}

See what I mean?

General Equality: simple mistakes

Avoiding private members is only part of the problem. Let’s look at MyPoint again.

class MyPoint {
  final num x, y;
  MyPoint(this.x, this.y);
  bool operator ==(Object other) =>
    other is MyPoint && other.x == x && other.y == y;
}

No private members. No type assumptions. We’re home free! Right?

Try this in try.dartlang.org:

class MyPoint {
  final num x, y;
  MyPoint(this.x, this.y);
  bool operator ==(Object other) =>
    other is MyPoint && other.x == x && other.y == y;
}

class MyPoint3 extends MyPoint {
  final num z;
  MyPoint3(num x, num y, this.z) : super(x, y);
  bool operator ==(Object other) =>
    other is MyPoint3 && other.x == x && other.y == y && other.z == z;
}

void main() {
  var mp = new MyPoint(1,2);
  var mp3 = new MyPoint3(1,2,3);

  print(mp == mp3); // true
  print(mp3 == mp); // false
}

You’ve just broken one of the three laws of equality.

Cue mass hysteria.

There are solutions to this problem, but they are not simple. We’ll have to bother Florian Loitsch for details.

…and I haven’t even gotten into implementing hashCode and handling mutable fields – here’s a taste.

Now what?

Keep calm, but be paranoid.

  • Be careful when implementing ==.
    • Make sure it’s something you want to sign up for.
    • Read up on implementing hashCode correctly.
    • If you’re class has mutable state, be afraid!
  • Always type the argument to operator == as Object and do a type check.
  • If your class is public, remember: any instance of Foo may not be my implementation of Foo.
    • Stick to the public interface.
    • Document what you consider equality.

An alternative: public class, private implementation

If you’ve dug around the Dart source code, you’ve likely seen this pattern:

abstract class SomeClass {
  int get a;
  String get b;
  bool someMethod();

  factory SomeClass(int a, String b) => new _SomeClassImpl(a, b);
}

Besides allowing us to efficiently support both the Dart virtual machine and compiling to Javascript, this pattern also allows one to detect random implementations of a type.

class _SomeClassImpl implements SomeClass {
  final int a;
  final String b;
  bool someMethod() ...

  _SomeClassImpl(this.a, this.b);

  // No impersonators allowed
  bool operator ==(Object other) =>
    other is _SomeClassImpl && other.a == a && other.b == b;
}

This pattern also allows you to lock down an object system where an arbitrary instance won’t fly.

Future and Zone are examples from the Dart SDK. You won’t get very far trying to implement or extend either of them – at least if you try to use them in place of their native implementations.

This post was edited and published using StackEdit. Freakin’ awesome. I may not ditch blogger after all.

2014-05-02

Delivered: Pop, Pop, Win! ported to StageXL


The Dart Editor ships with a Minesweeper-inspired game – Pop, Pop, Win! – that I originally hacked together in late 2012.
About a month ago, I made an open request for a Dart community member to port the Pop, Pop, Win! demo to StageXL – a powerful and flexible 2D graphics library for Dart written by Bernhard Pichler.

Alex Gann stepped up and delivered.
Once Dart v1.4 is released, you'll be able to explorer the source code and run the game from the welcome page of the Dart Editor.

In the mean time, you can browse the source on the GitHub source mirror.

I'm also keeping a running copy of the latest code on this GitHub page.

Alex did a bunch of work to optimize image and audio formats, including turning the audio files into a sprite. The download size of the entire game dropped by over a third: from 1.8MB to 1.1MB.

You'll also notice you can play games of arbitrary complexity – click on the logo for help and to switch difficulty.

Huge thanks to Alex for his hard work.

Huge thanks to Bernhard for the great StageXL package and for helping out Alex with a few feature requests during development.

2014-04-28

Method Piping - Dart Syntax Proposal


Update 1: I was delighted, but not totally surprised that someone else thought this was a good idea – 2 months ago. Patrice Chalin wrote up the proposal on his blog in February and filed a feature request. I've closed my request as a duplicate of his.

Disclaimer: This is not in anyway official. It's an idea. If you're excited about it, great. Please star the associated issue. I'm trying to judge interest in the Dart community.

One of the first Dart issues – the 13th, to be precise – was for C#-style extension methods.

This is not possible in a consistent and performant way due to Dart's type system.

But I'm pretty sure that most Dart developers that want C#-style extension methods would be happy with something much more simple.

Imagine the following code
...
class Foo {
  ...
  void fooMethod() {
    ...
    var items = [true, null, "Test", 42];
    var isEven = _isEven(countWhere(items, (e) => e is bool)); 
  }
}

_isEven could be an instance method on Foo, or a static method on Foo, or a top-level method in the current library.

countWhere could also be a instance or static method on Foo, or a top-level method on the current library or an imported library. It could also be a function defined in-line in fooMethod.

Now imagine an alternative syntax for the same behavior, with a new sugar in the form of |> – the method pipe.

...
class Foo {
  ...
  void fooMethod() {
    ...
    var items = [true, null, "Test", 42];
    var isEven = items |> countWhere((e) => e is bool) |> _isEven(); 
  }
}

Method pipe is simple. Anything of the form

g(f(expression))

Can be written as

expression |> f() |> g()

Basically, the expression on the left side of |> is passed as the first parameter of the function on the right of |>.

What about multiple arguments? Named arguments?

If you had function

int count(int a, int b, int c, {int d}) { ... }

You can call it as

count(1, 2, 3, d: 4);

or

1 |> count(2, 3, d: 4);

What if you try to call a method that has no default parameters? Or you try to call a field like a method?

The same thing that happens now.
int value = 5;
void run({String arg}) { ... }

// Today: Warning in the analyzer and errors at runtime
value(10);
run("test");

// Tomorrow: Warnings in the analyzer and errors at runtime;
10 |> value();
"test" |> run();

See where I'm going?

The method pipe syntax – |> – is completely up for debate. But I like the model. One could create and import a library full of top-level methods that consume IterablegroupBy, countWhere, wrapWithUnmodifiableList, etc and write.

var items = [1, 2, 3, 4, 5];

var groups = items |> groupBy((e) => ...);
var evenCount = items |> countWhere((e) => ...);
var readOnly = items |> wrapWithUnmodifiableList();

Thoughts?

Here's the feature request.

2014-04-26

There are (at least) two things wrong with this Dart code


Specifically with operator ==.

class Foo {
  int _a;
  int _b;

  int get a => _a;
  int get b => _b;

  ...

  bool operator ==(Foo other) {
    return other._a == this._a && other._b == this._b;
  }
}

Any guesses?

2014-03-29

Wanted: Hacker to port Pop, Pop, Win! to StageXL


tl;dr: Want an interesting Dart project? Port Pop, Pop, Win! to StageXL. If you do a good job, you're code will get into the Dart SDK.

When I wrote Pop, Pop, Win! I hacked together an HTML Canvas graphics library into bot_web. The library – bot_retained – did everything I needed, but I really haven't done much with it in over a year.

In the mean time, StageXL has been going nuts. It supports rendering to Canvas and WebGL. It supports Texture Packer. Bernhard has been keeping it fresh.

I'm actively trying to cleanup a bunch of little projects I've built over the last couple of years. Deprecating my own retained graphics library and moving folks towards StageXL seems like goodness.

Interested in doing the port?

The source code for PPW now lives in the Dart SDK. I'd suggest doing a copy-paste of the project into a new Git repository and starting there. The graphics bits are pretty well isolated. If you're pretty serious and can show that you've made a stab, I'm happy to offer help.

Dig in. Send me a link to your project on GitHub if you make progress or hit any bumps.

Happy hacking!

2014-03-28

Tracking changes to PART of the Dart SDK

For historical reasons, the Dart project uses Subversion for source control.

There are a few folks who would like to use Git for the project, including me, but it's not the top of our priority list.

You can get some of the benefits of Git by looking at or even cloning our GitHub mirror.

One of my favorite uses: tracking changes to an individual package.

Steps

1 - Navigate to a package of interest, say unittest.

2 - Click on the History link.

3 - Notice that kevmoo has been making a lot of changes lately.

This is a great way to see what changes have happened since the latest release of a package or just to track individual parts of the Dart project.

Nice, huh?

2014-03-25

Dart Package Devs: Put version constraints in your pubspec

tl;dr

  • Update: this guidance is specific Dart packages. I'll write about Dart apps soon.
  • Read about the Pub Versioning Philosophy.
  • If your package uses new features in the SDK or packages, make sure you set an inclusive minimum version constraint – >= X.Y.Z – to avoid breaking folks who have not yet upgraded.
  • Set exclusive maximum version constraints – <X.Y.Z – to make sure major releases of the SDK and packages don't break your code.
  • Understand how pre-v1.0 are versioned with regard to breaking changes.
  • Take a look at pubspec files in our SDK to understand how we handle versioning. Example: http package pubspec.yaml.

New features and minimum version constraints

Dart v1.3 is coming soon. String has learned a few tricks since v1.2, specifically:
  • String padLeft(int newLength, String padding)
  • String padLeft(int newLength, String padding)
  • String trimLeft()
  • String trimRight()
Awesome, right? I can imagine package authors doing the following:
  1. Download Dart 1.3 Editor and SDK
  2. Replace all internal hacks with new String functions.
  3. Release?
No! #3 should be: Update your pubspec.yaml.

pubspec.yaml content

In the Dart Editor

Setting the minimum SDK to 1.3.0 tells the pub tools and your users the users that a package requires at least v1.3 of the Dart SDK. Better to get an error from pub install or pub update than to get static warnings or runtime errors about missing functions.

You'll also notice more than just an inclusive minimum SDK constraint – >=1.3.0 – there's also an exclusive maximum SDK constraint – <2.0.0.

Our promise it to follow semantic versioning for the SDK and our shipped packages. The short version:
  • For a version X.Y.Z
  • Increases to Y indicate new features, but no breaking changes.
  • Increases to X indicate breaking changes.
It should be safe to set your maximum SDK version to be any release up to, but not including, v2.0.

Packages and pre-v1.0

The same applies to packages managed by the Dart team, like unittest, args, path, etc.

We have a number of packages that are pre-v1.0 – unittest is a great example.

unittest is one of our oldest and most used packages. We'd really like to lock it down and clean it up before releasing something we consider v1.0. 

If you look at the changelog for unittest, you'll see a number of breaking changes between v0.9.2 and v0.10.0.

For pre-v1.0 packages, we bump the versioning logic up a level, so for version 0.Y.Z, changes to Y are breaking and changes to Z are non-breaking.

Look again at the changelog for unittest. You'll see a number of features marked as DEPRECATED. These are things that will likely go away – stop working – in v0.11.0 of unittest.

If you want to make sure you code does not suddenly break, you should set a version constraint on unittest of something like >=0.10.0 <0.11.0.

Am I missing new hotness?

A lot of folks dislike setting maximum version constraints.

I always want to be running the new hotness!

I don't want to miss out on new features!

pub upgrade is your friend.

If you run pub upgrade from the console or within the Dart Editor, you'll see useful hints when there are newer versions of packages than what you currently specify.


You can then carefully update and check version constraints before you release.

If you want to visualize a complex package with many dependencies, check out pubviz.

Happy hacking!