Emulating overloading

163 views
Skip to first unread message

Ian Mackenzie

unread,
Jul 18, 2015, 1:12:45 AM7/18/15
to mi...@dartlang.org
I'm looking at creating native Dart bindings to a C++ library that I've been developing, and one of the things I keep having to consider is how to deal with wrapping functions that in C++ are overloaded. In some cases I've been able to refactor the C++ library to replace overloaded functions with separately-named functions instead (in many cases increasing readability and simplifying the code!), but there are a few holdouts. For instance, in C++ I have code like

// C++

class Point3d
{
public:
    Point3d projectedOnto(const Axis3d& axis) const;
    Point3d projectedOnto(const Plane3d& plane) const;
};

I can think of a few options for making this work in Dart. First, just use an untyped argument:

// Dart

class Point3d {
  Point3d projectedOnto(datum) {...}
}

which is simplest but not type safe and doesn't indicate well to the caller what is required (maybe call the argument 'axisOrPlane'?). Alternately, have the Dart versions of Axis3d and Plane3d inherit from Datum3d or something, then have

// Dart

class Point3d {
  Point3d projectedOnto(Datum3d datum) {...}
}

which is more safe but still not clear to the user what is required (they have to figure out that Datum3d is extended/implemented by Axis3d and Plane3d). The simplest version is to probably just add suffixes, at least in Dart (maybe in C++ as well for consistency):

// Dart

class Point3d {
  Point3d projectedOntoAxis(Axis3d axis) {...}
  Point3d projectedOntoPlane(Plane3d plane) {...}
}

but that's getting pretty verbose and prevents the code from reading like a nice phrase. One more I thought of would be to use typed keyword arguments to a single method where it would hopefully be obvious that only one should be specified:

// Dart

class Point3d {
  Point3d projectedOnto({Axis3d axis, Plane3d plane}) {...}
}

although that is just as verbose to use as the suffixed version and a bit weirder (and I'm guessing less efficient).

Thoughts? Which version do you think is the cleanest/most readable/most idiomatic?

Jan Mostert

unread,
Jul 18, 2015, 3:22:53 AM7/18/15
to General Dart Discussion
I was also wondering whether method overloading is something in the Dart pipeline.
According to this  closed feature request, it seems no: https://github.com/dart-lang/sdk/issues/49

In the meantime as a workaround, I just use variable number of arguments or optional arguments.

--
Jan Vladimir Mostert
janvladimirmostert.com


--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

Daniel Joyce

unread,
Jul 18, 2015, 3:08:09 PM7/18/15
to General Dart Discussion
If Dart is not gonna give us method overloading, to prevent api bloat like this, then we need pattern matching/guards. Otherwise the choice is API bloat, or parameters are marked dynamic with impl using if-else matching.

Excuse my dart/scala lovechild

Point3D project(dynamic axisOrPlane) {
   pointOrPlane match {
      case axis:Axis => doAxisStuff(axis)
      case plane:Plane => doPlaneStuff(plane)
   }

}

Fall through switch case is a mistake waiting to happen 90% of the time, unless there is a pressing need for Duff's Device in Dart. I don't why every language adopted what is mostly a design bug in C. 

This API ballooning in dynamic languages like Dart/Python, or choosing to use if/else with instanceOf could be made nicer with pattern matchers and guards.

The other solution is double dispatch, which also verbose and requires editing multiple files if the api is ever expanded. Smalltalk uses this alot

But it seems to me, wouldn't it make more sense for axis or plane to know how to project points, and not point3D?

// Simply holds a point in space, should know nothing about projection
class Point3d{
}

class Plane3D{
    Point3d projectPoint(Point3D)
}

class Axis3D{
    Point3d projectPoint(Point3D)
}

But if you really want Point3D to handle projection, we then implement double dispatch, which narrows the type and lets it work. Used a lot in python and smalltalk.



class Point3D{
   Point3d projectOnto(dynamic axisOrPlane3D){
      axisOrPlane3D.projectPoint(this)
   }
}

If you want to provide some more safety, you could define a trait

trait PointProjector {
  projectPoint(Point3D)
}

class Plane3D extends PointProjector

class Axis3D extends PointProjector

class Point3D{
  Point3d projectOnto(PointProjector projector){
    projector.projectPoint(this)
  }
}

Of course, this causes types to explode, and to really round out the api, you need to add more methods to Axis and Plane, and keep them all in sync. The fact Dart has traits, which allow implementation of methods, would molify a lot of this.

But this is what you get with dynamic languages with anemic type systems. 

--
Daniel Joyce

The meek shall inherit the Earth, for the brave will be among the stars.

Rich Eakin

unread,
Jul 18, 2015, 5:52:44 PM7/18/15
to General Dart Discussion
I'm also keen to find better, more type safe ways to allow for operator overloading in math classes. For example, currently I'm using GLM math lib in C++, GLSL math for shaders, and I'd like the same for dart.

On Sat, Jul 18, 2015 at 3:07 PM, Daniel Joyce <daniel.a.joyce@gmail.com> wrote:
If Dart is not gonna give us method overloading, to prevent api bloat like this, then we need pattern matching/guards. Otherwise the choice is API bloat, or parameters are marked dynamic with impl using if-else matching.

Excuse my dart/scala lovechild

Point3D project(dynamic axisOrPlane) {
   pointOrPlane match {
      case axis:Axis => doAxisStuff(axis)
      case plane:Plane => doPlaneStuff(plane)
   }

}


I like this approach the most, it is keeping the implementation more readable than using instanceof() directly.  I also wonder, would it be possible for static analyzers to be able to detect if the var passed in won't be matched, and flag an error before runtime?  Anything a static analyzer could pick up would be ideal, considering we're in a dynamic language.

Daniel Joyce

unread,
Jul 18, 2015, 7:22:00 PM7/18/15
to General Dart Discussion

In Scala you can with sealed case classes catch the case at compile time where matches are not exhaustive.  Otherwise usually good practice to have a final guard to trap on a common super class or unknown case.

case _ => … unexpected edge case. Underscore is 'matches everything'


--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

Ian Mackenzie

unread,
Jul 19, 2015, 2:07:03 AM7/19/15
to mi...@dartlang.org
On Sunday, 19 July 2015 05:08:09 UTC+10, Daniel Joyce wrote
But it seems to me, wouldn't it make more sense for axis or plane to know how to project points, and not point3D?

// Simply holds a point in space, should know nothing about projection
class Point3d{
}

class Plane3D{
    Point3d projectPoint(Point3D)
}

class Axis3D{
    Point3d projectPoint(Point3D)
}

This would be one solution, but for a variety of reasons I prefer point.projectedOnto(plane). For one thing, I think it's closer to how someone would actually describe the operation. Also, I have lots of types of geometry that can be projected onto planes, not just points (triangles, line segments, curves, axes...). The way things are currently set up, which seems to work well, is that all of them inherit from a Transformable mixin (CRTP) class that implements a bunch of operations like projectedOnto(), rotatedAbout(), mirroredAbout(), scaledAbout(), translatedBy() etc.

Lots of messages in this thread talking about how the language could be modified to better support overloading, but I think that's a separate discussion. The lack of overloading in Dart does simplify things quite a bit (I've spent a ton of time dealing with overloading ambiguities and the like in C++!) so I'm happy to work with it - I was more curious about people's thoughts on the *current* best way to handle cases like the one I described.

Ian Mackenzie

unread,
Jul 19, 2015, 2:17:03 AM7/19/15
to mi...@dartlang.org
Also, for what it's worth, I'm less concerned about the *implementation* of the function (pattern matching etc.) since it will be redirecting a lot to native C++ calls, so it will be kind of hairy anyways. I'm more concerned about choosing the function *signature(s)* to ensure that the externally-facing API is as predictable, easy to understand and type-safe as possible. (Bonus points if the API translates well back to C++ so I can be as consistent as possible between C++ and Dart code.)

Adam Bender

unread,
Jul 20, 2015, 10:30:30 AM7/20/15
to mi...@dartlang.org
One really low-tech solution I have seen in some of the dart that is generated from Java looks like this:

Java:
double computeDistance(DoublePoint one, DoublePoint two)
int computeDistance(IntPoint 1, IntPoint2) 

Dart:

double computeDistanceDoublePointDoublePointReturnsDouble(DoublePoint one, DoublePoint two)
int computeDistanceIntPointIntPointReturnsInt(IntPoint one, IntPoint two)

The names are atrocious but are more or less guaranteed to be unique and are suitable for mechanical translation if needs be. Obviously, this makes a terrible human interface but if your bindings are generated no one has to know :) I think the axis/plane approach works if the semantics of the overload map nicely to a noun, but I have found that isnt always the case.

Daniel Joyce

unread,
Jul 20, 2015, 11:51:05 AM7/20/15
to mi...@dartlang.org
Just Say No to Hungarian Notation

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

Jim Trainor

unread,
Jul 20, 2015, 3:54:56 PM7/20/15
to mi...@dartlang.org
In C++ or Java when you have access only to a  base class representation of an object, yet you still want to dispatch polymorphically, you implement double dispatch.  Perhaps that a solution in this case.

Something like:

class Point3d {
  // Project anything
  Point3d projectedOnto(datum) { datum->projectPoint(this) }

  // Project something concrete.
  Point3d projectPlane(Plane3d aPlane) { ... }
}

class Plane3d {
  Point3d projectPoint(Point3d point) {
    point->projectPlane(this);
  }
}

... still doesn't give you analyzer warning on unsupported types but at least uses the machinery the language has to offer to resolve the type rather than doing it manually yourself in the projectedOnto call.


Ian Mackenzie

unread,
Jul 20, 2015, 10:37:53 PM7/20/15
to mi...@dartlang.org
On Tuesday, 21 July 2015 05:54:56 UTC+10, Jim Trainor wrote:
In C++ or Java when you have access only to a  base class representation of an object, yet you still want to dispatch polymorphically, you implement double dispatch.  Perhaps that a solution in this case.

I can't think of too many cases where you would not know at compile time whether you're projecting onto an axis or plane...I think I'm leaning towards the projectedOntoAxis()/projectedOntoPlane() route. From looking at a handful of core Dart classes, there are a handful of cases where a suffix is used to emulate overloading (e.g. File.writeAsBytes vs File.writeAsString) and at least one place where a common interface is added (Pattern, implemented by String and RegExp to allow calling both string.contains('some string') and string.contains(new RegExp('some pattern') in a type-safe way). There are a bunch more places in my code where a common interface would have to be added, though, and it's not clear what they should all be called (e.g. point.distanceTo(...) in C++ can take a point, axis, or plane argument - DistanceReference or something?). I also think adding extra interfaces is one more thing for and end user to have to look up and figure out. The suffixes are wordy, but they're simple, efficient (no dynamic dispatching) and clear. (Also, it makes it easy to add projectedOntoCurve() or projectedOntoSurface() in the future.)

I guess one counter-argument is that if Dart adds some form of overloading or union types in the future, an implementation based on an untyped argument or a common interface would be easier to migrate to one based on the new functionality since the function name itself wouldn't have to change...but I don't see either of those features showing up in the near future.
Reply all
Reply to author
Forward
0 new messages