code: function infer(val, opts) {
    opts = opts || {};
  
    // Optional custom inference hook.
    if (opts.valueHook) {
      var type = opts.valueHook(val, opts);
      if (type !== undefined) {
        if (!types.Type.isType(type)) {
          throw new Error(f('invalid value hook return value: %j', type));
        }
        return type;
      }
    }
  
    // Default inference logic.
    switch (typeof val) {
      case 'string':
        return createType('string', opts);
      case 'boolean':
        return createType('boolean', opts);
      case 'number':
        if ((val | 0) === val) {
          return createType('int', opts);
        } else if (Math.abs(val) < 9007199254740991) {
          return createType('float', opts);
        }
        return createType('double', opts);
      case 'object':
        if (val === null) {
          return createType('null', opts);
        } else if (Array.isArray(val)) {
          if (!val.length) {
            return EMPTY_ARRAY_TYPE;
          }
          return createType({
            type: 'array',
            items: combine(val.map(function (v) { return infer(v, opts); }))
          }, opts);
        } else if (Buffer.isBuffer(val)) {
          return createType('bytes', opts);
        }
        var fieldNames = Object.keys(val);
        if (fieldNames.some(function (s) { return !types.isValidName(s); })) {
          // We have to fall back to a map.
          return createType({
            type: 'map',
            values: combine(fieldNames.map(function (s) {
              return infer(val[s], opts);
            }), opts)
          }, opts);
        }
        return createType({
          type: 'record',
          fields: fieldNames.map(function (s) {
            return {name: s, type: infer(val[s], opts)};
          })
        }, opts);
      default:
        throw new Error(f('cannot infer type from: %j', val));
    }
  }
docs: Infer a type from a value.