Returns a unitary matrix describing the given value.

    Args:
        val: The value to describe with a unitary matrix.
        default: Determines the fallback behavior when `val` doesn't have
            a unitary matrix. If `default` is not set, a TypeError is raised. If
            default is set to a value, that value is returned.

    Returns:
        If `val` has a _unitary_ method and its result is not NotImplemented,
        that result is returned. Otherwise, if `val` is a cirq.Gate or
        cirq.Operation, decomposition will be attempted and the resulting
        unitary is returned if unitaries exist for all operations of the
        decompostion. If the result is still NotImplemented and a default value
        was specified, the default value is returned.

    Raises:
        TypeError: `val` doesn't have a _unitary_ method (or that method
            returned NotImplemented) and also no default value was specified.

def unitary(val: Any,
            default: TDefault = RaiseTypeErrorIfNotProvided
            ) -> Union[np.ndarray, TDefault]:
    """Returns a unitary matrix describing the given value.

    Args:
        val: The value to describe with a unitary matrix.
        default: Determines the fallback behavior when `val` doesn't have
            a unitary matrix. If `default` is not set, a TypeError is raised. If
            default is set to a value, that value is returned.

    Returns:
        If `val` has a _unitary_ method and its result is not NotImplemented,
        that result is returned. Otherwise, if `val` is a cirq.Gate or
        cirq.Operation, decomposition will be attempted and the resulting
        unitary is returned if unitaries exist for all operations of the
        decompostion. If the result is still NotImplemented and a default value
        was specified, the default value is returned.

    Raises:
        TypeError: `val` doesn't have a _unitary_ method (or that method
            returned NotImplemented) and also no default value was specified.
    """
    from cirq import Gate, Operation  # HACK: Avoids circular dependencies.

    getter = getattr(val, '_unitary_', None)
    result = NotImplemented if getter is None else getter()
    if result is not NotImplemented:
        return result

    # Fallback to decomposition for gates and operations
    if isinstance(val, (Gate, Operation)):
        decomposed_unitary = _decompose_and_get_unitary(val)
        if decomposed_unitary is not None:
            return decomposed_unitary

    if default is not RaiseTypeErrorIfNotProvided:
        return default

    if getter is None:
        raise TypeError("object of type '{}' "
                        "has no _unitary_ method.".format(type(val)))
    raise TypeError("object of type '{}' does have a _unitary_ method, "
                    "but it returned NotImplemented.".format(type(val)))