HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Javascript velocity converter, fully chainable

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
javascriptvelocityfullyconverterchainable

Problem

I created this out of curiosity and it has some code duplication issues and the fact that sticking .yield() on the end of a call to a unit converter is pretty strange.

How would you improve it?

```
/**
* A very special velocity converter
* Designed mainly for a very natural syntax
* It allows full chainable conversion calls and maintains its on internal state.
*
* example:
* remotewind.convert(10).knots().to.mph().to.kmh().yield();
*
* Each unit has a yield "method" which returns the last value (the internal state)
*
* @param val
* @return Converter
*/
remotewind.convert = function(val) {

var val = val;

function Unit(){
this.yield = function(){
return val;
}
}

function Ms(ms){
val = ms;
this.to = {
kmh : function(){ return new Kmh(val * 3.6) },
mph : function(){ return new Mph(val * 2.2369362920544) },
knots: function(){ return new Knots(val * 1.943844492) }
}
}
Ms.prototype = new Unit();

function Kmh(kmh){
val = kmh;
this.to = {
ms : function(){ return new Ms(val * 0.277777778) },
mph : function(){ return new Mph(val * 0.621371192) },
knots: function(){ return new Knots(val * 0.539956803) }
}
}
Kmh.prototype = new Unit();

function Mph(mph){
val = mph;
this.to = {
ms : function(){ return new Ms(val * 0.44704) },
kmh : function(){ return new Kmh(val * 0.621371192) },
knots: function(){ return new Knots(val * 0.868976242) }
}
}
Mph.prototype = new Unit();

function Knots(kn){
val = kn;
this.to = {
ms : function(){ return new Ms(val * 0.514444444) },
mph : function(){ return new Mph(val * 1.150779448) },
knots: function(){ return new Knots(val * 1.852) }
}
}
Knots.prototype = new Unit();

function Converter(val){

Solution

Here's a fairly cryptic rewrite, focussing mostly on extensibility and minimal repetition. The trick I'm using is to pick one unit as our "base" unit from which all others are derived. The base-unit value (m/s in this case) is carried through the chain.

var convert = (function () {
  var conversions = {
    ms:    1, // use m/s as our base unit
    kmh:   3.6,
    mph:   2.23693629,
    knots: 1.94384449
    // feel free to add more 
  };

  function Unit(unit, ms) {
    this.value = ms * conversions[unit];
    this.to = {};
    for(var otherUnit in conversions) {
      (function (target) {
        this.to[target] = function () {
          return new Unit(target, ms);
        }
      }).call(this, otherUnit);
    }
  }

  Unit.prototype = {
    yield: function () {
      return this.valueOf();
    },

    toString: function () {
      return String(this.value);
    },

    valueOf: function () {
      return this.value;
    }
  };

  return function (value) {
    var units = {};
    for(var unit in conversions) {
      (function (unit) {
        units[unit] = function () {
          return new Unit(unit, value / conversions[unit]);
        };
      }(unit));
    }
    return units;
  } 
}());


Edit: Just for fun, here's an ultra-convoluted version that handles different measurement types. Couldn't get temperature conversions in there, as simple multiplication/division isn't enough to deal with C/K/F conversions. Boo.

Really though, the code's pretty dense, and I can't recommend coding like this. But again, this was just for fun.

Anyway, the usage would be something like

convert.speed(60).mph().to.kmh();  // ~97km/h
convert.distance(100).m().to.ft(); // ~330ft
convert.mass(1000).g().to.lb();    // ~2.2lb


And here's the hairy code:

var convert = (function () {
  var conversions = {
    speed: {
      ms:    1, // use m/s as our base unit
      kmh:   3.6,
      mph:   2.23693629,
      knots: 1.94384449
    },

    distance: {
      m:      1, // use meters as our base
      inches: 39.3700787402, // can't use "in" as that's a keyword. Darn.
      ft:     3.280839895,
      mi:     0.000621371192,
      nm:     0.000539956803 // nautical miles, not nanometers
    },

    mass: {
      g:  1, // use grams as our base
      lb: 0.002204622622,
      oz: 0.0352739619
    }
  };

  function Unit(type, unit, base) {
    this.value = base * conversions[type][unit];
    this.to = {};
    for(var otherUnit in conversions[type]) {
      (function (target) {
        this.to[target] = function () {
          return new Unit(type, target, base);
        }
      }).call(this, otherUnit);
    }
  }

  Unit.prototype = {
    yield: function () {
      return this.valueOf();
    },

    toString: function () {
      return String(this.value);
    },

    valueOf: function () {
      return this.value;
    }
  };

  // my god, it's full of scopes!
  var types = {};
  for(var type in conversions) {
    (function (type) {
      types[type] = function (value) {
        var units = {};
        for(var unit in conversions[type]) {
          (function (unit) {
            units[unit] = function () {
              return new Unit(type, unit, value / conversions[type][unit]);
            }
          }(unit));
        }
        return units;
      };
    }(type));
  }

  return types;
}());

Code Snippets

var convert = (function () {
  var conversions = {
    ms:    1, // use m/s as our base unit
    kmh:   3.6,
    mph:   2.23693629,
    knots: 1.94384449
    // feel free to add more 
  };

  function Unit(unit, ms) {
    this.value = ms * conversions[unit];
    this.to = {};
    for(var otherUnit in conversions) {
      (function (target) {
        this.to[target] = function () {
          return new Unit(target, ms);
        }
      }).call(this, otherUnit);
    }
  }

  Unit.prototype = {
    yield: function () {
      return this.valueOf();
    },

    toString: function () {
      return String(this.value);
    },

    valueOf: function () {
      return this.value;
    }
  };

  return function (value) {
    var units = {};
    for(var unit in conversions) {
      (function (unit) {
        units[unit] = function () {
          return new Unit(unit, value / conversions[unit]);
        };
      }(unit));
    }
    return units;
  } 
}());
convert.speed(60).mph().to.kmh();  // ~97km/h
convert.distance(100).m().to.ft(); // ~330ft
convert.mass(1000).g().to.lb();    // ~2.2lb
var convert = (function () {
  var conversions = {
    speed: {
      ms:    1, // use m/s as our base unit
      kmh:   3.6,
      mph:   2.23693629,
      knots: 1.94384449
    },

    distance: {
      m:      1, // use meters as our base
      inches: 39.3700787402, // can't use "in" as that's a keyword. Darn.
      ft:     3.280839895,
      mi:     0.000621371192,
      nm:     0.000539956803 // nautical miles, not nanometers
    },

    mass: {
      g:  1, // use grams as our base
      lb: 0.002204622622,
      oz: 0.0352739619
    }
  };

  function Unit(type, unit, base) {
    this.value = base * conversions[type][unit];
    this.to = {};
    for(var otherUnit in conversions[type]) {
      (function (target) {
        this.to[target] = function () {
          return new Unit(type, target, base);
        }
      }).call(this, otherUnit);
    }
  }

  Unit.prototype = {
    yield: function () {
      return this.valueOf();
    },

    toString: function () {
      return String(this.value);
    },

    valueOf: function () {
      return this.value;
    }
  };

  // my god, it's full of scopes!
  var types = {};
  for(var type in conversions) {
    (function (type) {
      types[type] = function (value) {
        var units = {};
        for(var unit in conversions[type]) {
          (function (unit) {
            units[unit] = function () {
              return new Unit(type, unit, value / conversions[type][unit]);
            }
          }(unit));
        }
        return units;
      };
    }(type));
  }

  return types;
}());

Context

StackExchange Code Review Q#35955, answer score: 4

Revisions (0)

No revisions yet.