Tips on organizing functions in Scala


#1

I’ve recently got interested in writing more functional and function-oriented code in general.

I’m just getting started but I constantly feel like I were reinventing the wheel when thinking on some problems. Right now my biggest concern is code organization. I have A LOT of small functions that do some simple processing (as a motivating example: String validation - suppose I’ve build hundreds of small functions checking whether string is uppercased, lowercased etc.). I use descriptive object names to group them together in some subcategories.

Now, I want to use my small functions to solve real problems. Consider validating a form (case class Form). I’ve heard that it is generally discouraged to use classes with methods - thus I create another object that holds separate functions for each field - and these smaller functions are combined in yet another function that validates object as a whole. All this code is this case class specific and I won’t be able to reuse it anywhere else. For every other class I’d like to validate I have to prepare analogous object. The codebase grows - this logic is not contained in the class itself, is probably stored in separate file. Yet it to my OOP mind it looks more logical to put this code inside a class. Or at least define some Validator interface and implement instance for this class.

I find it really difficult not to get lost in this approach to coding. What I am asking for are some tips / example projects / books on organizing this kind of code. This question might seem naive but I don’t have any mentor whatsoever to guide me in the right direction.


#2

As described, I would typically put those functions into the companion object for the case class. And I would look for common refactorings to reduce the amount of duplication – it’s not unusual to define an object that inherits from a trait where common functionality lives.

It’s relatively advanced, but you might also want to think about looking into Shapeless, which can be really great at reducing this sort of boilerplate – it allows you to just plug in your case class, and run through the fields based on their types.


#3

The trait we are talking about has all methods implemented, am I right? Like EmailValidation (further composed of simple string validators) could be reused (mixed) in several such companions?


#4

Possibly, although it would be very common for the trait to leave one or two things abstract, to be filled in by the concrete objects, and providing functionality built on top of those abstract functions.

(Although I’ll note that we’re speaking at such a hypothetical level here that this is all hand-waving…)


#5

There a bunch of ways and abstractions to implement validation, a more conservative would be use traits for such common checks like your String validations and then compose them to a higher level. Here is a example from what I understood. (Not tested)

Some string validation

package validators

trait StringValidator {
  def isLowerCase(value: String): Boolean =
    value.toLowerCase == value

  def isUrlSafe(value: String): Boolean =
    value.matches("^[a-zA-Z0-9_-]*$")
}

Some random password validation

package validators

trait PasswordValidator {

  private val minLength = 6

  private val maxLength = 30

  def withinBounds(pass: String): Boolean = isInBetween(pass.length)

  private def isInBetween(length: Int) =
    length <= maxLength && length >= minLength

}

A common interface so you know who can be called to be validated

package validators

trait Validatable {

  def isValid: Boolean

}

import validators.{PasswordValidator, StringValidator, Validatable}

case class CredentialsForm(username: String, password: String)
  extends Validatable with StringValidator with PasswordValidator {

  override def isValid: Boolean = {
    isLowerCase(username) && isUrlSafe(username) && withinBounds(password)
  }
}

Note that those functions on traits are pure, and it is ok to put methods inside your case class, if they don’t mess up with their immutability, for example once constructed the Form the isValid method will hold the same value always.

Even Shapeless could solve that beautifully, it is not a good idea right now, since you are beginning with Scala, start with your pure functions that’s the base, then when you end up refactoring and understanding what Shapeless is doing then go for it!