function getFunctionName (fn) {
  return fn.name || fn.toString().match(/function (.*?)\s*\(/)[1]
}

function getTypeTypeName (type) {
  if (nativeTypes.Function(type)) return type.toJSON ? type.toJSON() : getFunctionName(type)
  if (nativeTypes.Object(type)) return JSON.stringify(type)

  return type
}

function getValueTypeName (value) {
  if (nativeTypes.Null(value)) return ''

  return getFunctionName(value.constructor)
}

function tfErrorString (type, value) {
  var typeTypeName = getTypeTypeName(type)
  var valueTypeName = getValueTypeName(value)

  return 'Expected ' + typeTypeName + ', got ' + (valueTypeName && valueTypeName + ' ') + JSON.stringify(value)
}

function tfPropertyErrorString (type, name, value) {
  return tfErrorString('property \"' + name + '\" of type ' + getTypeTypeName(type), value)
}

var nativeTypes = {
  Array (value) { return value !== null && value !== undefined && value.constructor === Array },
  Boolean (value) { return typeof value === 'boolean' },
  Buffer (value) { return Buffer.isBuffer(value) },
  Function (value) { return typeof value === 'function' },
  Null (value) { return value === undefined || value === null },
  Number (value) { return typeof value === 'number' },
  Object (value) { return typeof value === 'object' },
  String (value) { return typeof value === 'string' },
  '' () { return true }
}

var otherTypes = {
  arrayOf (type) {
    function arrayOf (value, strict) {
      try {
        return nativeTypes.Array(value) && value.every(x => typeforce(type, x, strict))

      } catch (e) {
        return false
      }
    }
    arrayOf.toJSON = () => '[' + JSON.stringify(type) + ']'

    return arrayOf
  },

  maybe (type) {
    function maybe (value, strict) {
      return nativeTypes.Null(value) || typeforce(type, value, strict)
    }
    maybe.toJSON = () => '?' + (type.toJSON && type.toJSON() || JSON.stringify(type))

    return maybe
  },

  object (type) {
    function object (value, strict) {
      typeforce(nativeTypes.Object, value, strict)

      var propertyName, propertyType, propertyValue

      try {
        for (propertyName in type) {
          propertyType = type[propertyName]
          propertyValue = value[propertyName]

          typeforce(propertyType, propertyValue, strict)
        }

      } catch (e) {
        throw new TypeError(tfPropertyErrorString(propertyType, propertyName, propertyValue))
      }

      if (strict) {
        for (propertyName in value) {
          if (type[propertyName]) continue

          throw new TypeError('Unexpected property "' + propertyName + '"')
        }
      }

      return true
    }
    object.toJSON = () => JSON.stringify(type)

    return object
  },

  oneOf (types) {
    return function oneOf (value, strict) {
      return types.some(type => {
        try {
          return typeforce(type, value, strict)

        } catch (e) {
          return false
        }
      })
    }
  },

  value (expected) {
    return function value (actual) {
      return actual === expected
    }
  }
}

function compile (type) {
  if (nativeTypes.String(type)) {
    if (type[0] === '?') {
      type = type.slice(1)

      return otherTypes.maybe(compile(type))
    }

    return nativeTypes[type] || type

  } else if (nativeTypes.Object(type)) {
    if (nativeTypes.Array(type)) {
      return otherTypes.arrayOf(compile(type[0]))
    }

    var compiled = {}

    for (var propertyName in type) {
      compiled[propertyName] = compile(type[propertyName])
    }

    return otherTypes.object(compiled)
  }

  return type
}

function typeforce (type, value, strict) {
  if (nativeTypes.Function(type)) {
    if (type(value, strict)) return true

    throw new TypeError(tfErrorString(type, value))
  }

  // JIT
  type = compile(type)
  if (!nativeTypes.String(type)) return typeforce(type, value, strict)
  if (type === getValueTypeName(value)) return true

  throw new TypeError(tfErrorString(type, value))
}

// assign all types to typeforce function
var typeName
Object.keys(nativeTypes).forEach(typeName => {
  var nativeType = nativeTypes[typeName]
  nativeType.toJSON = () => typeName

  typeforce[typeName] = nativeType
})

for (typeName in otherTypes) {
  typeforce[typeName] = otherTypes[typeName]
}

module.exports = typeforce
module.exports.compile = compile

@@ -2938,16 +2938,196 @@
 %7D%0A  %7D,%0A%0A
+  quacksLike (type) %7B%0A    function quacksLike (value, strict) %7B%0A      return type === getValueTypeName(value)%0A    %7D%0A    quacksLike.toJSON = () =%3E type%0A%0A    return quacksLike%0A  %7D,%0A%0A
   value 
@@ -3135,24 +3135,24 @@
 expected) %7B%0A
-
     return f
@@ -3421,20 +3421,43 @@
 ype%5D %7C%7C 
+otherTypes.quacksLike(
 type
+)
 %0A%0A  %7D el
@@ -3963,201 +3963,53 @@
 T%0A  
-type = compile(type)%0A  if (!nativeTypes.String(type)) return typeforce(type, value, strict)%0A  if (type === getValueTypeName(value)) return true%0A%0A  throw new TypeError(tfErrorString(type, value)
+return typeforce(compile(type), value, strict
 )%0A%7D%0A

break out duck typing to a new type