docs: def decompose(
      val: TValue,
      *,
      intercepting_decomposer: Callable[['cirq.Operation'],
                                        Union[None,
                                              NotImplementedType,
                                              'cirq.OP_TREE']] = None,
      fallback_decomposer: Callable[['cirq.Operation'],
                                    Union[None,
                                          NotImplementedType,
                                          'cirq.OP_TREE']] = None,
      keep: Callable[['cirq.Operation'], bool] = None,
      on_stuck_raise: Union[None,
                            Exception,
                            Callable[['cirq.Operation'],
                                     Union[None, Exception]]]
      = _value_error_describing_bad_operation
  ) -> List['cirq.Operation']:
      """Recursively decomposes a value into `cirq.Operation`s meeting a criteria.
  
      Args:
          val: The value to decompose into operations.
          intercepting_decomposer: An optional method that is called before the
              default decomposer (the value's `_decompose_` method). If
              `intercepting_decomposer` is specified and returns a result that
              isn't `NotImplemented` or `None`, that result is used. Otherwise the
              decomposition falls back to the default decomposer.
  
              Note that `val` will be passed into `intercepting_decomposer`, even
              if `val` isn't a `cirq.Operation`.
          fallback_decomposer: An optional decomposition that used after the
              `intercepting_decomposer` and the default decomposer (the value's
              `_decompose_` method) both fail.
          keep: A predicate that determines if the initial operation or
              intermediate decomposed operations should be kept or else need to be
              decomposed further. If `keep` isn't specified, it defaults to "value
              can't be decomposed anymore".
          on_stuck_raise: If there is an operation that can't be decomposed and
              also can't be kept, `on_stuck_raise` is used to determine what error
              to raise. `on_stuck_raise` can either directly be an `Exception`, or
              a method that takes the problematic operation and returns an
              `Exception`. If `on_stuck_raise` is set to `None` or a method that
              returns `None`, undecomposable operations are simply silently kept.
              `on_stuck_raise` defaults to a `ValueError` describing the unwanted
              undecomposable operation.
  
      Returns:
          A list of operations that the given value was decomposed into. If
          `on_stuck_raise` isn't set to None, all operations in the list will
          satisfy the predicate specified by `keep`.
  
      Raises:
          TypeError:
              `val` isn't a `cirq.Operation` and can't be decomposed even once.
              (So it's not possible to return a list of operations.)
  
          ValueError:
              Default type of error raised if there's an undecomposable operation
              that doesn't satisfy the given `keep` predicate.
  
          TError:
              Custom type of error raised if there's an undecomposable operation
              that doesn't satisfy the given `keep` predicate.
      """
      from cirq import ops  # HACK: Avoids circular dependencies.
  
      if (on_stuck_raise is not _value_error_describing_bad_operation and
              keep is None):
          raise ValueError(
              "Must specify 'keep' if specifying 'on_stuck_raise', because it's "
              "not possible to get stuck if you don't have a criteria on what's "
              "acceptable to keep.")
  
      decomposers = [d
                     for d in [intercepting_decomposer,
                               _default_decomposer,
                               fallback_decomposer]
                     if d]
  
      def decomposer(op):
          for d in decomposers:
              r = d(op)
              if r is not NotImplemented and r is not None:
                  return r
          return NotImplemented
  
      output = []
      queue = [val]  # type: List[Any]
      while queue:
          item = queue.pop(0)
  
          if isinstance(item, ops.Operation) and keep is not None and keep(item):
              output.append(item)
              continue
  
          decomposed = decomposer(item)
          if decomposed is not NotImplemented and decomposed is not None:
              queue[:0] = ops.flatten_op_tree(decomposed)
              continue
  
          if (not isinstance(item, ops.Operation) and
                  isinstance(item, collections.Iterable)):
              queue[:0] = ops.flatten_op_tree(item)
              continue
  
          if keep is not None and on_stuck_raise is not None:
              if isinstance(on_stuck_raise, Exception):
                  raise on_stuck_raise
              elif callable(on_stuck_raise):
                  error = on_stuck_raise(item)
                  if error is not None:
                      raise error
  
          output.append(item)
  
      return output