2

My project involves the translation of Python 2.7 code to Dart code. In order to fully emulate all the features of Python data types I have created wrapper classes in Dart which extend the functionality of the primitive Dart data types to match the corresponding Python type. There are wrappers for all types such as $PyNum for numbers, $PyString for strings and so on. All is fine and the translated code works fine. For code is like:

def fib(n):
    if n <= 2:
        return 1
    else:
        return fib (n - 1) + fib (n - 2)

print (fib(36))

The corresponding generated Dart code is:

import 'lib/inbuilts.dart';
import 'dart:io';

fib(n) {
    if (n <= new $PyNum(2)) {
        return new $PyNum(1);
    } else {
        return (fib((n - new $PyNum(1))) + fib((n - new $PyNum(2))));
    }
}

main() {
    stdout.writeln(fib(new $PyNum(36)));
}

The code works fine but in codes like this where there is extreme recursions, the excessive number of wrapper objects created at each function instance is taking a severe toll on the running time of the code. For example the unwrapped Dart code:

import'dart:io';

fib(n) {
    if (n <= 2) {
        return 1;
    } else {
        return (fib((n - 1)) + fib((n - 2)));
    }
}

main() {
    stdout.writeln(fib(36));
}

This runs almost 15x faster than the wrapped code for obvious reasons. All calculations involving wrapped data types return a new instance of that Class. Its absolutely critical for me to emulate all features that Python provides in its data types via Dart and wrapping is the only thing that comes to my mind at the moment. I tried using singleton classes to make a common object for calculations but fails in recursive and threaded situations.

My $PyNum wrapper class is like this:

class $PyNum {
    num _value;

    $PyNum(value) {
        switch ($getType(value)) {
            case 6:
                _value = value;
                break;
            case 7:
                try {
                    _value = num.parse(value);
                } catch (ex) {
                    print("Invalid string literal for num parsing");
                    exit(1);
                }
                break;
            case 5:
                _value = value.value();
                break;
            default:
                throw "Invalid input for num conversion";
        }
    }

    value() => _value;
    toString() => _value.toString();

    operator +(other) => new $PyNum(_value + other.value());
    operator -(other) => new $PyNum(_value - other.value());
    operator *(other) => new $PyNum(_value * other.value());
    operator ~/(other) => new $PyNum(_value ~/ other.value());
    operator |(other) => new $PyNum(_value | other.value());
    operator &(other) => new $PyNum(_value & other.value());
    operator ^(other) => new $PyNum(_value ^ other.value());
    operator %(other) => new $PyNum(_value % other.value());
    operator <<(other) => new $PyNum(_value << other.value());
    operator >>(other) => new $PyNum(_value >> other.value());
    operator ==(other) {
        switch ($getType(other)) {
            case 6:
                return _value == other;
            case 5:
                return _value == other.value();
            default:
                return false;
        }
    }
    operator <(other) {
        switch ($getType(other)) {
            case 6:
                return _value < other;
            case 5:
                return _value < other.value();
            default:
                return true;
        }
    }
    operator >(other) => !(this < other) && (this != other);
    operator <=(other) => (this < other) || (this == other);
    operator >=(other) => (this > other) || (this == other);
}

$getType(variable) {
    if (variable is bool)
        return 0;
    else if (variable is $PyBool)
        return 1;
    else if (variable is $PyDict)
        return 2;
    else if (variable is $PyList)
        return 3;
    else if (variable is List)
        return 4;
    else if (variable is $PyNum)
        return 5;
    else if (variable is num)
        return 6;
    else if (variable is $PyString)
        return 7;
    else if (variable is $PyTuple)
        return 8;
    else
        return -1;
}

can const objects be made out off this class? I am not very sure how to do it exactly.

Is there any other way to efficiently do this and still be able to emulate all of Python's features? Any help is much appreciated!

Rahul De
  • 393
  • 3
  • 14

3 Answers3

4

I have a similar situation where I need to connect additional information with basic data types like String, int, double, ...
I didn't find a solution other than wrapping them.

What you can try is to optimize these wrapper classes by

  • making them const (with a const constructor
  • making the fields final where possible
  • others might be possible

EDIT
- You definitely want to get rid of all those switch statements.

  1. 0.134285 sec: without wrappers
  2. 0.645971 sec: with simplified constructor, operator <= (see below)
    using const constructor didn't make a notable difference (propably more important when converted to JS)
  3. 1.449707 sec: with simplified constructor
  4. 3.792590 sec: your code
class $PyNum2 {
  final num _value;

  const $PyNum2(this._value);

  factory $PyNum2.from(value) {
    switch ($getType2(value)) {
      case 6:
        return new $PyNum2(value);
        break;
      case 7:
        try {
          return new $PyNum2(num.parse(value));
        } catch (ex) {
          print("Invalid string literal for num parsing");
          exit(1);
        }
        break;
      case 5:
        return new $PyNum2(value.value());
        break;
      default:
        throw "Invalid input for num conversion";
    }
  }

  value() => _value;
  toString() => _value.toString();

  operator +(other) => new $PyNum2(_value + other.value());
  operator -(other) => new $PyNum2(_value - other.value());
  operator *(other) => new $PyNum2(_value * other.value());
  operator ~/(other) => new $PyNum2(_value ~/ other.value());
  operator %(other) => new $PyNum2(_value % other.value());
  operator ==(other) {
    switch ($getType2(other)) {
      case 6:
        return _value == other;
      case 5:
        return _value == other.value();
      default:
        return false;
    }
  }
  operator <(other) {
    switch ($getType2(other)) {
      case 6:
        return _value < other;
      case 5:
        return _value < other.value();
      default:
        return true;
    }
  }
  operator >(other) => !(this < other) && (this != other);
  operator <=(other) => this.value() <= other.value(); //(this < other) || (this == other);
  operator >=(other) => (this > other) || (this == other);
}

see also:

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I have updated the question with the Wrapper class I've wrote. Can this be converted to a const constructor? – Rahul De Mar 22 '14 at 12:44
  • I doubt it, but you could easily make a separate const constructor e.g. fromInt(this._value) – Alan Knight Mar 22 '14 at 16:38
  • But then I have to make _value final right? Its value gets modified during any mathematical operation which wont be possible if I make it final i guess? – Rahul De Mar 22 '14 at 17:26
  • I took just a brief look, but I had the impression you created a new object for the result anyway. I'll take a close look when I have more time. – Günter Zöchbauer Mar 22 '14 at 17:27
  • @Gunther, the code operator <=(other) => (this < other) || (this == other); is needed rather than the operator <=(other) => this.value() <= other.value(); code you suggested, as it goes into an infinite recursion in the <= operator overload. – Rahul De Apr 05 '14 at 04:25
  • It's a while that I played with your code. I just think you could probably create more specific types and omit those type checks in your constructor/methods/operators. I simplified your code so I could test the performance when these checks are removed, this wasn't intended to be a fully worked out solution.. You could use the factory constructor to check the value and return a more specific wrapper depending on the initial value. – Günter Zöchbauer Apr 05 '14 at 16:14
  • @GünterZöchbauer The factory constructor factory $PyNum2.from(value) {} you suggested is never being called due to the presence of the const $PyNum2(this._value); As a result whatever data I'm sending to the constructor is simply getting stored in the _value. For example new $PyNum("123") should store 123 after parsing the string but its storing the string "123". – Rahul De Apr 06 '14 at 05:41
  • That was just to show an example how you can split the simple case from the more complex case to avoid switch or if statements in the simple case. I didn't know enough about your concrete requirements to make use of it in the example. – Günter Zöchbauer Apr 06 '14 at 09:28
  • @GünterZöchbauer Found a workaround using the const constructor. Alls well. Thanks! – Rahul De Apr 06 '14 at 15:30
2

I haven't tried this, but how about never wrapping the object and for any method you need in Python implement it as a standalone function with a type-case instead. So anything that is common between the two would run at full speed. Methods only implemented in one python class would be pretty quick, and you'd only be doing a lot of type tests for things that were megamorphic in Python.

Or just write a Python interpreter in Dart.

Even in the examples you give you'd probably do a lot better if you used const objects instead of allocating a new PyNum every time.

Alan Knight
  • 2,759
  • 1
  • 15
  • 13
  • I have updated the question with the Wrapper class I've wrote. Can this be converted to a const constructor? – Rahul De Mar 22 '14 at 12:43
1

Sample code where the wrappers 6.3 times slower:

  1. Constructor only wrapps value. If you need consversion use anothers approach, eg. other additional constructors.
  2. Comparison operators splitted for reducing unnecessary types checks.
  3. Arithmetic operators improved, added type checks.
  4. Python types combined into group $PyType. This reduces types checks.

Unnecessary code removed from this sample.

import 'dart:io';

fib(n) {
  if (n <= 2) {
    return 1;
  } else {
    return (fib((n - 1)) + fib((n - 2)));
  }
}

fib2(n) {
  if (n <= new $PyNum(2)) {
    return new $PyNum(1);
  } else {
    return (fib2((n - new $PyNum(1))) + fib2((n - new $PyNum(2))));
  }
}

main() {
  measure("fib", () => stdout.writeln(fib(42)));
  measure("fib2", () => stdout.writeln(fib2(new $PyNum(42))));
}

void measure(String msg, f()) {
  var sw = new Stopwatch();
  sw.start();
  f();
  sw.stop();
  print("$msg: ${sw.elapsedMilliseconds}");
}

class $PyTypes {
  static const $PyTypes NUM = const $PyTypes("NUM");

  final String name;

  const $PyTypes(this.name);
}

abstract class $PyType {
  $PyTypes get pyType;
}

class $PyNum extends $PyType {
  final int value;

  $PyTypes get pyType => $PyTypes.NUM;

  $PyNum(this.value);

  operator +(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return new $PyNum(value + pyNum.value);
      }
    } else if (other is int) {
      return new $PyNum(value + other);
    }

    throw new ArgumentError("other: $other");
  }

  operator -(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return new $PyNum(value - pyNum.value);
      }
    } else if (other is int) {
      return new $PyNum(value - other);
    }

    throw new ArgumentError("other: $other");
  }

  operator ==(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value == pyNum.value;
      }
    } else if (other is int) {
      return value == other;
    }

    throw new ArgumentError("other: $other");
  }

  operator <(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value < pyNum.value;
      }
    } else if (other is int) {
      return value < other;
    }

    throw new ArgumentError("other: $other");
  }

  operator <=(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value <= pyNum.value;
      }
    } else if (other is int) {
      return value <= other;
    }

    throw new ArgumentError("other: $other");
  }

  String toString() => value.toString();
}
mezoni
  • 10,684
  • 4
  • 32
  • 54