Created
August 29, 2017 21:50
-
-
Save kolemannix/a483024d6bfa72b60884315d9db6892c to your computer and use it in GitHub Desktop.
NonEmpty String trim example (Scala)
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
object Sanitizing { | |
case class NonEmptyString(s: String) { | |
require(s.nonEmpty) | |
} | |
type EmptiableStringMagnet = Either[String, Option[String]] | |
} | |
trait Sanitizing { | |
import Sanitizing._ | |
implicit def stringEmptiableStringWitness(s: String): EmptiableStringMagnet = Left(s) | |
implicit def optStringEmptiableStringWitness(s: Option[String]): EmptiableStringMagnet = Right(s) | |
private def superTrimImpl(s: String): Option[NonEmptyString] = s.trim match { | |
case trimmed if trimmed.isEmpty => None | |
case trimmed if trimmed.nonEmpty => Some(NonEmptyString(trimmed)) | |
} | |
def superTrim(s: EmptiableStringMagnet): Option[NonEmptyString] = s match { | |
case Left(s) => superTrimImpl(s) | |
case Right(maybeS: Option[String]) => maybeS.flatMap(superTrimImpl(_)) | |
} | |
/* Only needed if you want extension methods... "foo".supahTrim */ | |
implicit class StringOps(s: String) { | |
def supahTrim: Option[NonEmptyString] = superTrim(s) | |
} | |
implicit class OptionStringOps(s: Option[String]) { | |
def supahTrim: Option[NonEmptyString] = superTrim(s) | |
} | |
} | |
// Usage | |
object Example extends Sanitizing { | |
val foo = " foo " | |
val bar = Some(" ") | |
val fooTrimmed: Option[Sanitizing.NonEmptyString] = superTrim(foo) | |
val barTrimmed: Option[Sanitizing.NonEmptyString] = superTrim(bar) | |
println(fooTrimmed) // > Some(NonEmptyString("foo")) | |
println(barTrimmed) // > None | |
foo.supahTrim == superTrim(foo) // > true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The context of this gist was a discussion about a trim method that returns an Option, populated if the trimmed value still has content, but that can also accept either a String or Option[String]. Method overloading is the obvious solution, but this is more fun and would prove more scalable to large use cases (Magnet pattern vs Overloading is very well explored here: http://spray.io/blog/2012-12-13-the-magnet-pattern/)