I'm implementing a library for kadmin commands. With, amongst other things, the following methods:
def changePassword(principal: String, newPassword: String): Either[ErrorCase, Boolean]
def deletePrincipal(principal: String): Either[ErrorCase, Boolean]
These two operations might return an error, so their return type is Either[ErrorCase, Boolean]. Where ErrorCase is defined as:
trait ErrorCase
case object InsufficientPermissions extends ErrorCase
case object PrincipalDoesNotExist extends ErrorCase
case object IncorrectPassword extends ErrorCase
case object PasswordTooShort extends ErrorCase
case object PasswordWithoutEnoughCharacterClasses extends ErrorCase
case object PasswordIsBeingReused extends ErrorCase
case object PasswordExpired extends ErrorCase
case object UnknownError extends ErrorCase
My problem is: if I define the trait ErrorCase to be a sealed trait, I will be putting a burden on the user of the API to check all the possible ErrorCases when he/she invokes one of the API methods. This makes sense for the changePassword method, because all of these error cases might happen in this operation. But for the deletePrincipal method this makes no sense since all the error cases related with passwords will never happen. In other words, the API methods share error cases, but every method does not necessarily use every error case.
How can I make the trait sealed but somehow specify that in each of the methods only some of the ErrorCases will be used.
Any problem in computer science can be solved with another level of
indirection, except the problem of too many levels of indirection.
sealed trait ErrorCase
sealed trait PasswordErrors extends ErrorCase
sealed trait OtherErrors extends ErrorCase
case object IncorrectPassword extends PasswordErrors
case object PasswordTooShort extends PasswordErrors
case object PasswordWithoutEnoughCharacterClasses extends PasswordErrors
case object PasswordIsBeingReused extends PasswordErrors
case object PasswordExpired extends PasswordErrors
case object InsufficientPermissions extends OtherErrors
case object PrincipalDoesNotExist extends OtherErrors
case object UnknownError extends OtherErrors
Not sure what's the best way to handle the UnknownError (subclass of OtherErrors, or of ErrorCase directly, or something else), but it's up to you to figure this out.
I will be putting a burden on the user of the API to check all the possible ErrorCases when he/she invokes one of the API methods
Why? They can just match on _ to handle all error cases at once.
deletePrincipal("me") match {
case Right(bool) => ???
case Left(PrincipalDoesNotExist) => ???
case _ => ???
}
The only problem I see is if you're providing a catamorphism for ErrorCase (i.e. a fold method). But then again, a fold with 8 parameters is pretty awful anyway.
Related
Is there any way to describe the type in scala for case classes companion-objects of particular type ?
For example I have
trait SomeTrait
case class Foo() extends SomeTrait
case class Bar() extends SomeTrait
And I need to get common type for companion object of Foo and Bar
like [Foo.type & Bar.type]
You can create another trait that the companion objects will extend. If you need to make the companion objects pattern matchable, add case in front of them, and make the trait they extend sealed for exhaustivity reasons:
sealed trait SomeTrait
sealed trait AnotherTrait
case class Foo() extends SomeTrait
case class Bar() extends SomeTrait
case object Foo extends AnotherTrait
case object Bar extends AnotherTrait
And for example you can see which object you pattern matched with something like:
def methodName(x: AnotherTrait): String = x match {
case Foo => "it is Foo singleton"
case Bar => "it is Bar singleton"
}
println(methodName(Foo)) // it is Foo singleton
I have two layers in my app: domain and application. Each layer has its own "error" ADT. For instance:
package com.domain.person
sealed trait DomainError
case object NoPermission extends DomainError
final case class Person(hasPermission: Boolean): Either[DomainError, ???] {
def doSomething() = {
if (!hasPermission)
Left(NoPermission)
else
...
}
}
and in my application layer (another package):
package com.application.person
sealed trait ApplicationError
case object PersonNotFound extends ApplicationError
case object UnexpectedFatalError extends ApplicationError
// and a function f :: Either ApplicationError Something
The issue is, since DomainError lives in another package, I can't just simply extend my ApplicationError trait:
sealed trait ApplicationError extends DomainError // compilation error
I could create yet another case object to wrap DomainError:
sealed trait ApplicationError
// n list of errors, and then:
final case class WrappedDomainError(d: DomainError) extends ApplicationError
but that solution is suboptimal at best.
And also, what if I want to be more specific in my doSomething() and, instead of returning a whole DomainError, a different subset?
doSomething :: Either DoSomethingErrors ???
I would have to account for all cases in each of my domain layer's functions.
Is there any way I can do a proper sum type in Scala?
Thanks
Wrapping your domain error in application error is not a bad idea, TBH. It's what I would've done in your situation. A few more options to consider:
make your DomainError and ApplicationError extends a common supertype Error, CommonError, Failure, etc. My personal preference is to extend Throwable - this way your error ASTs can become isomorphic to exceptions which can come in handy for Java interop reasons.
error channel also being composed of unions. Your final type will look somewhat like Either[ApplicationError Either DomainError, A]. It's a bit mouthful but you can make it look less ugly by introducing aliases.
type Result[+A] = Either[ApplicationError Either DomainError, A]
def doSomething: Result[???]
replace either with your own AST or use scalaz or other library's alternatives to Either3
sealed trait Result[+A]
case class Success[A](a: A) extends Result[A]
case class ApplicationErr(err: ApplicationError) extends Result[Nothing]
case class DomainErr[A](err: DomainErr) extends Result[Nothing]
def doSomething: Result[???]
Interpret DomainErrors into ApplicationErrors
val maybeDomainErrorVal: Either[DomainError, ???] = ???
val maybeApplicationErrorVal: Either[ApplicationError, ???] =
maybeDomainErrorVal.leftMap {
case NoPermission => UnexpectedFatalError
}
I'm implementing scala interpreter and I have some problem.
To begin with, I want to implement tuple class
In my Value.scala, followed class exist:
case class TupleV(values: List[Type]) extends Value
Also, in my Expr.scala, followed class exist
case class TupleE(expressions: List[Type] extends Expr
Type is also in Expr.scala as followed:
sealed trait Type
case object IntT extends Type
case object BooleanT extends Type
case object TupleT extends Type
case object ListT extends Type
case object FunctionT extends Type
So, I implemented as followed:
case TupleE(values)=>TupleV(values)
but it says List[Expr] found, List[Value] is required. What is the problem in my code? what should I to to fix it?
Also I tried to implement in different way using ConsE and ConsV class which enable me to divide list into head and tail:
case class ConsE(head: Expr, tail: Expr) extends Expr//in Expr.scala
case class ConsV(head: Value, tail: Value) extends Value//in Value.scala
case ConsE(head, tail)=>ConsV(interp(head), interp(tail)//my implementation for ConsV interpreter&works well now
Using that, I tried
case TupleE(expression)=>expression match{
case ConsE(head, tail)=>ConsV(head, tail)
}
But it returns: "constructor cannot be instantiated to expected type" error for ConsE. How should I fix it?
Try to replace
case class TupleV(values: List[Type]) extends Value
case class TupleE(expressions: List[Type]) extends Expr
with
case class TupleV(values: List[Value]) extends Value
case class TupleE(expressions: List[Expr]) extends Expr
(Are those definitions given to you in an exercise or are they your own definitions that you can modify?)
Based on
case TupleE(values)=>TupleV(values)
you're writing interpreter
def interpret(expr: Expr): Value
i.e. continue what you started in How should I implement "add" interpreter in scala?
Then the definitions of TupleV, TupleE should be latter above. Former their definitions above make less sense to me.
Type is needed when you typecheck an Expr
def typecheck(expr: Expr): Type // or Option[Type]
Then you'll have one more hierarchy
case class TupleT(types: List[Type]) extends Type
So, I implemented as followed:
case TupleE(values)=>TupleV(values)
but it says List[Expr] found, List[Value] is required. What is the
problem in my code? what should I to to fix it?
This is irreproducible. The code compiles
https://scastie.scala-lang.org/KWlyFOYDRHOgN6UibVY1pw
Using that, I tried
case TupleE(expression)=>expression match{
case ConsE(head, tail)=>ConsV(head, tail)
}
But it returns: "constructor cannot be instantiated to expected type"
error for ConsE.
This error is clear: in TupleE(expression) expression has type List[Type], it can't match ConsE, it can match only ordinary scala List.
I am currently using Enumeratum 1.5.13 in Scala 2.12.6. I have the following defined:
sealed abstract class PartOfSpeech(val value: Int) extends IntEnumEntry
case object PartOfSpeech extends IntEnum[PartOfSpeech] {
val values = findValues
case object Noun extends PartOfSpeech(0)
case object Adjective extends PartOfSpeech(1)
case object Verb extends PartOfSpeech(2)
case object Adverb extends PartOfSpeech(3)
case object Numeric extends PartOfSpeech(4)
case object Exclamation extends PartOfSpeech(5)
case object Preposition extends PartOfSpeech(6)
case object Pronoun extends PartOfSpeech(7)
case object Conjunction extends PartOfSpeech(8)
case object Determiner extends PartOfSpeech(9)
case object Article extends PartOfSpeech(10)
}
Then, when I attempt to use the withName() method like this:
val pos = PartOfSpeech.withName("Noun")
...I receive a compilation error indicating the method withName is not found. So, given I don't see a ScalaDoc for Enumeratum (at least I have not been able to find them), I don't know how to answer this without digging through its source code. Before investing time in that, I thought I might see if someone else has a simple and/or obvious solution.
What you are looking for was implemented on version 1.7.0 of Enumeratum.
you have 2 options to work with:
EnumEntry with "withName" function.
StringEnumEntry with "withValue" function.
In both ways you can get your needs
// Option 1:
sealed trait PartOfSpeech(val value: Int) extends EnumEntry
case object PartOfSpeech extends Enum[PartOfSpeech] {
val values = findValues
case object Noun extends PartOfSpeech(0)
case object Adjective extends PartOfSpeech(1)
case object Verb extends PartOfSpeech(2)
case object Adverb extends PartOfSpeech(3)
}
val pos = PartOfSpeech.withName("Noun") // return 0
// Option 2:
sealed trait PartOfSpeech(val value: String, val index: Int) extends StringEnumEntry
case object PartOfSpeech extends StringEnum[PartOfSpeech] {
val values = findValues
case object Noun extends PartOfSpeech("Noun", 0)
case object Adjective extends PartOfSpeech("Adjective", 1)
case object Verb extends PartOfSpeech("Verb", 2)
case object Adverb extends PartOfSpeech("Adverb", 3)
}
val pos = PartOfSpeech.withValue("None") // return 0
From what I can tell, the only way to make this work is for me to implement it myself within the case object. So, I added this:
private val enumEntryByClassSimpleNameLowerCase: Map[String, PartOfSpeech] =
values.map(enumEntry => (enumEntry.getClass.getSimpleName.takeWhile(_ != '$').toLowerCase, enumEntry)).toMap
def withName(value: String): Option[PartOfSpeech] =
enumEntryByClassSimpleNameLowerCase.get(value.toLowerCase)
It feels like a hack. I sure hope there's something inside of Enumeratum I am just missing. Please let me know if there is a better way to achieve this outcome. I use enums a whole bunch. So, this boilerplate is going to leak all over my codebase.
I'm currently learning Scala, and wanted to replicate this Haskell algebraic data type:
data Tree = Empty
| Leaf Int
| Node Tree Tree
This is what I came up with in Scala:
sealed trait Tree[T]
case class Empty[T]() extends Tree[T]
case class Leaf[T](value: T) extends Tree[T]
case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T]
However, someone told me that I should use a case object for Empty, which I suppose is true since it doesn't take parameters - but then again it does require a type parameter.
I tried the following but none of them compile:
case object Empty[T] extends Tree[T]
case object Empty extends Tree[T]
case object Empty extends Tree
So I'm wondering if there a way to use case object in this instance or not.
A singleton can't be generic because there's only one of them. If you want Tree to be covariant (i.e. Tree[Int] is a subtype of Tree[Any]), then you can define the types as
sealed trait Tree[+T]
case object Empty extends Tree[Nothing]
Otherwise, leave it as a case class.