All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.snowplowanalytics.snowplow.runtime.syntax.foldable.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2023-present Snowplow Analytics Ltd. All rights reserved.
 *
 * This program is licensed to you under the Snowplow Community License Version 1.0,
 * and you may not use this file except in compliance with the Snowplow Community License Version 1.0.
 * You may obtain a copy of the Snowplow Community License Version 1.0 at https://docs.snowplow.io/community-license-1.0
 */
package com.snowplowanalytics.snowplow.runtime.syntax

import cats.implicits._
import cats.{Foldable, Monad}
import java.nio.ByteBuffer

trait FoldableExtensionSyntax {
  implicit final def snowplowFoldableSyntax[M[_]](foldable: Foldable[M]): FoldableExtensionOps[M] = new FoldableExtensionOps(foldable)
}

final class FoldableExtensionOps[M[_]](private val M: Foldable[M]) extends AnyVal {

  /**
   * Traversal over a List with an effect
   *
   * This is similar to a cats Traverse. But it is more efficient than cats Traverse because it does
   * not attempt to keep the order of the original list in the final result.
   *
   * This is helpful in many snowplow apps, where order of events is not important to us.
   *
   * Example:
   * {{{
   * Foldable[List].tranverseUnordered(events) { event =>
   *    IO.delay {
   *      transformEvent(event)
   *    }
   *  }
   * }}}
   */
  def traverseUnordered[F[_]: Monad, A, B](items: M[A])(f: A => F[B]): F[List[B]] =
    M.foldM(items, List.empty[B]) { case (acc, item) =>
      f(item).map(_ :: acc)
    }

  /**
   * Traversal over a List with an effect that produces success or failure
   *
   * This is helpful in many snowplow apps where we process events in batches, and each event might
   * produce a bad row. We typically want to handle the resulting bad rows separately from the
   * successes. And we don't care about the order of events.
   *
   * Example:
   * {{{
   *  Foldable[List].traverseUnordered(strings) { str =>
   *    IO.delay {
   *      Event.parse(str).toEither
   *    }
   *  }.map { case (badRows, events) =>
   *    // handle results
   *  }
   * }}}
   */
  def traverseSeparateUnordered[F[_]: Monad, A, B, C](items: M[A])(f: A => F[Either[B, C]]): F[(List[B], List[C])] =
    M.foldM(items, (List.empty[B], List.empty[C])) { case ((lefts, rights), item) =>
      f(item).map {
        case Left(b)  => (b :: lefts, rights)
        case Right(c) => (lefts, c :: rights)
      }
    }

  /**
   * Sum elements of a List
   *
   * Helpful in snowplow apps for summing the lengths of byte buffers
   *
   * Example:
   * {{{
   * Foldable[Chunk].sumBy(byteBuffers) { b =>
   *   b.limit() - b.position()
   * }
   * }}}
   */
  def sumBy[F[_], A](items: M[A])(f: A => Long): Long =
    M.foldLeft(items, 0L) { case (acc, item) =>
      acc + f(item)
    }

  /**
   * Sum total number of bytes in a list of byte buffers
   *
   * Example:
   * {{{
   * Foldable[Chunk].sumBytes(byteBuffers)
   * }}}
   */
  def sumBytes[F[_]](items: M[ByteBuffer]): Long =
    sumBy(items) { byteBuffer =>
      (byteBuffer.limit() - byteBuffer.position()).toLong
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy