in src/main/scala/com/twitter/stitch/Arrow.scala [3689:3879]
def tuple[A, B, C, D](a1: Arrow[A, B], a2: Arrow[C, D]): Arrow[(A, C), (B, D)] =
join(a1.contramap(_._1), a2.contramap[(A, C)](_._2))
/**
* `Arrow.zipWithArg` is equivalent to `{ x => a(x).map((x, _)) }`
*/
def zipWithArg[T, U](f: Arrow[T, U]): Arrow[T, (T, U)] =
Arrow.join(Arrow.identity[T], f)
/**
* let-scope a [[TwitterLocal]] around the contained [[Arrow]] `a` based on a by-name `local`
* When running this [[Arrow]], `local` is evaluated once per call to [[Arrow.run]], meaning it's evaluated only once for each batch.
*
* This is equivalent to `local.let(local)(a)` but correctly applies the `local` to the `a`'s execution.
*
* @note [[let]] applies the [[Local]] __only non-batched operations__,
* [[call]] and [[Group]]s do not have access to [[Local]]s set with [[let]].
* To access a [[Local]] value within a [[call]], or [[Group]], access the value
* before making your [[call]] and add it to your input.
* For instance,`a.map(v => (v, local())).call(...)`
*
* @param local the [[TwitterLocal]] that should be let-scoped around `a`
* @param value the value of the `local` within the execution of `a`
* @param a the underlying [[Arrow]] which will have the `local` set during it's evaluation
* @tparam L the type of the [[TwitterLocal]] and it's value
*/
def let[T, U, L](local: TwitterLocal[L])(value: => L)(a: Arrow[T, U]): Arrow[T, U] =
let(LocalLetter(local))(value)(a)
/**
* let-scope a [[TwitterLocal]] around the contained [[Arrow]] `a` based on the input
*
* This is equivalent to `local.let(local)(a)` but correctly applies the `local` to the `a`'s execution.
*
* If your `update` returns a constant value, [[let]] is a more efficient.
*
* @note [[letWithArg]] applies the [[Local]] __only non-batched operations__,
* [[call]] and [[Group]]s do not have access to [[Local]]s set with [[letWithArg]].
* To access a [[Local]] value within a [[call]], or [[Group]], access the value
* before making your [[call]] and add it to your input.
* For instance,`a.map(v => (v, local())).call(...)`
*
* @note if the input to this [[Arrow]] is in a [[Throw]] state then the underlying [[TwitterLocal]] will not be scoped around `a`
* but `a` will be executed with the underlying [[Throw]] passed in.
* Similarly, if `update` throws, then `a` will be executed with a [[Throw]] of that exception without the
* [[TwitterLocal]] being scoped.
*
* @param local the [[TwitterLocal]] that should be let-scoped around `a`
* @param update a function which will be evaluated to determine the value of the `local` within the execution of `a`
* @param a the underlying [[Arrow]] which will have the `local` set during it's evaluation
* @tparam L the type of the [[TwitterLocal]] and it's value
*/
def letWithArg[T, U, L](local: TwitterLocal[L])(update: T => L)(a: Arrow[T, U]): Arrow[T, U] =
letWithArg(LocalLetter(local))(update)(a)
/**
* clear a [[TwitterLocal]] around the contained [[Arrow]] `a`
*
* This is equivalent to `local.letClear(a)` but correctly clears the `local` for `a`'s execution.
*
* @param local the [[TwitterLocal]] that should be let-scoped around `a`
* @param a the underlying [[Arrow]] which will have the `local` set during it's evaluation
*/
def letClear[T, U](local: TwitterLocal[_])(a: => Arrow[T, U]): Arrow[T, U] =
letClear(LocalLetClearer(local))(a)
/**
* let-scope a [[TwitterLocal]] around the contained [[Arrow]] `a` based on a by-name `value`
* When running this [[Arrow]], `local` is evaluated once per call to [[Arrow.run]], meaning it's evaluated only once for each batch.
*
* This is equivalent to `Letter.let(value)(a)` but correctly applies the [[Letter.let]] to the `a`'s execution.
*
* This is used for let-scoping [[TwitterLocal]]s which are private to a class and can only be accessed with their own
* let method instead of giving access to the underlying [[TwitterLocal]]
*
* @note [[let]] applies the [[Local]] __only non-batched operations__,
* [[call]] and [[Group]]s do not have access to [[Local]]s set with [[let]].
* To access a [[Local]] value within a [[call]], or [[Group]], access the value
* before making your [[call]] and add it to your input.
* For instance,`a.map(v => (v, local())).call(...)`
*
* @param letter [[Letter]] that let-scopes around a block of code
* @param a the underlying [[Arrow]] which will have the `local` set during it's evaluation
* @tparam L the type of the [[TwitterLocal]] and it's value
*/
def let[T, U, L](letter: Letter[L])(value: => L)(a: Arrow[T, U]): Arrow[T, U] =
TwitterLocals.Let(() => value, letter, a)
/**
* let-scope a [[TwitterLocal]] around the contained [[Arrow]] `a` based on the input
*
* This is equivalent to `Letter.let(value)(a)` where `value` changes for each argument in the batch
* but correctly applies the [[Letter.let]] to the `a`'s execution.
*
* If your `update` returns a constant value, [[let]] is a more efficient.
*
* This is used for let-scoping [[TwitterLocal]]s which are private to a class and can only be accessed with their own
* let method instead of giving access to the underlying [[TwitterLocal]]
*
* @note [[letWithArg]] applies the [[Local]] __only non-batched operations__,
* [[call]] and [[Group]]s do not have access to [[Local]]s set with [[letWithArg]].
* To access a [[Local]] value within a [[call]], or [[Group]], access the value
* before making your [[call]] and add it to your input.
* For instance,`a.map(v => (v, local())).call(...)`
*
* @note if the input to this [[Arrow]] is in a [[Throw]] state then the underlying [[TwitterLocal]] will not be scoped around `a`
* but `a` will be executed with the underlying [[Throw]] passed in.
* Similarly, if `update` throws, then `a` will be executed with a [[Throw]] of that exception without the
* [[TwitterLocal]] being scoped.
*
* @param letter [[Letter]] that let-scopes around a block of code
* @param update a function which will be evaluated to determine the value of the `local` within the execution of `a`
* @param a the underlying [[Arrow]] which will have the `local` set during it's evaluation
* @tparam L the type of the [[TwitterLocal]] and it's value
*/
def letWithArg[T, U, L](letter: Letter[L])(update: T => L)(a: Arrow[T, U]): Arrow[T, U] =
TwitterLocals.LetWithArg[T, U, L](letter, update, a)
/**
* clear a [[TwitterLocal]] around the contained [[Arrow]] `a`
*
* This is equivalent to `LetClearer.letClear(a)` but correctly clears the `local` for `a`'s execution.
*
* This is used for let-scoping [[TwitterLocal]]s which are private to a class and can only be accessed with their own
* let method instead of giving access to the underlying [[TwitterLocal]]
*
* @param letClearer the [[TwitterLocal]] that should be let-scoped around `a`
* @param a the underlying [[Arrow]] which will have the `local` set during it's evaluation
*/
def letClear[T, U](letClearer: LetClearer)(a: Arrow[T, U]): Arrow[T, U] =
TwitterLocals.LetClear(letClearer, a)
/**
* Creates a new `Arrow[T, U]` that dispatches input to one of two sub-arrows
* based on the result of a predicate. This is equivalent to
* `{ x => if (cond(x)) a1(x) else a2(x) }`.
*/
def ifelse[T, U](cond: T => Boolean, a1: Arrow[T, U], a2: Arrow[T, U]): Arrow[T, U] =
choose[T, U](
(cond, a1),
(_ => true, a2)
)
/**
* Creates a new `Arrow[T, U]` that dispatches input to the first
* sub-arrow for which the corresponding predicate returns true. If
* no predicate returns true for the input, that will result in a
* `MatchError` for that input.
*
* Behavior is undefined if a predicate throws an exception.
*/
def choose[T, U](choices: Choice[T, U]*): Arrow[T, U] =
Choose(choices)
/**
* Encapsulates a choice for the `choose` method. If the given predicate returns true
* for a given input, then the corresponding arrow is invoked for that input.
*/
type Choice[-T, +U] = (T => Boolean, Arrow[T, U])
object Choice {
/**
* Builds a `Choice` that dispatches to the given `Arrow` when the given predicate is
* true for the input.
*/
def when[T, U](cond: T => Boolean, arrow: Arrow[T, U]): Choice[T, U] =
(cond, arrow)
/**
* Builds a `Choice` that dispatches to the given `Arrow` when the given `PartialFunction`
* is defined at the input.
*/
def ifDefinedAt[T, S, U](pf: PartialFunction[T, S], a: Arrow[S, U]): Choice[T, U] =
// we make this transformation to expose constant arrows so
// Choose can optimize them. Ordinarily it's not valid to
// transform Arrow.map(f).andThen(Arrow.Const(t)) to
// Arrow.Const(t), because f may throw an exception; but here
// pf may not throw an exception.
a match {
case Const(_) => (pf.isDefinedAt, a.asInstanceOf[Arrow[T, U]])
case _ => (pf.isDefinedAt, Arrow.map(pf).andThen(a))
}
/**
* Buildes a `Choice` with a predicate that is always `true`. This can be used
* as the last `Choice` as a catch-all.
*/
def otherwise[T, U](a: Arrow[T, U]): Choice[T, U] =
(_ => true, a)
}