Here are my two cents with a full example.
I assume you want to do this in nice-and-easy functional style, so let's remove the mutable ListBuffer and see how to deal with it. The description of the steps are after the code.
UPDATE: I want to note that, in your code, the usage of ListBuffer is acceptable because it doesn't break rerefential transparency. This is because you don't expose its mutable nature outside the function and the output of the function depends only on its inputs.
Nonetheless I usually prefer to avoid using mutable data if not for specific reasons, like compactness or performance.
object StackOverflowAnswer {
/* Base type with some common fields */
class MyError( val code: String, val msg: String )
/* FileNotFound type, subtype of MyError, with its specific fields */
case class FileNotFound( filename: String ) extends MyError(
"ERR01", s"$filename not found"
)
/* UserDoesntExist type, subtype of MyError, with its specific fields */
case class UserDoesntExist( userName: String ) extends MyError(
"ERR01", s"$userName doesn't exist"
)
/*
* Validates the file. If it finds an error it returns a Some(MyError) with
* the error that's been found
* */
def checkForBadFile( data: String ): Option[MyError] =
if( data.contains("bad_file.txt") )
Some(FileNotFound("bad_file.txt"))
else
None
/*
* Validates the user. If it finds an error it returns a Some(MyError) with
* the error that's been found
* */
def checkForMissingUser( data: String ): Option[MyError] =
if( data.contains("bad_username") )
Some(UserDoesntExist("bad_username"))
else
None
/*
* Performs all the validations and returns a list with the errors.
*/
def validate( data: String ): List[MyError] = {
val fileCheck = checkForBadFile( data )
val userCheck = checkForMissingUser( data )
List( fileCheck, userCheck ).flatten
}
/* Run and test! */
def main( args: Array[String] ): Unit = {
val goodData = "This is a text"
val goodDataResult = validate( goodData ).map( _.msg )
println(s"The checks for '$goodData' returned: $goodDataResult")
val badFile = "This is a text with bad_file.txt"
val badFileResult = validate( badFile ).map( _.msg )
println(s"The checks for '$badFile' returned: $badFileResult")
val badUser = "This is a text with bad_username"
val badUserResult = validate( badUser ).map( _.msg )
println(s"The checks for '$badUser' returned: $badUserResult")
val badBoth = "This is a text with bad_file.txt and bad_username"
val badBothResult = validate( badBoth ).map( _.msg )
println(s"The checks for '$badBoth' returned: $badBothResult")
}
}
I start defining the type structure for the error, similar to your.
Then I have two functions that performs validations for each check you want. When they find an error, they return it using the Option type of Scala. If you're not familiar with Option, you can have a look at this link or do some googling.
Then I have the validation function that calls and store each the above single checks. The last bit is the use of flatten (doc here) that "flattens" List[Option[MyError]] into List[MyError], removing the middle Option.
Then there is the actual code that shows you some examples.
: List[Error] {- what, this is supposed to compile, really? And... no, that's not following functional programming style. In functional programming style you would concentrate on solving the problem, and stash all the annoying error accumulation away in some applicative effect. Yourvalidatedoesn't seem to produce anything useful, all it returns are errors.Validateduseful (even though I cannot guarantee that it will be pleasant to use with 2.11).