docs: def reduce(tensor, pattern: str, reduction: str, **axes_lengths: int):
      """
      einops.reduce provides combination of reordering and reduction using reader-friendly notation.
      
      Examples for reduce operation:
      
      >>> x = np.random.randn(100, 32, 64)
      >>> # perform max-reduction on the first axis
      >>> y = reduce(x, 't b c -> b c', 'max')
      >>> # same as previous, but with clearer axes meaning
      >>> y = reduce(x, 'time batch channel -> batch channel', 'max')
  
      >>> x = np.random.randn(10, 20, 30, 40)
      >>> # 2d max-pooling with kernel size = 2 * 2 for image processing
      >>> y1 = reduce(x, 'b c (h1 h2) (w1 w2) -> b c h1 w1', 'max', h2=2, w2=2)
      >>> # if one wants to go back to the original height and width, depth-to-space trick can be applied
      >>> y2 = rearrange(y1, 'b (c h2 w2) h1 w1 -> b c (h1 h2) (w1 w2)', h2=2, w2=2)
      >>> assert parse_shape(x, 'b _ h w') == parse_shape(y2, 'b _ h w')
      >>> # Adaptive 2d max-pooling to 3 * 4 grid
      >>> reduce(x, 'b c (h1 h2) (w1 w2) -> b c h1 w1', 'max', h1=3, w1=4).shape
      (10, 20, 3, 4)
      >>> # Global average pooling
      >>> reduce(x, 'b c h w -> b c', 'mean').shape
      (10, 20)
      >>> # Subtracting mean over batch for each channel
      >>> y = x - reduce(x, 'b c h w -> () c () ()', 'mean')
      >>> # Subtracting per-image mean for each channel
      >>> y = x - reduce(x, 'b c h w -> b c () ()', 'mean') 
      
      :param tensor: tensor: tensor of any supported library (e.g. numpy.ndarray, tensorflow, pytorch, mxnet.ndarray).
              list of tensors is also accepted, those should be of the same type and shape
      :param pattern: string, reduction pattern
      :param reduction: one of available reductions ('min', 'max', 'sum', 'mean', 'prod'), case-sensitive
      :param axes_lengths: any additional specifications for dimensions
      :return: tensor of the same type as input
      """
      try:
          hashable_axes_lengths = tuple(sorted(axes_lengths.items()))
          recipe = _prepare_transformation_recipe(pattern, reduction, axes_lengths=hashable_axes_lengths)
          return recipe.apply(tensor)
      except EinopsError as e:
          message = ' Error while processing {}-reduction pattern "{}".'.format(reduction, pattern)
          if not isinstance(tensor, list):
              message += '\n Input tensor shape: {}. '.format(get_backend(tensor).shape(tensor))
          else:
              message += '\n Input is list. '
          message += 'Additional info: {}.'.format(axes_lengths)
          raise EinopsError(message + '\n {}'.format(e))