com.phasmidsoftware.number.core.FP.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of number_2.13 Show documentation
Show all versions of number_2.13 Show documentation
Fuzzy, Lazy Scala library for numerical computation
The newest version!
/*
* Copyright (c) 2023. Phasmid Software
*/
package com.phasmidsoftware.number.core
import java.net.URL
import scala.io.Source
import scala.language.implicitConversions
import scala.reflect.ClassTag
import scala.util.Using.Releasable
import scala.util.{Either, Failure, Left, Right, Success, Try, Using}
/**
* This module is concerned with the generic operations for operating on Numbers.
*
*/
object FP {
/**
* Sequence method to combine elements of Try.
*
* @param xys an Iterator of Try[X]
* @tparam X the underlying type
* @return a Try of Iterator[X]
*/
def sequence[X](xys: Iterator[Try[X]]): Try[Iterator[X]] = sequence(xys.to(List)).map(_.iterator)
/**
* Sequence method to combine elements of Try.
*
* @param xos an Iterator of Try[X]
* @tparam X the underlying type
* @return a Try of Iterator[X]
*/
def sequence[X](xos: Iterator[Option[X]]): Option[Iterator[X]] = sequence(xos.to(List)).map(_.iterator)
/**
* Sequence method to combine elements of type Option[X].
*
* @param xos an Iterable of Option[X].
* @tparam X the underlying type.
* @return if xos
contains any Nones, the result will be None, otherwise Some(...).
* NOTE: that the output collection type will be Seq, regardless of the input type
*/
def sequence[X](xos: Iterable[Option[X]]): Option[Seq[X]] =
xos.foldLeft(Option(Seq[X]())) {
(xso, xo) => for (xs <- xso; x <- xo) yield xs :+ x
}
/**
* Sequence method to combine elements of Try.
*
* @param xys an Iterable of Try[X]
* @tparam X the underlying type
* @return a Try of Seq[X]
* NOTE: that the output collection type will be Seq, regardless of the input type
*/
def sequence[X](xys: Iterable[Try[X]]): Try[Seq[X]] =
xys.foldLeft(Try(Seq[X]())) {
(xsy, xy) => for (xs <- xsy; x <- xy) yield xs :+ x
}
/**
* Sequence method to invert the order of types Option/Try.
*
* @param xyo an Option of Try[X].
* @tparam X the underlying type.
* @return a Try of Option[X].
*/
def sequence[X](xyo: Option[Try[X]]): Try[Option[X]] = xyo match {
case Some(Success(x)) => Success(Some(x))
case Some(Failure(x)) => Failure(x)
case None => Success(None)
}
/**
* Method to convert a None value to a given exception (rather than the NoSuchElement exception).
*
* @param to an Option[T].
* @param x a Throwable to be thrown if to is None.
* @tparam T the underlying type of to.
* @return t if to is Some(t); otherwise x will be thrown.
*/
def recover[T](to: Option[T], x: => Throwable): T = to match {
case Some(t) => t
case None => throw x
}
/**
* This method and tryMap are (almost) twins (working for Option and Try respectively --
* although there are a few differences other than the monad type).
* To yield the (optional) result, we map the right-hand member of the input (lRe) with a function rToZ.
* We return either the result or, if empty, then the result of flatMap applied to the left member with a function lToZo.
*
* @param lRe the input, an Either[L,R].
* @param r2Z a function R => Z.
* @param l2Zo a function L => Option[Z].
* @tparam L the type of the left-side of the Either.
* @tparam R the type of the right-side of the Either.
* @tparam Z the underlying type of the result.
* @return an Option[Z]
*/
def optionMap[L, R, Z](lRe: Either[L, R])(r2Z: R => Z, l2Zo: L => Option[Z]): Option[Z] =
lRe.toOption.map(r2Z) orElse lRe.left.toOption.flatMap(l2Zo)
/**
* This method and optionMap are (almost) twins (working for Try and Option respectively).
* To yield the (tried) result, we map the right-hand member of the input (lRe) with a function rToZ.
* The result of this is then pattern-matched:
* In the Some(z) case, we return the Success(z).
* In the None case, we return the result of tryMapLeft applied to the lRe with the function l2Zy.
*
* NOTE: in practice, this does not seem to be used, but it should remain here.
*
* @param lRe the input, an Either[L,R].
* @param r2Z a function R => Z.
* @param l2Zy a function L => Try[Z]
* @tparam L the type of the left-side of the Either.
* @tparam R the type of the right-side of the Either.
* @tparam Z the underlying type of the result.
* @return a Try[Z]
*/
def doMap[L, R, Z](lRe: Either[L, R])(r2Z: R => Z, l2Zy: L => Try[Z]): Try[Z] =
lRe.toOption.map(r2Z) match {
case Some(z) => Success(z)
case None => tryMapLeft(lRe, l2Zy)
}
/**
* This method is similar to doMap but with some important differences.
* To yield the (tried) result, we map the right-hand member of the input (lRe) with a function rToZy.
* The result of this is then pattern-matched:
* In the Some(Success(z)) case, we return the Success(z).
* In the Some(Failure) case, we invoke tryMapLeft with the transpose of lRe and the function l2Zy.
* In the None case, we return the result of tryMapLeft applied to the lRe with the function l2Zy.
*
* @param lRe the input, an Either[L,R].
* @param r2Zy a function R => Try[Z].
* @param l2Zy a function L => Try[Z]
* @param r2L an (implicit) converter from R to L.
* @tparam L the type of the left-side of the Either.
* @tparam R the type of the right-side of the Either.
* @tparam Z the underlying type of the result.
* @return a Try[Z]
*/
def tryMap[L, R, Z](lRe: Either[L, R])(r2Zy: R => Try[Z], l2Zy: L => Try[Z])(implicit r2L: R => L): Try[Z] =
lRe.toOption.map(r2Zy) match {
case Some(Success(z)) => Success(z)
case Some(Failure(_)) => tryMapLeft(transpose(lRe), l2Zy)
case None => tryMapLeft(lRe, l2Zy)
}
/**
* Method to yield a URL for a given resourceForClass in the classpath for C.
*
* @param resourceName the name of the resourceForClass.
* @tparam C a class of the package containing the resourceForClass.
* @return a Try[URL].
*/
def resource[C: ClassTag](resourceName: String): Try[URL] = resourceForClass(resourceName, implicitly[ClassTag[C]].runtimeClass)
/**
* Method to yield a Try[URL] for a resource name and a given class.
*
* @param resourceName the name of the resource.
* @param clazz the class, relative to which, the resource can be found (defaults to the caller's class).
* @return a Try[URL]
*/
def resourceForClass(resourceName: String, clazz: Class[_] = getClass): Try[URL] = Option(clazz.getResource(resourceName)) match {
case Some(u) => Success(u)
case None => Failure(new Exception(s"$resourceName is not a valid resource for $clazz"))
}
/**
* This method is invoked by tryMap when the input is "left" or when the r2Zy method fails.
* To yield the (tried) result, we map the left-hand member of the input (lRe) with a function l2Zy.
* The wrapped value of this is then returned (unless empty, in which case a Failure is returned).
*
* @param lRe the input, an Either[L,R].
* @param l2Zy a function L => Try[Z]
* @tparam L the type of the left-side of the Either.
* @tparam R the type of the right-side of the Either.
* @tparam Z the underlying type of the result.
* @return a Try[Z]
*/
private def tryMapLeft[L, R, Z](lRe: Either[L, R], l2Zy: L => Try[Z]): Try[Z] =
lRe.left.toOption.map(l2Zy) getOrElse Failure(new NoSuchElementException)
/**
* If lRe is a Right(R), then we transpose it into a Left(L).
*
* @param lRe an Either[L, R].
* @tparam L the left type.
* @tparam R the right type.
* @return a Left(L) as an Either[L, R].
*/
def transpose[L, R](lRe: Either[L, R])(implicit rToL: R => L): Either[L, R] = lRe match {
case Right(y) => Left(rToL(y))
case Left(_) => lRe
}
/**
* This method fills a gap in the Scala library.
* It converts an Option[X] to a Try[X] with the benefit of a default value.
*
* @param xo an Option[X].
* @param default the value of Try[X] to be returned in the event that xo is empty.
* @tparam X the underlying type of input and output.
* @return a Try[X]
*/
def toTry[X](xo: Option[X], default: => Try[X]): Try[X] = xo match {
case Some(x) => Success(x)
case None => default
}
/**
* This method fills a gap in the Scala library.
* It converts an Option[X] to a Try[X] with the benefit of a default value.
*
* @param xo an Option[X].
* @param default a Throwable to be returned as a Failure in the event that xo is empty.
* @tparam X the underlying type of input and output.
* @return a Try[X]
*/
def toTryWithThrowable[X](xo: Option[X], default: => Throwable): Try[X] = toTry(xo, Failure(default))
/**
* This method is a substitute for Try.apply in the case that we want it as a function
* (otherwise, we run into a type inference problem).
*
* @param x an X.
* @tparam X the type of x.
* @return Success(x)
*/
def identityTry[X](x: X): Try[X] = Success(x)
/**
* Method to yield a Failure.
*
* @param e a Throwable
* @tparam Z the underlying type of the result.
* @return a function of Z => Failure[Z] based on e
*/
def fail[X, Z](e: Throwable): X => Try[Z] = _ => Failure(e)
/**
* Method to yield a Failure.
*
* @param s a String
* @tparam Z the underlying type of the result.
* @return a function of Z => Failure[Z] based on NumberException(s)
*/
def fail[X, Z](s: String): X => Try[Z] = fail(NumberException(s))
/**
* Method to yield a partially lifted version of a Function1 as a function.
*
* @param f an X => Z.
* @tparam X the type of the input to f and the input to the resulting function.
* @tparam Z the type of the output from f and the underlying type of the resulting function.
* @return a function X => Try[Z]
*/
def tryF[X, Z](f: X => Z): X => Try[Z] = x => Try(f(x))
/**
* Method to yield a partially lifted version of a Function2 as a function.
*
* @param f an (X,Y) => Z.
* @tparam X the type of the first input to f and of the resulting function.
* @tparam Y the type of the second input to f and of the resulting function.
* @tparam Z the type of the output from f and the underlying type of the resulting function.
* @return a function X => Try[Z]
*/
def tryF[X, Y, Z](f: (X, Y) => Z): (X, Y) => Try[Z] = (x, y) => Try(f(x, y))
/**
* Method to yield an Option of T according to whether the predicate p yields true.
*
* @param p a predicate on T.
* @param t an actual value of T.
* @tparam T the type of t (and the underlying type of the result).
* @return Some(t) if p(t) is true, otherwise None.
*/
def optional[T](p: T => Boolean)(t: T): Option[T] = Some(t).filter(p)
/**
* Method to get the value of an Option[X] but throwing a given exception rather than the usual NoSuchElement.
*
* @param xo an optional value of X (called by name).
* @param t a throwable.
* @tparam X the underlying type of xo and the type of the result.
* @return the value of xo or throws t.
* @throws Throwable t
*/
def getOrThrow[X](xo: => Option[X], t: => Throwable): X = xo.getOrElse(throw t)
def readFromResource(filename: String, function: Array[String] => Option[String]): Try[Seq[BigInt]] =
TryUsing(FP.resource(filename) map (Source.fromURL(_))) {
source =>
val bn = implicitly[Numeric[BigInt]]
val wos: Iterator[Option[String]] = source.getLines().map(l => function(l.split("""\s""")))
val bos: Iterator[Option[BigInt]] = for (p <- wos) yield for (q <- p; qq <- bn.parseString(q)) yield qq
FP.toTry(FP.sequence(bos.toList), Failure(NumberException(s"invalid input in file: $filename")))
}
}
/**
* These converters are used by the tryMap and transpose.
*/
object Converters {
implicit def convertIntToRational(x: Int): Either[Option[Double], Rational] = Right(Rational(x))
implicit def convertRationalToOptionalDouble(x: Rational): Option[Double] = Try(x.toDouble).toOption
}
object TryUsing {
/**
* This method is to Using.apply as flatMap is to Map.
*
* @param resource a resource which is used by f and will be managed via Using.apply
* @param f a function of R => Try[A].
* @tparam R the resource type.
* @tparam A the underlying type of the result.
* @return a Try[A]
*/
def apply[R: Releasable, A](resource: => R)(f: R => Try[A]): Try[A] = Using(resource)(f).flatten
/**
* This method is similar to apply(r) but it takes a Try[R] as its parameter.
* The definition of f is the same as in the other apply, however.
*
* @param ry a Try[R] which is passed into f and will be managed via Using.apply
* @param f a function of R => Try[A].
* @tparam R the resource type.
* @tparam A the underlying type of the result.
* @return a Try[A]
*/
def apply[R: Releasable, A](ry: Try[R])(f: R => Try[A]): Try[A] = for (r <- ry; a <- apply(r)(f)) yield a
}