Skip to content

Instantly share code, notes, and snippets.

@eyalroth
Last active February 9, 2020 10:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eyalroth/0e36f3ac942c73fbabf5f0f9002816db to your computer and use it in GitHub Desktop.
Save eyalroth/0e36f3ac942c73fbabf5f0f9002816db to your computer and use it in GitHub Desktop.
Scala3 - nested ADTs example - Response
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
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
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
}
}
}
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