
com.mchange.sc.v3.failable.Failable.scala Maven / Gradle / Ivy
package com.mchange.sc.v3.failable
import scala.collection._
import scala.util.Try
import scala.util.control.NonFatal
object Failable {
val Empty : Failable[Nothing] = Failed("EmptyFailable")("An attempt to filter or pattern-match a Failable failed, leaving EmptyFailable.", None);
def sequence[T]( failables : Seq[Failable[T]] ) : Failable[immutable.Seq[T]] = {
failables.foldLeft( succeed( immutable.Seq.empty[T] ) ){ ( fseq, fnext ) =>
fseq.flatMap( seq => fnext.map( next => seq :+ next ) )
}
}
def apply[T]( block : =>T ) = Try( block ).toFailable
def succeed[T]( result : T ) : Failable[T] = Succeeded( result )
def fail[S : Failed.Source]( source : S, includeStackTrace : Boolean = true ) : Failable[Nothing] = {
val ms = implicitly[Failed.Source[S]]
ms.getFailed( source, includeStackTrace )
}
/**
* A utility to re-establish the irrelevant right type as universally acceptable Nothing
*/
def refail( prefail : Failed[_] ) : Failable[Nothing] = prefail.asInstanceOf[Failable[Nothing]]
// the explicit provision of the implicit Failed.Source.Throwable param is apparently required when the definition of that parameter comes
// later in the compilation unit... Grrr.
//
// See e.g. https://stackoverflow.com/questions/2731185/why-does-this-explicit-call-of-a-scala-method-allow-it-to-be-implicitly-resolved
val ThrowableToFailed : PartialFunction[Throwable, Failable[Nothing]] = { case scala.util.control.NonFatal( t : Throwable ) => fail( t )( Failed.Source.ForThrowable ) }
}
sealed trait Failable[+T] {
def isEmpty : Boolean = this == Failable.Empty
def assertResult : T = assertSucceeded.result
def assertSucceeded : Succeeded[T] = {
try {
this.asInstanceOf[Succeeded[T]]
}
catch {
case cce : ClassCastException => this.asInstanceOf[Failed[T]].vomit
}
}
def assertFailed : Failed[T] = this.asInstanceOf[Failed[T]]
def assertThrowable : Throwable = this.assertFailed.toThrowable
def isFailed : Boolean = !isSucceeded;
def asFailed : Failed[T] = assertFailed
def asSucceeded : Succeeded[T] = assertSucceeded
def get : T = this.assertResult
def assert : T = this.assertResult
// monad ops
def flatMap[U]( f : T => Failable[U] ) : Failable[U]
def map[U]( f : T => U ) : Failable[U]
def withFilter( p : T => Boolean ) : Failable[T]
// extra ops
def exists( f : T => Boolean ) : Boolean
def forall( f : T => Boolean ) : Boolean
def foreach[U]( f : T => U ) : Any
def getOrElse[ TT >: T ]( or : =>TT ) : TT
def toOption : Option[T]
def toSeq : immutable.Seq[T]
def flatten[U](implicit evidence : T <:< Failable[U]) : Failable[U]
def recover[TT >: T]( f : Failed[T] => TT ) : Failable[TT]
def recoverWith[TT >: T]( defaultValue : TT ) : Failable[TT]
def orElse[TT >: T]( other : =>Failable[TT] ) : Failable[TT]
def fold[X]( ff : Failed[T] => X )( fr : T => X ) : X
def isSucceeded : Boolean
}
// kind of yuk, but we've renamed this from "Failure" to "Fail" to avoid inconvenient
// need to qualify names when working with scala.util.Failure.
final object Failed {
final object Source {
implicit val ForString = new Failed.Source[String] {
def getMessage( source : String ) : String = source;
}
implicit def forAnyThrowable[T <: Throwable] = new Failed.Source[T] {
def getMessage( source : T ) : String = s"${source.getClass.getName}: ${source.getMessage()}";
override def getStackTrace( source : T ) = source.getStackTrace;
}
implicit val ForThrowable = this.forAnyThrowable[Throwable]
}
trait Source[T] {
def getMessage( source : T ) : String
def getStackTrace( source : T ) : Array[StackTraceElement] = Thread.currentThread().getStackTrace()
def getFailed( source : T, includeStackTrace : Boolean = true ) : Failed[Nothing] = {
val mbStackTrace = if ( includeStackTrace ) Some( getStackTrace( source ) ) else None
Failed[Nothing]( source )( getMessage( source ), mbStackTrace )
}
}
def simple( message : String ) : Failed[Nothing] = Failed( message )( message, None );
val FromThrowable = Failable.ThrowableToFailed
}
import Failable.{ refail, ThrowableToFailed }
final case class Failed[+T]( val source : Any )( val message : String, val mbStackTrace : Option[Array[StackTraceElement]] ) extends Failable[T] {
override def toString() : String = "Failed: " + mbStackTrace.fold( message ) { stackTrace =>
(List( message ) ++ stackTrace).mkString( StackTraceElementSeparator )
}
def toThrowable : Throwable = {
source match {
case t : Throwable => t
case _ => new NonthrowableFailureException( this );
}
}
def vomit : Nothing = throw this.toThrowable
// monad ops
def flatMap[U]( f : T => Failable[U] ) : Failable[U] = refail( this )
def map[U]( f : T => U ) : Failable[U] = refail( this )
def withFilter( p : T => Boolean ) : Failable[T] = this
// extra ops
def exists( f : T => Boolean ) : Boolean = false
def forall( f : T => Boolean ) : Boolean = true
def foreach[U]( f : T => U ) : Any = ()
def getOrElse[ TT >: T ]( or : =>TT ) : TT = or
def toOption : Option[T] = None
def toSeq : immutable.Seq[T] = immutable.Seq.empty[T]
def flatten[U](implicit evidence : T <:< Failable[U]) : Failable[U] = refail( this )
def recover[TT >: T]( f : Failed[T] => TT ) : Failable[TT] = try { Succeeded( f( this ) ) } catch ThrowableToFailed
def recoverWith[TT >: T]( defaultValue : TT ) : Failable[TT] = Succeeded( defaultValue )
def orElse[TT >: T]( other : =>Failable[TT] ) : Failable[TT] = other
def fold[X]( ff : Failed[T] => X )( fr : T => X ) : X = ff( this )
def isSucceeded : Boolean = false
}
final case class Succeeded[+T]( result : T ) extends Failable[T] {
// monad ops
def flatMap[U]( f : T => Failable[U] ) : Failable[U] = try { f(result) } catch ThrowableToFailed
def map[U]( f : T => U ) : Failable[U] = try { Succeeded( f(result) ) } catch ThrowableToFailed
def withFilter( p : T => Boolean ) : Failable[T] = try { if ( p(result) ) this else Failable.Empty } catch ThrowableToFailed
// extra ops
def exists( f : T => Boolean ) : Boolean = f(result)
def forall( f : T => Boolean ) : Boolean = f(result)
def foreach[U]( f : T => U ) : Any = f(result)
def getOrElse[ TT >: T ]( or : =>TT ) : TT = result
def toOption : Option[T] = Some( result )
def toSeq : immutable.Seq[T] = immutable.Seq( result )
def flatten[U](implicit evidence : T <:< Failable[U]) : Failable[U] = evidence( result )
def recover[TT >: T]( f : Failed[T] => TT ) : Failable[TT] = this
def recoverWith[TT >: T]( defaultValue : TT ) : Failable[TT] = this
def orElse[TT >: T]( other : =>Failable[TT] ) : Failable[TT] = this
def fold[X]( ff : Failed[T] => X )( fr : T => X ) : X = fr( this.result )
def isSucceeded : Boolean = true
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy