Last active
February 9, 2020 10:47
-
-
Save eyalroth/0e36f3ac942c73fbabf5f0f9002816db to your computer and use it in GitHub Desktop.
Scala3 - nested ADTs example - Response
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.util.control.NonFatal | |
sealed trait Response[+A] { | |
def returnValue: Option[A] | |
def map[B](f: A => B): Response[B] | |
def flatMap[B](f: A => Response[B]): Response[B] | |
} | |
case class Success[+A](value: A) extends Response[A] { | |
override val returnValue: Option[A] = value match { | |
case _: Unit => None | |
case _ => Option(value) | |
} | |
override def map[B](f: (A) => B): Response[B] = | |
try { | |
Success(f(value)) | |
} catch { | |
case NonFatal(e) => GenericFailure(s"Failed to map service response with value: $value", Some(e)) | |
} | |
override def flatMap[B](f: A => Response[B]): Response[B] = { | |
f(value) | |
} | |
} | |
object Success { | |
def empty: Success[Unit] = Success(()) | |
} | |
trait NoValueResponse { | |
self: Response[Nothing] => | |
override val returnValue: Option[Nothing] = None | |
override def map[B](f: (Nothing) => B): Response[B] = this | |
override def flatMap[B](f: (Nothing) => Response[B]): Response[B] = this | |
} | |
object NoChange extends Response[Nothing] with NoValueResponse | |
object Accepted extends Response[Nothing] with NoValueResponse | |
sealed trait Failure extends Response[Nothing] with NoValueResponse { | |
def message: String | |
} | |
case class NoSuchElement(message: String) extends Failure | |
case class IllegalArgument(message: String, cause: Option[Throwable] = None) extends Failure | |
case class GenericFailure(message: String, cause: Option[Throwable] = None) extends Failure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.util.control.NonFatal | |
/** | |
* A semi-monad representing the response of a service's operation, which may or may not contain a return value. | |
* | |
* @tparam A The type of the (optional) return value. | |
*/ | |
sealed trait Response[+A] { | |
/* --- Methods --- */ | |
/* --- Public Methods --- */ | |
/** | |
* @return The optional return-value. | |
*/ | |
def returnValue: Option[A] | |
/** | |
* @param f The given function. | |
* @tparam B The type of the given function's return value. | |
* @return The result of applying the given function to this response's return value, or the response itself if it | |
* has no return value. | |
*/ | |
def map[B](f: A => B): Response[B] | |
/** | |
* @param f The given function. | |
* @tparam B The type of the given function's response's return value. | |
* @return The result of applying the given function to this response's return value, or the response itself if it | |
* has no return value. | |
*/ | |
def flatMap[B](f: A => Response[B]): Response[B] | |
} | |
/** | |
* A response indicating that the operation has been successful. | |
* | |
* @tparam A The type of the (optional) return value. | |
*/ | |
case class Success[+A](value: A) extends Response[A] { | |
/* --- Methods --- */ | |
/* --- Public Methods --- */ | |
override val returnValue: Option[A] = value match { | |
case _: Unit => None | |
case _ => Option(value) | |
} | |
override def map[B](f: (A) => B): Response[B] = | |
try { | |
Success(f(value)) | |
} catch { | |
case NonFatal(e) => GenericFailure(s"Failed to map service response with value: $value", Some(e)) | |
} | |
override def flatMap[B](f: A => Response[B]): Response[B] = { | |
f(value) | |
} | |
} | |
object Success { | |
/* --- Constructors --- */ | |
def empty: Success[Unit] = Success(()) | |
} | |
/** | |
* A response with no return value. | |
*/ | |
trait NoValueResponse { | |
self: Response[Nothing] => | |
/* --- Methods --- */ | |
/* --- Public Methods --- */ | |
override val returnValue: Option[Nothing] = None | |
override def map[B](f: (Nothing) => B): Response[B] = this | |
override def flatMap[B](f: (Nothing) => Response[B]): Response[B] = this | |
} | |
/** | |
* A response indicating that the operation -- although it did not fail -- has made no changes to the service's | |
* resources. | |
*/ | |
object NoChange extends Response[Nothing] with NoValueResponse | |
/** | |
* A response indicating that the operation has been successfully accepted by the service. | |
*/ | |
object Accepted extends Response[Nothing] with NoValueResponse | |
/** | |
* A response indicating that the operation has failed. | |
*/ | |
sealed trait Failure extends Response[Nothing] with NoValueResponse { | |
/* --- Methods --- */ | |
/* --- Public Methods --- */ | |
/** | |
* @return A message which details the reason for the failure. | |
*/ | |
def message: String | |
} | |
/** | |
* A response of an operation which have failed to locate one of the requested element. | |
* | |
* @param message A detailed message of the failure. | |
* @see [[NoSuchElementException]] | |
*/ | |
case class NoSuchElement(message: String) extends Failure | |
/** | |
* A response of an operation which have received an illegal or inappropriate argument. | |
* | |
* @param message A detailed message of the failure. | |
* @param cause An optional throwable with further details of the failure. | |
* @see [[IllegalArgumentException]] | |
*/ | |
case class IllegalArgument(message: String, cause: Option[Throwable] = None) extends Failure | |
/** | |
* A response of a generic (non-specific) failure of an operation. | |
* | |
* @param message A detailed message of the failure. | |
* @param cause An optional throwable with further details of the failure. | |
*/ | |
case class GenericFailure(message: String, cause: Option[Throwable] = None) extends Failure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Response[+A] { | |
case Success(value: A) | |
case sealed trait NoValueResponse { | |
case NoChange | |
case Accepted | |
case sealed trait Failure { | |
case NoSuchElement(message: String) | |
case IllegalArgument(message: String, cause: Option[Throwable] = None) | |
case GenericFailure(message: String, cause: Option[Throwable] = None) | |
} | |
} | |
} | |
object Response { | |
implicit class Ops[A](response: Response[A]) { | |
def returnValue: Option[A] = response match { | |
case Success(value) => value match { | |
case _: Unit => None | |
case _ => Option(value) | |
} | |
case _: NoValueResponse => None | |
} | |
def map[B](f: A => B): Response[B] = response match { | |
case Success(value) => | |
try { | |
Success(f(value)) | |
} catch { | |
case NonFatal(e) => GenericFailure(s"Failed to map service response with value: $value", Some(e)) | |
} | |
case noValue: NoValueResponse => noValue | |
} | |
def flatMap[B](f: A => Response[B]): Response[B] = response match { | |
case Success(value) =>f(value) | |
case noValue: NoValueResponse => noValue | |
} | |
} | |
} | |
object Success { | |
def empty: Success[Unit] = Success(()) | |
} | |
object Failure | |
implicit class Ops(failure: Failure) { | |
def message: String = failure match { | |
case NoSuchElement(message) => message | |
case IllegalArgument(message, _) => message | |
case GenericFailure(message, _) => message | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Response[+A] { | |
def returnValue: Option[A] | |
def map[B](f: A => B): Response[B] | |
def flatMap[B](f: A => Response[B]): Response[B] | |
case Success(value: A) { | |
override val returnValue: Option[A] = value match { | |
case _: Unit => None | |
case _ => Option(value) | |
} | |
override def map[B](f: (A) => B): Response[B] = | |
try { | |
Success(f(value)) | |
} catch { | |
case NonFatal(e) => GenericFailure(s"Failed to map service response with value: $value", Some(e)) | |
} | |
override def flatMap[B](f: A => Response[B]): Response[B] = { | |
f(value) | |
} | |
} | |
case sealed trait NoValueResponse { | |
override val returnValue: Option[Nothing] = None | |
override def map[B](f: (Nothing) => B): Response[B] = this | |
override def flatMap[B](f: (Nothing) => Response[B]): Response[B] = this | |
case NoChange | |
case Accepted | |
case sealed trait Failure { | |
def message: String | |
case NoSuchElement(message: String) | |
case IllegalArgument(message: String, cause: Option[Throwable] = None) | |
case GenericFailure(message: String, cause: Option[Throwable] = None) | |
} | |
} | |
} | |
object Success { | |
def empty: Success[Unit] = Success(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment