Repository: gcanti/tcomb
Pull request #205 (2016-05-14 08:57:17)

add interface support to fromJSON
fromJSON handles new interface type


Patch:
diff --git a/lib/createExtend.js b/lib/createExtend.js
new file mode 100644
index 0000000..55715e0
--- /dev/null
+++ b/lib/createExtend.js
@@ -0,0 +1,32 @@
+var assert = require('./assert');
+var isObject = require('./isObject');
+var isArray = require('./isArray');
+var mixin = require('./mixin');
+var isStruct = require('./isStruct');
+var isInterface = require('./isInterface');
+
+function createExtend(ctor) {
+  return function extend(mixins, name) {
+    if (process.env.NODE_ENV !== 'production') {
+      assert(isArray(mixins) && mixins.every(function (x) {
+        return isObject(x) || isStruct(x) || isInterface(x);
+      }), function () { return 'Invalid argument mixins supplied to extend(mixins, name), expected an array of objects or structs'; });
+    }
+    var props = {};
+    var prototype = {};
+    mixins.forEach(function (x) {
+      if (isObject(x)) {
+        mixin(props, x);
+      }
+      else {
+        mixin(props, x.meta.props);
+        mixin(prototype, x.prototype);
+      }
+    });
+    var ret = ctor(props, name);
+    mixin(ret.prototype, prototype);
+    return ret;
+  };
+}
+
+module.exports = createExtend;
\ No newline at end of file
diff --git a/lib/extend.js b/lib/extend.js
deleted file mode 100644
index 3648cd3..0000000
--- a/lib/extend.js
+++ /dev/null
@@ -1,71 +0,0 @@
-var assert = require('./assert');
-var isFunction = require('./isFunction');
-var isArray = require('./isArray');
-var mixin = require('./mixin');
-var isStruct = require('./isStruct');
-var isInterface = require('./isInterface');
-var isObject = require('./isObject');
-var isType = require('./isType');
-var refinement = require('./refinement');
-
-function isRefinement(type) {
-  return isType(type) && type.meta.kind === 'subtype';
-}
-
-function getPredicates(type) {
-  return isRefinement(type) ?
-    [type.meta.predicate].concat(getPredicates(type.meta.type)) :
-    [];
-}
-
-function getUnrefinedType(type) {
-  return isRefinement(type) ?
-    getUnrefinedType(type.meta.type) :
-    type;
-}
-
-function decompose(type) {
-  return {
-    predicates: getPredicates(type),
-    unrefinedType: getUnrefinedType(type)
-  };
-}
-
-function compose(predicates, unrefinedType) {
-  return predicates.reduce(function (type, predicate) {
-    return refinement(type, predicate);
-  }, unrefinedType);
-}
-
-function getProps(type) {
-  return isObject(type) ? type : type.meta.props;
-}
-
-function pushAll(arr, elements) {
-  Array.prototype.push.apply(arr, elements);
-}
-
-function extend(combinator, mixins, name) {
-  if (process.env.NODE_ENV !== 'production') {
-    assert(isFunction(combinator), function () { return 'Invalid argument combinator supplied to extend(combinator, mixins, name), expected a function'; });
-    assert(isArray(mixins), function () { return 'Invalid argument mixins supplied to extend(combinator, mixins, name), expected an array'; });
-  }
-  var props = {};
-  var prototype = {};
-  var predicates = [];
-  mixins.forEach(function (x, i) {
-    var decomposition = decompose(x);
-    var unrefinedType = decomposition.unrefinedType;
-    if (process.env.NODE_ENV !== 'production') {
-      assert(isObject(unrefinedType) || isStruct(unrefinedType) || isInterface(unrefinedType), function () { return 'Invalid argument mixins[' + i + '] supplied to extend(combinator, mixins, name), expected an object, struct, interface or a refinement (of struct or interface)'; });
-    }
-    pushAll(predicates, decomposition.predicates);
-    mixin(props, getProps(unrefinedType));
-    mixin(prototype, unrefinedType.prototype);
-  });
-  var result = compose(predicates, combinator(props, name));
-  mixin(result.prototype, prototype);
-  return result;
-}
-
-module.exports = extend;
\ No newline at end of file
diff --git a/lib/fromJSON.js b/lib/fromJSON.js
index fd82465..cf2a454 100644
--- a/lib/fromJSON.js
+++ b/lib/fromJSON.js
@@ -49,6 +49,21 @@ function fromJSON(value, type) {
       }
       return new type(ret);
 
+    case 'interface' :
+      if (process.env.NODE_ENV !== 'production') {
+        assert(isObject(value), function () {
+          return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected an object)';
+        });
+      }
+      var interProps = type.meta.props;
+      ret = {};
+      for (k in interProps) {
+        if (interProps.hasOwnProperty(k)) {
+          ret[k] = fromJSON(value[k], interProps[k]);
+        }
+      }
+      return ret;
+
     case 'list' :
       if (process.env.NODE_ENV !== 'production') {
         assert(isArray(value), function () {
diff --git a/lib/func.js b/lib/func.js
index 67dc105..17e8810 100644
--- a/lib/func.js
+++ b/lib/func.js
@@ -10,7 +10,6 @@ var isBoolean = require('./isBoolean');
 var tuple = require('./tuple');
 var getFunctionName = require('./getFunctionName');
 var getTypeName = require('./getTypeName');
-var isType = require('./isType');
 
 function getDefaultName(domain, codomain) {
   return '(' + domain.map(getTypeName).join(', ') + ') => ' + getTypeName(codomain);
@@ -20,20 +19,6 @@ function isInstrumented(f) {
   return FunctionType.is(f) && isObject(f.instrumentation);
 }
 
-function getOptionalArgumentsIndex(types) {
-  var end = types.length;
-  var areAllMaybes = false;
-  for (var i = end - 1; i >= 0; i--) {
-    var type = types[i];
-    if (!isType(type) || type.meta.kind !== 'maybe') {
-      return (i + 1);
-    } else {
-      areAllMaybes = true;
-    }
-  }
-  return areAllMaybes ? 0 : end;
-}
-
 function func(domain, codomain, name) {
 
   domain = isArray(domain) ? domain : [domain]; // handle handy syntax for unary functions
@@ -45,8 +30,6 @@ function func(domain, codomain, name) {
   }
 
   var displayName = name || getDefaultName(domain, codomain);
-  var domainLength = domain.length;
-  var optionalArgumentsIndex = getOptionalArgumentsIndex(domain);
 
   function FuncType(value, curried) {
 
@@ -73,7 +56,7 @@ function func(domain, codomain, name) {
 
   FuncType.is = function (x) {
     return isInstrumented(x) &&
-      x.instrumentation.domain.length === domainLength &&
+      x.instrumentation.domain.length === domain.length &&
       x.instrumentation.domain.every(function (type, i) {
         return type === domain[i];
       }) &&
@@ -93,24 +76,20 @@ function func(domain, codomain, name) {
 
     function fn() {
       var args = Array.prototype.slice.call(arguments);
-      var argsLength = args.length;
+      var len = curried ?
+        args.length :
+        domain.length;
+      var argsType = tuple(domain.slice(0, len));
 
-      if (process.env.NODE_ENV !== 'production') {
-        // type-check arguments
-        var tupleLength = curried ? argsLength : Math.max(argsLength, optionalArgumentsIndex);
-        tuple(domain.slice(0, tupleLength), 'arguments of function ' + displayName)(args);
-      }
+      args = argsType(args); // type check arguments
 
-      if (curried && argsLength < domainLength) {
-        if (process.env.NODE_ENV !== 'production') {
-          assert(argsLength > 0, 'Invalid arguments.length = 0 for curried function ' + displayName);
-        }
-        var g = Function.prototype.bind.apply(f, [this].concat(args));
-        var newDomain = func(domain.slice(argsLength), codomain);
-        return newDomain.of(g, true);
+      if (len === domain.length) {
+        return create(codomain, f.apply(this, args));
       }
       else {
-        return create(codomain, f.apply(this, args));
+        var g = Function.prototype.bind.apply(f, [this].concat(args));
+        var newdomain = func(domain.slice(len), codomain);
+        return newdomain.of(g, curried);
       }
     }
 
@@ -131,5 +110,4 @@ function func(domain, codomain, name) {
 }
 
 func.getDefaultName = getDefaultName;
-func.getOptionalArgumentsIndex = getOptionalArgumentsIndex;
 module.exports = func;
diff --git a/lib/interface.js b/lib/interface.js
index a3d35b5..127e8be 100644
--- a/lib/interface.js
+++ b/lib/interface.js
@@ -2,41 +2,22 @@ var assert = require('./assert');
 var isTypeName = require('./isTypeName');
 var String = require('./String');
 var Function = require('./Function');
-var isBoolean = require('./isBoolean');
 var isObject = require('./isObject');
-var isNil = require('./isNil');
 var create = require('./create');
 var getTypeName = require('./getTypeName');
 var dict = require('./dict');
 var getDefaultInterfaceName = require('./getDefaultInterfaceName');
 var isIdentity = require('./isIdentity');
 var is = require('./is');
-var extend = require('./extend');
+var createExtend = require('./createExtend');
 
-function extendInterface(mixins, name) {
-  return extend(inter, mixins, name);
-}
+var extend = createExtend(inter);
 
-function getOptions(options) {
-  if (!isObject(options)) {
-    options = isNil(options) ? {} : { name: options };
-  }
-  if (!options.hasOwnProperty('strict')) {
-    options.strict = inter.strict;
-  }
-  return options;
-}
-
-function inter(props, options) {
-
-  options = getOptions(options);
-  var name = options.name;
-  var strict = options.strict;
+function inter(props, name) {
 
   if (process.env.NODE_ENV !== 'production') {
-    assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)'; });
-    assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to interface(props, [options]) combinator (expected a string)'; });
-    assert(isBoolean(strict), function () { return 'Invalid argument strict ' + assert.stringify(strict) + ' supplied to struct(props, [options]) combinator (expected a boolean)'; });
+    assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to interface(props, [name]) combinator (expected a dictionary String -> Type)'; });
+    assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to interface(props, [name]) combinator (expected a string)'; });
   }
 
   var displayName = name || getDefaultInterfaceName(props);
@@ -53,14 +34,6 @@ function inter(props, options) {
     if (process.env.NODE_ENV !== 'production') {
       path = path || [displayName];
       assert(isObject(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an object)'; });
-      // strictness
-      if (strict) {
-        for (k in value) {
-          if (value.hasOwnProperty(k)) {
-            assert(props.hasOwnProperty(k), function () { return 'Invalid additional prop "' + k + '" supplied to ' + path.join('/'); });
-          }
-        }
-      }
     }
 
     var idempotent = true;
@@ -89,8 +62,7 @@ function inter(props, options) {
     kind: 'interface',
     props: props,
     name: name,
-    identity: identity,
-    strict: strict
+    identity: identity
   };
 
   Interface.displayName = displayName;
@@ -108,18 +80,16 @@ function inter(props, options) {
   };
 
   Interface.update = function (instance, patch) {
-    return Interface(assert.update(instance, patch));
+    return new Interface(assert.update(instance, patch));
   };
 
   Interface.extend = function (xs, name) {
-    return extendInterface([Interface].concat(xs), name);
+    return extend([Interface].concat(xs), name);
   };
 
   return Interface;
 }
 
-inter.strict = false;
-inter.getOptions = getOptions;
 inter.getDefaultName = getDefaultInterfaceName;
-inter.extend = extendInterface;
+inter.extend = extend;
 module.exports = inter;
diff --git a/lib/mixin.js b/lib/mixin.js
index cc5041a..7e630bb 100644
--- a/lib/mixin.js
+++ b/lib/mixin.js
@@ -8,7 +8,7 @@ module.exports = function mixin(target, source, overwrite) {
     if (source.hasOwnProperty(k)) {
       if (overwrite !== true) {
         if (process.env.NODE_ENV !== 'production') {
-          assert(!target.hasOwnProperty(k) || target[k] === source[k], function () { return 'Invalid call to mixin(target, source, [overwrite]): cannot overwrite property "' + k + '" of target object'; });
+          assert(!target.hasOwnProperty(k), function () { return 'Invalid call to mixin(target, source, [overwrite]): cannot overwrite property "' + k + '" of target object'; });
         }
       }
       target[k] = source[k];
diff --git a/lib/struct.js b/lib/struct.js
index 4511a84..be70323 100644
--- a/lib/struct.js
+++ b/lib/struct.js
@@ -2,43 +2,24 @@ var assert = require('./assert');
 var isTypeName = require('./isTypeName');
 var String = require('./String');
 var Function = require('./Function');
-var isBoolean = require('./isBoolean');
 var isObject = require('./isObject');
-var isNil = require('./isNil');
 var create = require('./create');
 var getTypeName = require('./getTypeName');
 var dict = require('./dict');
 var getDefaultInterfaceName = require('./getDefaultInterfaceName');
-var extend = require('./extend');
+var createExtend = require('./createExtend');
 
 function getDefaultName(props) {
   return 'Struct' + getDefaultInterfaceName(props);
 }
 
-function extendStruct(mixins, name) {
-  return extend(struct, mixins, name);
-}
+var extend = createExtend(struct);
 
-function getOptions(options) {
-  if (!isObject(options)) {
-    options = isNil(options) ? {} : { name: options };
-  }
-  if (!options.hasOwnProperty('strict')) {
-    options.strict = struct.strict;
-  }
-  return options;
-}
-
-function struct(props, options) {
-
-  options = getOptions(options);
-  var name = options.name;
-  var strict = options.strict;
+function struct(props, name) {
 
   if (process.env.NODE_ENV !== 'production') {
-    assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to struct(props, [options]) combinator (expected a dictionary String -> Type)'; });
-    assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to struct(props, [options]) combinator (expected a string)'; });
-    assert(isBoolean(strict), function () { return 'Invalid argument strict ' + assert.stringify(strict) + ' supplied to struct(props, [options]) combinator (expected a boolean)'; });
+    assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to struct(props, [name]) combinator (expected a dictionary String -> Type)'; });
+    assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to struct(props, [name]) combinator (expected a string)'; });
   }
 
   var displayName = name || getDefaultName(props);
@@ -52,14 +33,6 @@ function struct(props, options) {
     if (process.env.NODE_ENV !== 'production') {
       path = path || [displayName];
       assert(isObject(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an object)'; });
-      // strictness
-      if (strict) {
-        for (k in value) {
-          if (value.hasOwnProperty(k)) {
-            assert(props.hasOwnProperty(k), function () { return 'Invalid additional prop "' + k + '" supplied to ' + path.join('/'); });
-          }
-        }
-      }
     }
 
     if (!(this instanceof Struct)) { // `new` is optional
@@ -84,8 +57,7 @@ function struct(props, options) {
     kind: 'struct',
     props: props,
     name: name,
-    identity: false,
-    strict: strict
+    identity: false
   };
 
   Struct.displayName = displayName;
@@ -99,14 +71,12 @@ function struct(props, options) {
   };
 
   Struct.extend = function (xs, name) {
-    return extendStruct([Struct].concat(xs), name);
+    return extend([Struct].concat(xs), name);
   };
 
   return Struct;
 }
 
-struct.strict = false;
-struct.getOptions = getOptions;
 struct.getDefaultName = getDefaultName;
-struct.extend = extendStruct;
+struct.extend = extend;
 module.exports = struct;
diff --git a/lib/update.js b/lib/update.js
index f0e3d24..ef1debb 100644
--- a/lib/update.js
+++ b/lib/update.js
@@ -18,39 +18,22 @@ function getShallowCopy(x) {
   return x;
 }
 
-function isCommand(k) {
-  return update.commands.hasOwnProperty(k);
-}
-
-function getCommand(k) {
-  return update.commands[k];
-}
-
 function update(instance, patch) {
 
   if (process.env.NODE_ENV !== 'production') {
     assert(isObject(patch), function () { return 'Invalid argument patch ' + assert.stringify(patch) + ' supplied to function update(instance, patch): expected an object containing commands'; });
   }
 
-  var value = instance;
+  var value = getShallowCopy(instance);
   var isChanged = false;
-  var newValue;
   for (var k in patch) {
     if (patch.hasOwnProperty(k)) {
-      if (isCommand(k)) {
-        newValue = getCommand(k)(patch[k], value);
-        if (newValue !== instance) {
-          isChanged = true;
-          value = newValue;
-        } else {
-          value = instance;
-        }
+      if (update.commands.hasOwnProperty(k)) {
+        value = update.commands[k](patch[k], value);
+        isChanged = true;
       }
       else {
-        if (value === instance) {
-          value = getShallowCopy(instance);
-        }
-        newValue = update(value[k], patch[k]);
+        var newValue = update(value[k], patch[k]);
         isChanged = isChanged || ( newValue !== value[k] );
         value[k] = newValue;
       }
@@ -73,10 +56,7 @@ function $push(elements, arr) {
     assert(isArray(elements), 'Invalid argument elements supplied to immutability helper { $push: elements } (expected an array)');
     assert(isArray(arr), 'Invalid value supplied to immutability helper $push (expected an array)');
   }
-  if (elements.length > 0) {
-    return arr.concat(elements);
-  }
-  return arr;
+  return arr.concat(elements);
 }
 
 function $remove(keys, obj) {
@@ -84,11 +64,8 @@ function $remove(keys, obj) {
     assert(isArray(keys), 'Invalid argument keys supplied to immutability helper { $remove: keys } (expected an array)');
     assert(isObject(obj), 'Invalid value supplied to immutability helper $remove (expected an object)');
   }
-  if (keys.length > 0) {
-    obj = getShallowCopy(obj);
-    for (var i = 0, len = keys.length; i < len; i++ ) {
-      delete obj[keys[i]];
-    }
+  for (var i = 0, len = keys.length; i < len; i++ ) {
+    delete obj[keys[i]];
   }
   return obj;
 }
@@ -102,14 +79,10 @@ function $splice(splices, arr) {
     assert(isArray(splices) && splices.every(isArray), 'Invalid argument splices supplied to immutability helper { $splice: splices } (expected an array of arrays)');
     assert(isArray(arr), 'Invalid value supplied to immutability helper $splice (expected an array)');
   }
-  if (splices.length > 0) {
-    arr = getShallowCopy(arr);
-    return splices.reduce(function (acc, splice) {
-      acc.splice.apply(acc, splice);
-      return acc;
-    }, arr);
-  }
-  return arr;
+  return splices.reduce(function (acc, splice) {
+    acc.splice.apply(acc, splice);
+    return acc;
+  }, arr);
 }
 
 function $swap(config, arr) {
@@ -119,12 +92,9 @@ function $swap(config, arr) {
     assert(isNumber(config.to), 'Invalid argument config.to supplied to immutability helper { $swap: config } (expected a number)');
     assert(isArray(arr), 'Invalid value supplied to immutability helper $swap (expected an array)');
   }
-  if (config.from !== config.to) {
-    arr = getShallowCopy(arr);
-    var element = arr[config.to];
-    arr[config.to] = arr[config.from];
-    arr[config.from] = element;
-  }
+  var element = arr[config.to];
+  arr[config.to] = arr[config.from];
+  arr[config.from] = element;
   return arr;
 }
 
@@ -133,22 +103,11 @@ function $unshift(elements, arr) {
     assert(isArray(elements), 'Invalid argument elements supplied to immutability helper {$unshift: elements} (expected an array)');
     assert(isArray(arr), 'Invalid value supplied to immutability helper $unshift (expected an array)');
   }
-  if (elements.length > 0) {
-    return elements.concat(arr);
-  }
-  return arr;
+  return elements.concat(arr);
 }
 
-function $merge(whatToMerge, value) {
-  var isChanged = false;
-  var result = getShallowCopy(value);
-  for (var k in whatToMerge) {
-    if (whatToMerge.hasOwnProperty(k)) {
-      result[k] = whatToMerge[k];
-      isChanged = isChanged || ( result[k] !== value[k] );
-    }
-  }
-  return isChanged ? result : value;
+function $merge(obj, value) {
+  return mixin(mixin({}, value), obj, true);
 }
 
 update.commands = {


Test patch:
diff --git a/test/es6.js b/test/es6.js
index 1a30fc2..0e2ecd9 100644
--- a/test/es6.js
+++ b/test/es6.js
@@ -80,7 +80,7 @@ describe('ES6 classes', function () {
     assert.deepEqual(f(c), 'Class');
     throwsWithMessage(function () {
       f(1);
-    }, '[tcomb] Invalid value 1 supplied to arguments of function (Class) => String/0: Class');
+    }, '[tcomb] Invalid value 1 supplied to [Class]/0: Class');
   });
 
 });
diff --git a/test/extend.js b/test/extend.js
deleted file mode 100644
index 20cd485..0000000
--- a/test/extend.js
+++ /dev/null
@@ -1,229 +0,0 @@
-/* globals describe, it */
-var assert = require('assert');
-var t = require('../index');
-var throwsWithMessage = require('./util').throwsWithMessage;
-
-var Point = t.struct({
-  x: t.Number,
-  y: t.Number
-}, 'Point');
-
-var PointInterface = t.inter({
-  x: t.Number,
-  y: t.Number
-});
-
-describe('extend(combinator, mixins, [name])', function () {
-
-  it('should throw if used with wrong arguments', function () {
-    throwsWithMessage(function () {
-      t.struct.extend();
-    }, '[tcomb] Invalid argument mixins supplied to extend(combinator, mixins, name), expected an array');
-    throwsWithMessage(function () {
-      t.struct.extend([1]);
-    }, '[tcomb] Invalid argument mixins[0] supplied to extend(combinator, mixins, name), expected an object, struct, interface or a refinement (of struct or interface)');
-  });
-
-  describe('struct.extend(mixins, [name])', function () {
-
-    it('should handle an array of mixins', function () {
-      var Point3D = t.struct.extend([Point, { z: t.Number }], 'Point3D');
-      assert.deepEqual(Point3D.meta.name, 'Point3D', 'name');
-      assert.deepEqual(Point3D.meta.props.x, t.Number, 'x');
-      assert.deepEqual(Point3D.meta.props.y, t.Number, 'y');
-      assert.deepEqual(Point3D.meta.props.z, t.Number, 'z');
-    });
-
-  });
-
-  describe('<Struct>.extend(mixins, [name])', function () {
-
-    it('should extend an existing struct', function () {
-      var Point3D = Point.extend({ z: t.Number }, 'Point3D');
-      assert.strictEqual(Point3D.meta.name, 'Point3D', 'name');
-      assert.strictEqual(Point3D.meta.props.x, t.Number, 'x');
-      assert.strictEqual(Point3D.meta.props.y, t.Number, 'y');
-      assert.strictEqual(Point3D.meta.props.z, t.Number, 'z');
-    });
-
-    it('should handle an array of mixins', function () {
-      var Type = t.struct({ a: t.String }, 'Type');
-      var Mixin = [{ b: t.Number, c: t.Boolean }];
-      var NewType = Type.extend(Mixin, 'NewType');
-      assert.strictEqual(t.getTypeName(NewType), 'NewType');
-      assert.strictEqual(NewType.meta.props.a, t.String);
-      assert.strictEqual(NewType.meta.props.b, t.Number);
-      assert.strictEqual(NewType.meta.props.c, t.Boolean);
-    });
-
-    it('should handle an array of objects / structs / interfaces as mixins', function () {
-      var A = t.struct({ a: t.String }, 'A');
-      var B = t.struct({ b: t.String }, 'B');
-      var C = t.struct({ c: t.String }, 'C');
-      var MixinD = { d: t.String };
-      var I = t.inter({ i: t.String }, 'I');
-      I.prototype.imethod = function() {};
-      var E = A.extend([B, C, MixinD, I]);
-      assert.deepEqual(E.meta.props, {
-        a: t.String,
-        b: t.String,
-        c: t.String,
-        d: t.String,
-        i: t.String
-      });
-      assert.equal(E.prototype.imethod === I.prototype.imethod, true);
-    });
-
-    it('should support prototypal inheritance', function () {
-      var Rectangle = t.struct({
-        w: t.Number,
-        h: t.Number
-      }, 'Rectangle');
-      Rectangle.prototype.area = function () {
-        return this.w * this.h;
-      };
-      var Cube = Rectangle.extend({
-        l: t.Number
-      });
-      Cube.prototype.volume = function () {
-        return this.area() * this.l;
-      };
-
-      assert(typeof Rectangle.prototype.area === 'function');
-      assert(typeof Cube.prototype.area === 'function');
-      assert(undefined === Rectangle.prototype.volume);
-      assert(typeof Cube.prototype.volume === 'function');
-      assert(Cube.prototype.constructor === Cube);
-
-      var c = new Cube({w: 2, h: 2, l: 2});
-      assert.deepEqual(c.volume(), 8);
-    });
-
-    it('should support multiple prototypal inheritance', function () {
-      var A = t.struct({ a: t.Str }, 'A');
-      A.prototype.amethod = function () {};
-      var B = t.struct({ b: t.Str }, 'B');
-      B.prototype.bmethod = function () {};
-      var C = t.struct({ c: t.Str }, 'C');
-      C.prototype.cmethod = function () {};
-      var Z = C.extend([A, B], 'Z');
-      var z = new Z({ a: 'a', b: 'b', c: 'c' });
-      assert.strictEqual(Z.meta.name, 'Z');
-      assert.strictEqual(t.Function.is(z.cmethod), true);
-      assert.strictEqual(t.Function.is(z.bmethod), true);
-      assert.strictEqual(t.Function.is(z.amethod), true);
-    });
-
-  });
-
-  describe('interface.extend', function () {
-    it('should handle an array of mixins', function () {
-      var Point3DInterface = t.inter.extend([PointInterface, { z: t.Number }], 'Point3DInterface');
-      assert.strictEqual(Point3DInterface.meta.name, 'Point3DInterface', 'name');
-      assert.strictEqual(Point3DInterface.meta.props.x, t.Number, 'x');
-      assert.strictEqual(Point3DInterface.meta.props.y, t.Number, 'y');
-      assert.strictEqual(Point3DInterface.meta.props.z, t.Number, 'z');
-    });
-  });
-
-  describe('<Interface>.extend(xs, [name])', function () {
-
-    it('should extend an existing interface', function () {
-      var Point3DInterface = PointInterface.extend({ z: t.Number }, 'Point3DInterface');
-      assert.strictEqual(Point3DInterface.meta.name, 'Point3DInterface', 'name');
-      assert.strictEqual(Point3DInterface.meta.props.x, t.Number, 'x');
-      assert.strictEqual(Point3DInterface.meta.props.y, t.Number, 'y');
-      assert.strictEqual(Point3DInterface.meta.props.z, t.Number, 'z');
-    });
-
-    it('should handle an array as argument', function () {
-      var Mixin = [{ b: t.Number, c: t.Boolean }];
-      var NewInterface = PointInterface.extend(Mixin, 'NewInterface');
-      assert.strictEqual(t.getTypeName(NewInterface), 'NewInterface');
-      assert.strictEqual(NewInterface.meta.props.x, t.Number);
-      assert.strictEqual(NewInterface.meta.props.y, t.Number);
-      assert.strictEqual(NewInterface.meta.props.b, t.Number);
-      assert.strictEqual(NewInterface.meta.props.c, t.Boolean);
-    });
-
-  });
-
-  describe('refinements', function () {
-
-    it('should extend refinements', function () {
-      var PersonContact = t.struct({
-        cell: t.maybe(t.String),
-        work: t.maybe(t.String)
-      });
-
-      function atLeastOne (x) {
-        return Boolean(x.cell || x.work);
-      }
-
-      var PersonContactStrict = t.refinement(PersonContact, atLeastOne);
-
-      var mixin = {
-        role: t.maybe(t.String),
-        department: t.maybe(t.String)
-      };
-
-      function atLeastOne$(x) {
-        return Boolean(x.role || x.department);
-      }
-
-      var Role = t.struct(mixin);
-      Role.prototype.getRole = function () {
-        return this.role + ' ' + this.department;
-      };
-      var RoleStrict = t.refinement(Role, atLeastOne$);
-
-      var CompanyContact = t.struct.extend([PersonContactStrict, mixin]);
-      assert.strictEqual(CompanyContact.meta.kind, 'subtype');
-      assert.strictEqual(CompanyContact.meta.predicate, atLeastOne);
-      assert.strictEqual(CompanyContact.meta.type.meta.kind, 'struct');
-      assert.deepEqual(Object.keys(CompanyContact.meta.type.meta.props), [
-        'cell',
-        'work',
-        'role',
-        'department'
-      ]);
-
-      CompanyContact = t.struct.extend([PersonContactStrict, t.inter(mixin)]);
-      assert.strictEqual(CompanyContact.meta.kind, 'subtype');
-      assert.strictEqual(CompanyContact.meta.predicate, atLeastOne);
-      assert.strictEqual(CompanyContact.meta.type.meta.kind, 'struct');
-      assert.deepEqual(Object.keys(CompanyContact.meta.type.meta.props), [
-        'cell',
-        'work',
-        'role',
-        'department'
-      ]);
-
-      CompanyContact = t.inter.extend([PersonContactStrict, mixin]);
-      assert.strictEqual(CompanyContact.meta.kind, 'subtype');
-      assert.strictEqual(CompanyContact.meta.predicate, atLeastOne);
-      assert.strictEqual(CompanyContact.meta.type.meta.kind, 'interface');
-      assert.deepEqual(Object.keys(CompanyContact.meta.type.meta.props), [
-        'cell',
-        'work',
-        'role',
-        'department'
-      ]);
-
-      var RolePersonContact = PersonContact.extend(RoleStrict);
-      assert.strictEqual(RolePersonContact.meta.kind, 'subtype');
-      assert.strictEqual(RolePersonContact.meta.predicate, atLeastOne$);
-      assert.strictEqual(RolePersonContact.meta.type.meta.kind, 'struct');
-      assert.deepEqual(Object.keys(RolePersonContact.meta.type.meta.props), [
-        'cell',
-        'work',
-        'role',
-        'department'
-      ]);
-      assert.strictEqual(RolePersonContact.prototype.getRole, Role.prototype.getRole);
-
-    });
-
-  });
-
-});
diff --git a/test/fromJSON.js b/test/fromJSON.js
index 56f895e..1c27209 100644
--- a/test/fromJSON.js
+++ b/test/fromJSON.js
@@ -68,6 +68,24 @@ describe('fromJSON', function () {
     assert.deepEqual(actual, source);
   });
 
+  it('should handle interface', function () {
+    var MyType = t.interface({
+      name: t.String,
+      birthDate: MyDate
+    }, 'MyType');
+
+    util.throwsWithMessage(function () {
+      fromJSON(null, MyType);
+    }, '[tcomb] Invalid argument value null supplied to fromJSON(value, type) (expected an object)');
+
+    var source = {
+      name: 'Giulio',
+      birthDate: date
+    };
+    var json = jsonify(source);
+    assert.deepEqual(fromJSON(json, MyType), source);
+  });
+
   it('should handle list', function () {
     var MyType = t.list(MyDate, 'MyType');
 
diff --git a/test/func.js b/test/func.js
index 388721f..681d314 100644
--- a/test/func.js
+++ b/test/func.js
@@ -36,40 +36,14 @@ describe('t.func(domain, codomain, [name])', function () {
 
       util.throwsWithMessage(function () {
         sum(1, 2, 3);
-      }, '[tcomb] Invalid value [\n  1,\n  2,\n  3\n] supplied to arguments of function (Number, Number) => Number (expected an array of length 2)');
+      }, '[tcomb] Invalid value [\n  1,\n  2,\n  3\n] supplied to [Number, Number] (expected an array of length 2)');
 
       util.throwsWithMessage(function () {
         sum('a', 2);
-      }, '[tcomb] Invalid value "a" supplied to arguments of function (Number, Number) => Number/0: Number');
+      }, '[tcomb] Invalid value "a" supplied to [Number, Number]/0: Number');
 
     });
 
-    it('should handle optional arguments', function () {
-      function Class(a) {
-        this.a = a;
-      }
-      assert.equal(t.func.getOptionalArgumentsIndex([t.Number, t.Number]), 2);
-      assert.equal(t.func.getOptionalArgumentsIndex([t.Number, t.maybe(t.Number)]), 1);
-      assert.equal(t.func.getOptionalArgumentsIndex([t.maybe(t.Number)]), 0);
-      assert.equal(t.func.getOptionalArgumentsIndex([t.Number, t.maybe(t.Number), t.Number]), 3);
-      assert.equal(t.func.getOptionalArgumentsIndex([]), 0);
-      assert.equal(t.func.getOptionalArgumentsIndex([Class]), 1);
-      assert.equal(t.func.getOptionalArgumentsIndex([Class, t.maybe(t.Number)]), 1);
-
-      var T = t.func([t.Number, t.maybe(t.Number)], t.Number);
-      var sum = T.of(function (a, b) {
-        if (t.Nil.is(b)) {
-          b = 2;
-        }
-        return a + b;
-      });
-      assert.equal(sum(1), 3);
-      assert.equal(sum(1, 2), 3);
-      util.throwsWithMessage(function () {
-        sum(1, 'a');
-      }, '[tcomb] Invalid value "a" supplied to arguments of function (Number, ?Number) => Number/1: ?Number');
-    });
-
     it('should check the return value', function () {
 
       var T = t.func([t.Number, t.Number], t.Number);
@@ -116,16 +90,6 @@ describe('t.func(domain, codomain, [name])', function () {
 
   describe('currying', function () {
 
-    it('should throw if no arguments are passed in', function () {
-      var Type = t.func([t.Number, t.Number, t.Number], t.Number);
-      var sum = Type(function (a, b, c) {
-        return a + b + c;
-      }, true);
-      util.throwsWithMessage(function () {
-        sum();
-      }, '[tcomb] Invalid arguments.length = 0 for curried function (Number, Number, Number) => Number');
-    });
-
     it('should curry functions', function () {
       var Type = t.func([t.Number, t.Number, t.Number], t.Number);
       var sum = Type(function (a, b, c) {
@@ -153,12 +117,12 @@ describe('t.func(domain, codomain, [name])', function () {
 
       util.throwsWithMessage(function () {
         sum('a');
-      }, '[tcomb] Invalid value "a" supplied to arguments of function (Number, Number) => Number/0: Number');
+      }, '[tcomb] Invalid value "a" supplied to [Number]/0: Number');
 
       util.throwsWithMessage(function () {
         var sum1 = sum(1);
         sum1('a');
-      }, '[tcomb] Invalid value "a" supplied to arguments of function (Number) => Number/0: Number');
+      }, '[tcomb] Invalid value "a" supplied to [Number]/0: Number');
 
     });
 
@@ -174,7 +138,7 @@ describe('t.func(domain, codomain, [name])', function () {
       assert.deepEqual(sum(1, 2, 3), 6);
       util.throwsWithMessage(function () {
         sum(1, 2);
-      }, '[tcomb] Invalid value [\n  1,\n  2\n] supplied to arguments of function (Number, Number, Number) => Number (expected an array of length 3)');
+      }, '[tcomb] Invalid value [\n  1,\n  2\n] supplied to [Number, Number, Number] (expected an array of length 3)');
     });
 
   });
diff --git a/test/interface.js b/test/interface.js
index 3325cf7..a125112 100644
--- a/test/interface.js
+++ b/test/interface.js
@@ -20,42 +20,15 @@ describe('t.interface(props, [name])', function () {
   describe('combinator', function () {
 
     it('should throw if used with wrong arguments', function () {
-
       throwsWithMessage(function () {
         t.inter();
-      }, '[tcomb] Invalid argument props undefined supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)');
-
+      }, '[tcomb] Invalid argument props undefined supplied to interface(props, [name]) combinator (expected a dictionary String -> Type)');
       throwsWithMessage(function () {
         t.inter({a: null});
-      }, '[tcomb] Invalid argument props {\n  "a": null\n} supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)');
-
+      }, '[tcomb] Invalid argument props {\n  "a": null\n} supplied to interface(props, [name]) combinator (expected a dictionary String -> Type)');
       throwsWithMessage(function () {
         t.inter({}, 1);
-      }, '[tcomb] Invalid argument name 1 supplied to interface(props, [options]) combinator (expected a string)');
-
-      throwsWithMessage(function () {
-        t.inter({}, {strict: 1});
-      }, '[tcomb] Invalid argument strict 1 supplied to struct(props, [options]) combinator (expected a boolean)');
-
-    });
-
-  });
-
-  describe('inter.getOptions', function () {
-
-    it('should handle options', function () {
-      assert.deepEqual(t.inter.getOptions(), { strict: false });
-      assert.deepEqual(t.inter.getOptions({}), { strict: false });
-      assert.deepEqual(t.inter.getOptions('Person'), { strict: false, name: 'Person' });
-      assert.deepEqual(t.inter.getOptions({ strict: false }), { strict: false });
-      assert.deepEqual(t.inter.getOptions({ strict: true }), { strict: true });
-      t.inter.strict = true;
-      assert.deepEqual(t.inter.getOptions(), { strict: true });
-      assert.deepEqual(t.inter.getOptions({}), { strict: true });
-      assert.deepEqual(t.inter.getOptions('Person'), { strict: true, name: 'Person' });
-      assert.deepEqual(t.inter.getOptions({ strict: false }), { strict: false });
-      assert.deepEqual(t.inter.getOptions({ strict: true }), { strict: true });
-      t.inter.strict = false;
+      }, '[tcomb] Invalid argument name 1 supplied to interface(props, [name]) combinator (expected a string)');
     });
 
   });
@@ -89,47 +62,16 @@ describe('t.interface(props, [name])', function () {
       assert.equal(Point.is(hi.point), true);
     });
 
-    it('should handle strict option', function () {
-      var Person = t.inter({
-        name: t.String,
-        surname: t.maybe(t.String)
-      }, { name: 'Person', strict: true });
-
-      assert.strictEqual(Person.meta.name, 'Person');
-      assert.strictEqual(Person.meta.strict, true);
-
-      throwsWithMessage(function () {
-        Person({ name: 'Giulio', age: 42 });
-      }, '[tcomb] Invalid additional prop "age" supplied to Person');
-
-      throwsWithMessage(function () {
-        // simulating a typo on a maybe prop
-        Person({ name: 'Giulio', sur: 'Canti' });
-      }, '[tcomb] Invalid additional prop "sur" supplied to Person');
-
-      function Input(name) {
-        this.name = name;
-      }
-      Input.prototype.method = function () {};
-      assert.doesNotThrow(function () {
-        Person(new Input('Giulio'));
-      });
-    });
-
-    it('should handle global strict option', function () {
-      t.inter.strict = true;
-      var Person = t.inter({
-        name: t.String,
-        surname: t.maybe(t.String)
-      }, 'Person');
+  });
 
-      throwsWithMessage(function () {
-        Person({ name: 'Giulio', age: 42 });
-        t.inter.strict = false;
-      }, '[tcomb] Invalid additional prop "age" supplied to Person');
-      t.inter.strict = false;
+  describe('interface.extend', function () {
+    it('should handle an array of mixins', function () {
+      var Point3DInterface = t.inter.extend([PointInterface, {z: t.Number}], 'Point3DInterface');
+      assert.deepEqual(Point3DInterface.meta.name, 'Point3DInterface', 'name');
+      assert.deepEqual(Point3DInterface.meta.props.x, t.Number, 'x');
+      assert.deepEqual(Point3DInterface.meta.props.y, t.Number, 'y');
+      assert.deepEqual(Point3DInterface.meta.props.z, t.Number, 'z');
     });
-
   });
 
   describe('#is(x)', function () {
@@ -175,4 +117,27 @@ describe('t.interface(props, [name])', function () {
 
   });
 
+  describe('#extend(xs, [name])', function () {
+
+    it('should extend an existing interface', function () {
+      var Point3DInterface = PointInterface.extend({z: t.Number}, 'Point3DInterface');
+      assert.deepEqual(Point3DInterface.meta.name, 'Point3DInterface', 'name');
+      assert.deepEqual(Point3DInterface.meta.props.x, t.Number, 'x');
+      assert.deepEqual(Point3DInterface.meta.props.y, t.Number, 'y');
+      assert.deepEqual(Point3DInterface.meta.props.z, t.Number, 'z');
+    });
+
+    it('should handle an array as argument', function () {
+      var Mixin = [{b: t.Number, c: t.Boolean}];
+      var NewInterface = PointInterface.extend(Mixin, 'NewInterface');
+      assert.deepEqual(t.getTypeName(NewInterface), 'NewInterface');
+      assert.deepEqual(NewInterface.meta.props.x, t.Number);
+      assert.deepEqual(NewInterface.meta.props.y, t.Number);
+      assert.deepEqual(NewInterface.meta.props.b, t.Number);
+      assert.deepEqual(NewInterface.meta.props.c, t.Boolean);
+    });
+
+  });
+
+
 });
diff --git a/test/struct.js b/test/struct.js
index a0a6e29..bfd6b2c 100644
--- a/test/struct.js
+++ b/test/struct.js
@@ -16,41 +16,32 @@ describe('t.struct(props, [name])', function () {
 
       throwsWithMessage(function () {
         t.struct();
-      }, '[tcomb] Invalid argument props undefined supplied to struct(props, [options]) combinator (expected a dictionary String -> Type)');
+      }, '[tcomb] Invalid argument props undefined supplied to struct(props, [name]) combinator (expected a dictionary String -> Type)');
 
       throwsWithMessage(function () {
         t.struct({a: null});
-      }, '[tcomb] Invalid argument props {\n  "a": null\n} supplied to struct(props, [options]) combinator (expected a dictionary String -> Type)');
+      }, '[tcomb] Invalid argument props {\n  "a": null\n} supplied to struct(props, [name]) combinator (expected a dictionary String -> Type)');
 
       throwsWithMessage(function () {
         t.struct({}, 1);
-      }, '[tcomb] Invalid argument name 1 supplied to struct(props, [options]) combinator (expected a string)');
-
-      throwsWithMessage(function () {
-        t.struct({}, {strict: 1});
-      }, '[tcomb] Invalid argument strict 1 supplied to struct(props, [options]) combinator (expected a boolean)');
+      }, '[tcomb] Invalid argument name 1 supplied to struct(props, [name]) combinator (expected a string)');
 
     });
 
   });
 
-  describe('struct.getOptions', function () {
-
-    it('should handle options', function () {
-      assert.deepEqual(t.struct.getOptions(), { strict: false });
-      assert.deepEqual(t.struct.getOptions({}), { strict: false });
-      assert.deepEqual(t.struct.getOptions('Person'), { strict: false, name: 'Person' });
-      assert.deepEqual(t.struct.getOptions({ strict: false }), { strict: false });
-      assert.deepEqual(t.struct.getOptions({ strict: true }), { strict: true });
-      t.struct.strict = true;
-      assert.deepEqual(t.struct.getOptions(), { strict: true });
-      assert.deepEqual(t.struct.getOptions({}), { strict: true });
-      assert.deepEqual(t.struct.getOptions('Person'), { strict: true, name: 'Person' });
-      assert.deepEqual(t.struct.getOptions({ strict: false }), { strict: false });
-      assert.deepEqual(t.struct.getOptions({ strict: true }), { strict: true });
-      t.struct.strict = false;
+  describe('struct.extend', function () {
+    it('should handle an array of mixins', function () {
+      var Point = t.struct({
+        x: t.Number,
+        y: t.Number
+      }, 'Point');
+      var Point3D = t.struct.extend([Point, {z: t.Number}], 'Point3D');
+      assert.deepEqual(Point3D.meta.name, 'Point3D', 'name');
+      assert.deepEqual(Point3D.meta.props.x, t.Number, 'x');
+      assert.deepEqual(Point3D.meta.props.y, t.Number, 'y');
+      assert.deepEqual(Point3D.meta.props.z, t.Number, 'z');
     });
-
   });
 
   describe('constructor', function () {
@@ -73,61 +64,6 @@ describe('t.struct(props, [name])', function () {
       }, '[tcomb] Invalid value undefined supplied to Struct{x: Number, y: Number}/x: Number');
     });
 
-    it('should handle strict option', function () {
-      var Person = t.struct({
-        name: t.String,
-        surname: t.maybe(t.String)
-      }, { name: 'Person', strict: true });
-
-      assert.strictEqual(Person.meta.name, 'Person');
-      assert.strictEqual(Person.meta.strict, true);
-
-      throwsWithMessage(function () {
-        new Person({ name: 'Giulio', age: 42 });
-      }, '[tcomb] Invalid additional prop "age" supplied to Person');
-
-      throwsWithMessage(function () {
-        // simulating a typo on a maybe prop
-        new Person({ name: 'Giulio', sur: 'Canti' });
-      }, '[tcomb] Invalid additional prop "sur" supplied to Person');
-
-      function Input(name) {
-        this.name = name;
-      }
-      Input.prototype.method = function () {};
-      assert.doesNotThrow(function () {
-        new Person(new Input('Giulio'));
-      });
-    });
-
-    it('should handle global strict option', function () {
-      t.struct.strict = true;
-      var Person = t.struct({
-        name: t.String,
-        surname: t.maybe(t.String)
-      }, 'Person');
-
-      throwsWithMessage(function () {
-        new Person({ name: 'Giulio', age: 42 });
-        t.struct.strict = false;
-      }, '[tcomb] Invalid additional prop "age" supplied to Person');
-      t.struct.strict = false;
-    });
-
-    it('how to handle unions of structs when global strict is true', function () {
-      var T1 = t.struct({}, { strict: false });
-      var T2 = t.struct({}, { strict: false });
-      var U = t.union([T1, T2]);
-      U.dispatch = function (x) {
-        switch (x.type) {
-          case '1': return T1;
-          case '2': return T2;
-        }
-      };
-      var x = U({type: '2'});
-      assert.strictEqual(x instanceof T2, true);
-    });
-
   });
 
   describe('#is(x)', function () {
@@ -153,4 +89,88 @@ describe('t.struct(props, [name])', function () {
 
   });
 
+  describe('#extend(xs, [name])', function () {
+
+    it('should extend an existing struct', function () {
+      var Point = t.struct({
+        x: t.Number,
+        y: t.Number
+      }, 'Point');
+      var Point3D = Point.extend({z: t.Number}, 'Point3D');
+      assert.deepEqual(Point3D.meta.name, 'Point3D', 'name');
+      assert.deepEqual(Point3D.meta.props.x, t.Number, 'x');
+      assert.deepEqual(Point3D.meta.props.y, t.Number, 'y');
+      assert.deepEqual(Point3D.meta.props.z, t.Number, 'z');
+    });
+
+    it('should handle an array as argument', function () {
+      var Type = t.struct({a: t.String}, 'Type');
+      var Mixin = [{b: t.Number, c: t.Boolean}];
+      var NewType = Type.extend(Mixin, 'NewType');
+      assert.deepEqual(t.getTypeName(NewType), 'NewType');
+      assert.deepEqual(NewType.meta.props.a, t.String);
+      assert.deepEqual(NewType.meta.props.b, t.Number);
+      assert.deepEqual(NewType.meta.props.c, t.Boolean);
+    });
+
+    it('should handle an array of objects or structs or interfaces as argument', function () {
+      var A = t.struct({a: t.String}, 'A');
+      var B = t.struct({b: t.String}, 'B');
+      var C = t.struct({c: t.String}, 'C');
+      var MixinD = {d: t.String};
+      var I = t.inter({i: t.String}, 'I');
+      I.prototype.imethod = function() {};
+      var E = A.extend([B, C, MixinD, I]);
+      assert.deepEqual(E.meta.props, {
+        a: t.String,
+        b: t.String,
+        c: t.String,
+        d: t.String,
+        i: t.String
+      });
+      assert.equal(E.prototype.imethod === I.prototype.imethod, true);
+    });
+
+    it('should support prototypal inheritance', function () {
+      var Rectangle = t.struct({
+        w: t.Number,
+        h: t.Number
+      }, 'Rectangle');
+      Rectangle.prototype.area = function () {
+        return this.w * this.h;
+      };
+      var Cube = Rectangle.extend({
+        l: t.Number
+      });
+      Cube.prototype.volume = function () {
+        return this.area() * this.l;
+      };
+
+      assert(typeof Rectangle.prototype.area === 'function');
+      assert(typeof Cube.prototype.area === 'function');
+      assert(undefined === Rectangle.prototype.volume);
+      assert(typeof Cube.prototype.volume === 'function');
+      assert(Cube.prototype.constructor === Cube);
+
+      var c = new Cube({w: 2, h: 2, l: 2});
+      assert.deepEqual(c.volume(), 8);
+    });
+
+    it('should support multiple prototypal inheritance', function () {
+      var A = t.struct({ a: t.Str }, 'A');
+      A.prototype.amethod = function () {};
+      var B = t.struct({ b: t.Str }, 'B');
+      B.prototype.bmethod = function () {};
+      var C = t.struct({ c: t.Str }, 'C');
+      C.prototype.cmethod = function () {};
+      var Z = C.extend([A, B], 'Z');
+      var z = new Z({ a: 'a', b: 'b', c: 'c' });
+      assert.strictEqual(Z.meta.name, 'Z');
+      assert.strictEqual(t.Function.is(z.cmethod), true);
+      assert.strictEqual(t.Function.is(z.bmethod), true);
+      assert.strictEqual(t.Function.is(z.amethod), true);
+    });
+
+  });
+
 });
diff --git a/test/update.js b/test/update.js
index e500294..190947b 100644
--- a/test/update.js
+++ b/test/update.js
@@ -20,104 +20,127 @@ describe('t.update(instance, patch)', function () {
     }, '[tcomb] Invalid argument patch undefined supplied to function update(instance, patch): expected an object containing commands');
   });
 
-  describe('$set', function () {
+  it('should handle $set command', function () {
+    var instance = 1;
+    var actual = update(instance, {$set: 2});
+    assert.deepEqual(actual, 2);
+    instance = [1, 2, 3];
+    actual = update(instance, {1: {'$set': 4}});
+    assert.deepEqual(instance, [1, 2, 3]);
+    assert.deepEqual(actual, [1, 4, 3]);
+  });
 
-    it('numbers', function () {
-      assert.equal(update(1, {$set: 2}), 2);
-    });
+  it('$set and null value, fix #65', function () {
+    var NullStruct = t.struct({a: t.Number, b: t.maybe(t.Number)});
+    var instance = new NullStruct({a: 1});
+    var updated = update(instance, {b: {$set: 2}});
+    assert.deepEqual(instance, {a: 1, b: null});
+    assert.deepEqual(updated, {a: 1, b: 2});
+  });
 
-    it('arrays', function () {
-      var arr1 = [1, 2, 3];
-      var arr2 = update(arr1, { 1: { '$set': 4 } });
-      assert.deepEqual(arr1, [1, 2, 3]);
-      assert.deepEqual(arr2, [1, 4, 3]);
-    });
+  it('should handle $apply command', function () {
+    var $apply = function (n) { return n + 1; };
+    var instance = 1;
+    var actual = update(instance, {$apply: $apply});
+    assert.deepEqual(actual, 2);
+    instance = [1, 2, 3];
+    actual = update(instance, {1: {'$apply': $apply}});
+    assert.deepEqual(instance, [1, 2, 3]);
+    assert.deepEqual(actual, [1, 3, 3]);
+  });
 
-    // issue #65
-    it('$set and null value', function () {
-      var T = t.struct({a: t.Number, b: t.maybe(t.Number)});
-      var instance = new T({a: 1});
-      var updated = update(instance, {b: {$set: 2}});
-      assert.deepEqual(instance, {a: 1, b: undefined});
-      assert.deepEqual(updated, {a: 1, b: 2});
-    });
+  it('should $apply dates', function () {
+    var instance = new Date(1973, 10, 30);
+    var actual = update(instance, { $apply: function (date) { return date.getFullYear(); } });
+    assert.equal(actual, 1973);
+  });
 
-    it('tuples', function () {
-      var instance = Tuple(['a', 1]);
-      var updated = update(instance, {0: {$set: 'b'}});
-      assert.deepEqual(updated, ['b', 1]);
-    });
+  it('should $apply regexps', function () {
+    var instance = /a/;
+    var actual = update(instance, { $apply: function (regexp) { return regexp.source; } });
+    assert.equal(actual, 'a');
+  });
 
-    it('structs', function () {
-      var instance = new Point({x: 0, y: 1});
-      var updated = update(instance, {x: {$set: 1}});
-      assert.deepEqual(instance, {x: 0, y: 1});
-      assert.deepEqual(updated, {x: 1, y: 1});
-    });
+  it('should handle $unshift command', function () {
+    var actual = update([1, 2, 3], {'$unshift': [4]});
+    assert.deepEqual(actual, [4, 1, 2, 3]);
+    actual = update([1, 2, 3], {'$unshift': [4, 5]});
+    assert.deepEqual(actual, [4, 5, 1, 2, 3]);
+    actual = update([1, 2, 3], {'$unshift': [[4, 5]]});
+    assert.deepEqual(actual, [[4, 5], 1, 2, 3]);
+  });
 
-    it('lists', function () {
-      var instance = List([1, 2, 3, 4]);
-      var updated = update(instance, {2: {$set: 5}});
-      assert.deepEqual(updated, [1, 2, 5, 4]);
-    });
+  it('should handle $push command', function () {
+    var actual = update([1, 2, 3], {'$push': [4]});
+    assert.deepEqual(actual, [1, 2, 3, 4]);
+    actual = update([1, 2, 3], {'$push': [4, 5]});
+    assert.deepEqual(actual, [1, 2, 3, 4, 5]);
+    actual = update([1, 2, 3], {'$push': [[4, 5]]});
+    assert.deepEqual(actual, [1, 2, 3, [4, 5]]);
+  });
 
-    it('dicts', function () {
-      var instance = Dict({a: 1, b: 2});
-      var updated = update(instance, {a: {$set: 2}});
-      assert.deepEqual(updated, {a: 2, b: 2});
-      updated = update(instance, {c: {$set: 3}});
-      assert.deepEqual(updated, {a: 1, b: 2, c: 3});
-    });
+  it('should handle $splice command', function () {
+    var instance = [1, 2, {a: [12, 17, 15]}];
+    var actual = update(instance, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
+    assert.deepEqual(instance, [1, 2, {a: [12, 17, 15]}]);
+    assert.deepEqual(actual, [1, 2, {a: [12, 13, 14, 15]}]);
+  });
 
-    it('should not change the reference when no changes occurs', function () {
-      var p1 = {x: 0, y: 1};
-      var p2 = update(p1, {});
-      assert.strictEqual(p1, p2);
-      var p3 = update(p1, {x: {$set: 0}});
-      assert.strictEqual(p1, p3);
+  it('should handle bad $splice command', function () {
+    var instance = [1, 2, {a: [12, 17, 15]}];
+    throwsWithMessage(function () {
+      update(instance, {2: {a: {$splice: [1, 1, 13, 14]}}});
+    }, '[tcomb] Invalid argument splices supplied to immutability helper { $splice: splices } (expected an array of arrays)');
+  });
 
-      var n1 = {a: {b: {c: 1}}};
-      var n2 = t.update(n1, {a: {b: {c: {$set: 1}}}});
-      assert.strictEqual(n1, n2);
+  it('should handle $remove command', function () {
+    var instance = {a: 1, b: 2};
+    var actual = update(instance, {'$remove': ['a']});
+    assert.deepEqual(instance, {a: 1, b: 2});
+    assert.deepEqual(actual, {b: 2});
+  });
 
-      var m1 = {a: 1, b: 2};
-      var m2 = t.update(m1, {a: {$set: 2}, b: {$set: 2}});
-      assert.equal(m1 === m2, false);
-    });
+  it('should handle $swap command', function () {
+    var instance = [1, 2, 3, 4];
+    var actual = update(instance, {'$swap': {from: 1, to: 2}});
+    assert.deepEqual(instance, [1, 2, 3, 4]);
+    assert.deepEqual(actual, [1, 3, 2, 4]);
+  });
 
+  it('can $merge and $remove at once', function () {
+    var instance = {a: [1, 2], b: true};
+    var actual = update(instance, { $merge: {a: [1, 2, 3] }, $remove: ['b'] });
+    assert.deepEqual(instance, {a: [1, 2], b: true});
+    assert.deepEqual(actual, {a: [1, 2, 3]});
   });
 
-  describe('$apply', function () {
+  it('should not change the reference when no changes occurs', function () {
+    var p1 = {x: 0, y: 1};
+    var p2 = update(p1, {});
+    assert.strictEqual(p1, p2);
+    var p3 = update(p1, {x: {$set: 0}});
+    assert.strictEqual(p1, p3);
 
-    it('numbers', function () {
-      var $apply = function (n) { return n + 1; };
-      var instance = 1;
-      var actual = update(instance, {$apply: $apply});
-      assert.deepEqual(actual, 2);
-    });
+    var n1 = {a: {b: {c: 1}}};
+    var n2 = t.update(n1, {a: {b: {c: {$set: 1}}}});
+    assert.strictEqual(n1, n2);
 
-    it('arrays', function () {
-      var $apply = function (n) { return n + 1; };
-      var instance = [1, 2, 3];
-      var actual = update(instance, {1: {'$apply': $apply}});
-      assert.deepEqual(instance, [1, 2, 3]);
-      assert.deepEqual(actual, [1, 3, 3]);
-    });
+    var m1 = {a: 1, b: 2};
+    var m2 = t.update(m1, {a: {$set: 2}, b: {$set: 2}});
+    assert.equal(m1 === m2, false);
+  });
 
-    it('dates', function () {
-      var instance = new Date(1973, 10, 30);
-      var actual = update(instance, { $apply: function (date) { return date.getFullYear(); } });
-      assert.equal(actual, 1973);
-    });
+  describe('structs', function () {
+
+    var instance = new Point({x: 0, y: 1});
 
-    it('regexps', function () {
-      var instance = /a/;
-      var actual = update(instance, { $apply: function (regexp) { return regexp.source; } });
-      assert.equal(actual, 'a');
+    it('should handle $set command', function () {
+      var updated = update(instance, {x: {$set: 1}});
+      assert.deepEqual(instance, {x: 0, y: 1});
+      assert.deepEqual(updated, {x: 1, y: 1});
     });
 
-    it('structs', function () {
-      var instance = new Point({x: 0, y: 1});
+    it('should handle $apply command', function () {
       var updated = update(instance, {x: {$apply: function (x) {
         return x + 2;
       }}});
@@ -125,183 +148,132 @@ describe('t.update(instance, patch)', function () {
       assert.deepEqual(updated, {x: 2, y: 1});
     });
 
+    it('should handle $merge command', function () {
+      var updated = update(instance, {'$merge': {x: 2, y: 2}});
+      assert.deepEqual(instance, {x: 0, y: 1});
+      assert.deepEqual(updated, {x: 2, y: 2});
+      var Nested = t.struct({
+        a: t.Number,
+        b: t.struct({
+          c: t.Number,
+          d: t.Number,
+          e: t.Number
+        })
+      });
+      instance = new Nested({a: 1, b: {c: 2, d: 3, e: 4}});
+      updated = update(instance, {b: {'$merge': {c: 5, e: 6}}});
+      assert.deepEqual(instance, {a: 1, b: {c: 2, d: 3, e: 4}});
+      assert.deepEqual(updated, {a: 1, b: {c: 5, d: 3, e: 6}});
+    });
+
     it('should not change the reference when no changes occurs', function () {
-      var $apply = function (n) { return n; };
-      var arr1 = [1, 2, 3];
-      var arr2 = update(arr1, {1: {'$apply': $apply}});
-      assert.strictEqual(arr1, arr2);
+      var p1 = new Point({x: 0, y: 1});
+      var p2 = Point.update(p1, {});
+      assert.strictEqual(p1, p2);
+      var p3 = Point.update(p1, {x: {$set: 0}});
+      assert.strictEqual(p1, p3);
     });
 
   });
 
-  describe('$unshift', function () {
-
-    it('should handle $unshift command', function () {
-      var actual = update([1, 2, 3], {'$unshift': [4]});
-      assert.deepEqual(actual, [4, 1, 2, 3]);
-      actual = update([1, 2, 3], {'$unshift': [4, 5]});
-      assert.deepEqual(actual, [4, 5, 1, 2, 3]);
-      actual = update([1, 2, 3], {'$unshift': [[4, 5]]});
-      assert.deepEqual(actual, [[4, 5], 1, 2, 3]);
-    });
+  describe('tuples', function () {
 
-    it('lists', function () {
-      var instance = List([1, 2, 3, 4]);
-      var updated = update(instance, {$unshift: [5]});
-      assert.deepEqual(updated, [5, 1, 2, 3, 4]);
-      updated = update(instance, {$unshift: [5, 6]});
-      assert.deepEqual(updated, [5, 6, 1, 2, 3, 4]);
-    });
+    var instance = Tuple(['a', 1]);
 
-    it('should not change the reference when no changes occurs', function () {
-      var arr = [1, 2, 3];
-      assert.strictEqual(update(arr, {'$unshift': []}), arr);
+    it('should handle $set command', function () {
+      var updated = update(instance, {0: {$set: 'b'}});
+      assert.deepEqual(updated, ['b', 1]);
     });
 
   });
 
-  describe('$push', function () {
+  describe('lists', function () {
 
-    it('should handle $push command', function () {
-      var actual = update([1, 2, 3], {'$push': [4]});
-      assert.deepEqual(actual, [1, 2, 3, 4]);
-      actual = update([1, 2, 3], {'$push': [4, 5]});
-      assert.deepEqual(actual, [1, 2, 3, 4, 5]);
-      actual = update([1, 2, 3], {'$push': [[4, 5]]});
-      assert.deepEqual(actual, [1, 2, 3, [4, 5]]);
+    var instance = List([1, 2, 3, 4]);
+
+    it('should handle $set command', function () {
+      var updated = update(instance, {2: {$set: 5}});
+      assert.deepEqual(updated, [1, 2, 5, 4]);
+    });
+
+    it('should handle $splice command', function () {
+      var updated = update(instance, {$splice: [[1, 2, 5, 6]]});
+      assert.deepEqual(updated, [1, 5, 6, 4]);
     });
 
-    it('lists', function () {
-      var instance = List([1, 2, 3, 4]);
+    it('should handle $concat command', function () {
       var updated = update(instance, {$push: [5]});
       assert.deepEqual(updated, [1, 2, 3, 4, 5]);
       updated = update(instance, {$push: [5, 6]});
       assert.deepEqual(updated, [1, 2, 3, 4, 5, 6]);
     });
 
-    it('should not change the reference when no changes occurs', function () {
-      var arr = [1, 2, 3];
-      assert.strictEqual(update(arr, {'$push': []}), arr);
-    });
-
-  });
-
-  describe('$splice', function () {
-
-    it('arrays', function () {
-      var instance = [1, 2, {a: [12, 17, 15]}];
-      var actual = update(instance, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
-      assert.deepEqual(instance, [1, 2, {a: [12, 17, 15]}]);
-      assert.deepEqual(actual, [1, 2, {a: [12, 13, 14, 15]}]);
-    });
-
-    it('should throw with bas arguments', function () {
-      var instance = [1, 2, {a: [12, 17, 15]}];
-      throwsWithMessage(function () {
-        update(instance, {2: {a: {$splice: [1, 1, 13, 14]}}});
-      }, '[tcomb] Invalid argument splices supplied to immutability helper { $splice: splices } (expected an array of arrays)');
-    });
-
-    it('lists', function () {
-      var instance = List([1, 2, 3, 4]);
-      var updated = update(instance, {$splice: [[1, 2, 5, 6]]});
-      assert.deepEqual(updated, [1, 5, 6, 4]);
+    it('should handle $prepend command', function () {
+      var updated = update(instance, {$unshift: [5]});
+      assert.deepEqual(updated, [5, 1, 2, 3, 4]);
+      updated = update(instance, {$unshift: [5, 6]});
+      assert.deepEqual(updated, [5, 6, 1, 2, 3, 4]);
     });
 
-    it('should not change the reference when no changes occurs', function () {
-      var arr = [1, 2, 3];
-      assert.strictEqual(update(arr, {'$splice': []}), arr);
+    it('should handle $swap command', function () {
+      var updated = update(instance, {$swap: {from: 1, to: 2}});
+      assert.deepEqual(updated, [1, 3, 2, 4]);
     });
 
   });
 
-  describe('$remove', function () {
+  describe('dicts', function () {
 
-    it('objects', function () {
-      var instance = {a: 1, b: 2};
-      var actual = update(instance, {'$remove': ['a']});
-      assert.deepEqual(instance, {a: 1, b: 2});
-      assert.deepEqual(actual, {b: 2});
-    });
+    var instance = Dict({a: 1, b: 2});
 
-    it('can $merge and $remove objects at once', function () {
-      var instance = {a: [1, 2], b: true};
-      var actual = update(instance, { $merge: {a: [1, 2, 3] }, $remove: ['b'] });
-      assert.deepEqual(instance, {a: [1, 2], b: true});
-      assert.deepEqual(actual, {a: [1, 2, 3]});
+    it('should handle $set command', function () {
+      var updated = update(instance, {a: {$set: 2}});
+      assert.deepEqual(updated, {a: 2, b: 2});
+      updated = update(instance, {c: {$set: 3}});
+      assert.deepEqual(updated, {a: 1, b: 2, c: 3});
     });
 
-    it('dicts', function () {
-      var instance = Dict({a: 1, b: 2});
+    it('should handle $remove command', function () {
       var updated = update(instance, {$remove: ['a']});
       assert.deepEqual(updated, {b: 2});
     });
 
-    it('can $merge and $remove dicts at once', function () {
-      var instance = Dict({a: 1, b: 2});
+    it('can $merge and $remove at once', function () {
       var updated = update(instance, { $merge: {a: [1, 2, 3] }, $remove: ['b'] });
       assert.deepEqual(updated, {a: [1, 2, 3]});
     });
 
-    it('should not change the reference when no changes occurs', function () {
-      var o1 = {x: 0, y: 1};
-      var o2 = update(o1, { $remove: [] });
-      assert.strictEqual(o1, o2);
-    });
-
   });
 
-  describe('$swap', function () {
+  describe('memory saving', function () {
 
-    it('arrays', function () {
-      var instance = [1, 2, 3, 4];
-      var actual = update(instance, {'$swap': {from: 1, to: 2}});
-      assert.deepEqual(instance, [1, 2, 3, 4]);
-      assert.deepEqual(actual, [1, 3, 2, 4]);
-    });
-
-    it('lists', function () {
-      var instance = List([1, 2, 3, 4]);
-      var updated = update(instance, {$swap: {from: 1, to: 2}});
-      assert.deepEqual(updated, [1, 3, 2, 4]);
-    });
-
-    it('should not change the reference when no changes occurs', function () {
-      var arr1 = [1, 2, 3, 4];
-      var arr2 = update(arr1, {'$swap': {from: 1, to: 1}});
-      assert.strictEqual(arr1, arr2);
-    });
-
-  });
-
-  describe('$merge', function () {
-
-    it('structs', function () {
-      var instance = new Point({x: 0, y: 1});
-      var updated = update(instance, {'$merge': {x: 2, y: 2}});
-      assert.deepEqual(instance, {x: 0, y: 1});
-      assert.deepEqual(updated, {x: 2, y: 2});
-      var Nested = t.struct({
+    it('should reuse members that are not updated', function () {
+      var Struct = t.struct({
         a: t.Number,
-        b: t.struct({
-          c: t.Number,
-          d: t.Number,
-          e: t.Number
-        })
+        b: t.String,
+        c: t.tuple([t.Number, t.Number])
       });
-      instance = new Nested({a: 1, b: {c: 2, d: 3, e: 4}});
-      updated = update(instance, {b: {'$merge': {c: 5, e: 6}}});
-      assert.deepEqual(instance, {a: 1, b: {c: 2, d: 3, e: 4}});
-      assert.deepEqual(updated, {a: 1, b: {c: 5, d: 3, e: 6}});
-    });
+      var List = t.list(Struct);
+      var instance = List([{
+        a: 1,
+        b: 'one',
+        c: [1000, 1000000]
+      }, {
+        a: 2,
+        b: 'two',
+        c: [2000, 2000000]
+      }]);
 
-    it('should not change the reference when no changes occurs', function () {
-      // issue #199
-      var bob = { name: 'Bob', surname: 'Builder' };
-      var stillBob = t.update(bob, { $merge: { name: 'Bob', surname: 'Builder' } });
-      assert.strictEqual(bob, stillBob);
-    });
+      var updated = update(instance, {
+        1: {
+          a: {$set: 119}
+        }
+      });
 
+      assert.strictEqual(updated[0], instance[0]);
+      assert.notStrictEqual(updated[1], instance[1]);
+      assert.strictEqual(updated[1].c, instance[1].c);
+    });
   });
 
   describe('all together now', function () {
@@ -359,48 +331,4 @@ describe('t.update(instance, patch)', function () {
 
   });
 
-  describe('should not change the reference when no changes occurs', function () {
-
-    it('structs', function () {
-      var p1 = new Point({x: 0, y: 1});
-      var p2 = Point.update(p1, {});
-      assert.strictEqual(p1, p2);
-      var p3 = Point.update(p1, {x: {$set: 0}});
-      assert.strictEqual(p1, p3);
-      var p5 = Point.update(p1, {$remove: []});
-      assert.strictEqual(p1, p5);
-      var p4 = Point.update(p1, {$merge: {}});
-      assert.strictEqual(p1, p4);
-    });
-
-    it('lists', function () {
-      var Struct = t.struct({
-        a: t.Number,
-        b: t.String,
-        c: t.tuple([t.Number, t.Number])
-      });
-      var List = t.list(Struct);
-      var instance = List([{
-        a: 1,
-        b: 'one',
-        c: [1000, 1000000]
-      }, {
-        a: 2,
-        b: 'two',
-        c: [2000, 2000000]
-      }]);
-
-      var updated = update(instance, {
-        1: {
-          a: {$set: 119}
-        }
-      });
-
-      assert.strictEqual(updated[0], instance[0]);
-      assert.notStrictEqual(updated[1], instance[1]);
-      assert.strictEqual(updated[1].c, instance[1].c);
-    });
-
-  });
-
 });