or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async.mdfixtures.mdindex.mdmatchers.mdproperty.mdscalactic.mdtest-styles.md
tile.json

scalactic.mddocs/

Scalactic

Scalactic is a Scala library that provides functional programming utilities for equality, constraints, and error handling. It offers an alternative to exceptions with the Or type, value classes for ensuring constraints, and utilities for customizing equality comparisons.

Capabilities

Or Type (Union Types)

The Or type represents a value that can be one of two types - either a "Good" success value or a "Bad" error value.

/**
 * Union type representing either a good value or a bad value
 */
sealed abstract class Or[+G, +B] extends Product with Serializable {
  def isGood: Boolean
  def isBad: Boolean
  def get: G
  def getBadOrElse[BB >: B](default: => BB): BB
  
  /**
   * Transform the good value while preserving bad values
   */
  def map[H](f: G => H): H Or B
  
  /**
   * FlatMap operation for chaining Or operations
   */
  def flatMap[H, C >: B](f: G => H Or C): H Or C
  
  /**
   * Apply a function to transform bad values
   */
  def badMap[C](f: B => C): G Or C
  
  /**
   * Fold the Or value by applying functions to both cases
   */
  def fold[T](fa: B => T, fb: G => T): T
}

/**
 * Represents a successful result
 */
final case class Good[+G](get: G) extends Or[G, Nothing] {
  def isGood: Boolean = true
  def isBad: Boolean = false
}

/**
 * Represents an error result  
 */
final case class Bad[+B](get: B) extends Or[Nothing, B] {
  def isGood: Boolean = false
  def isBad: Boolean = true
}

Usage Examples:

import org.scalactic._

// Creating Or values
val goodResult: Int Or String = Good(42)
val badResult: Int Or String = Bad("Error occurred")

// Safe operations that return Or instead of throwing exceptions
def divide(x: Int, y: Int): Int Or String = {
  if (y == 0) Bad("Division by zero")
  else Good(x / y)
}

// Chaining operations with map
val result = Good(10) map (_ * 2) map (_ + 1)
// Result: Good(21)

// FlatMap for chaining operations that return Or
val chainedResult = for {
  a <- divide(10, 2)    // Good(5)
  b <- divide(a, 3)     // Bad("Division by zero") if a was 0
  c <- divide(b, 1)     // Only executes if previous succeeded
} yield c

// Pattern matching
goodResult match {
  case Good(value) => println(s"Success: $value")
  case Bad(error) => println(s"Error: $error")
}

Attempt Function

Safely execute code that might throw exceptions, wrapping results in Or.

/**
 * Execute code safely, wrapping exceptions in Bad and results in Good
 */
def attempt[R](f: => R): R Or Throwable

Usage Examples:

import org.scalactic._

// Safe string to integer conversion
val parseResult = attempt { "42".toInt }
// Result: Good(42)

val parseError = attempt { "not-a-number".toInt }  
// Result: Bad(NumberFormatException)

// Chaining with other operations
val computation = for {
  num <- attempt { "42".toInt }
  doubled <- Good(num * 2)
  result <- attempt { s"Result: $doubled" }
} yield result

Value Classes (AnyVals)

Type-safe wrappers that ensure values meet certain constraints without runtime overhead.

/**
 * A string that cannot be empty
 */
final class NonEmptyString private (val value: String) extends AnyVal {
  override def toString: String = value
}

object NonEmptyString {
  /**
   * Create a NonEmptyString from a regular string
   */  
  def from(value: String): NonEmptyString Or One[ErrorMessage]
  def apply(value: String): NonEmptyString  // Throws if empty
  def unapply(nonEmptyString: NonEmptyString): Some[String]
}

/**
 * A list that cannot be empty
 */
final class NonEmptyList[+T] private (val toList: List[T]) extends AnyVal {
  def head: T
  def tail: List[T]  
  def length: Int
  def map[U](f: T => U): NonEmptyList[U]
  def flatMap[U](f: T => NonEmptyList[U]): NonEmptyList[U]
}

object NonEmptyList {
  def apply[T](firstElement: T, otherElements: T*): NonEmptyList[T]
  def from[T](list: List[T]): NonEmptyList[T] Or One[ErrorMessage]
}

/**
 * Similar value classes for other collections
 */
final class NonEmptyVector[+T] private (val toVector: Vector[T]) extends AnyVal
final class NonEmptyArray[T] private (val toArray: Array[T]) extends AnyVal  
final class NonEmptySet[T] private (val toSet: Set[T]) extends AnyVal
final class NonEmptyMap[K, +V] private (val toMap: Map[K, V]) extends AnyVal

Usage Examples:

import org.scalactic.anyvals._

// Safe construction with error handling
val nameResult = NonEmptyString.from("John")
// Result: Good(NonEmptyString("John"))

val emptyResult = NonEmptyString.from("")  
// Result: Bad(One(""))

// Direct construction (throws if constraint violated)
val name = NonEmptyString("John Doe")
println(name.value)  // "John Doe"

// Working with NonEmptyList
val numbers = NonEmptyList(1, 2, 3, 4, 5)
val doubled = numbers.map(_ * 2)
val summed = numbers.flatMap(n => NonEmptyList(n, n * 10))

// Pattern matching
name match {
  case NonEmptyString(value) => println(s"Name: $value")
}

Equality and Constraints

Customizable equality comparisons and type constraints.

/**
 * Trait for defining custom equality
 */
trait Equality[A] {
  def areEqual(a: A, b: Any): Boolean
}

/**
 * Trait for defining equivalence relations
 */  
trait Equivalence[T] {
  def equiv(a: T, b: T): Boolean
}

/**
 * Default equality implementations
 */
object Equality {
  /**
   * Default equality based on universal equality
   */
  implicit def default[A]: Equality[A]
  
  /**
   * Tolerance-based equality for floating point numbers
   */
  def tolerantDoubleEquality(tolerance: Double): Equality[Double]
  def tolerantFloatEquality(tolerance: Float): Equality[Float]
}

/**
 * Triple equals with constraint checking
 */
trait TripleEquals {
  def ===[T](right: T)(implicit constraint: CanEqual[T, T]): Boolean
  def !==[T](right: T)(implicit constraint: CanEqual[T, T]): Boolean
}

/**
 * Type constraint for equality comparisons
 */
trait CanEqual[-A, -B] {
  def areEqual(a: A, b: B): Boolean
}

Usage Examples:

import org.scalactic._

// Custom equality for case-insensitive strings
implicit val stringEquality = new Equality[String] {
  def areEqual(a: String, b: Any): Boolean = b match {
    case s: String => a.toLowerCase == s.toLowerCase
    case _ => false
  }
}

// Triple equals with constraints
import TripleEquals._
"Hello" === "HELLO"  // true with custom equality

// Tolerance-based floating point comparison  
implicit val doubleEquality = Equality.tolerantDoubleEquality(0.01)
3.14159 === 3.14200  // true within tolerance

Normalization

Transform values before comparison or processing.

/**
 * Trait for normalizing values before operations
 */
trait Normalization[A] {
  def normalized(a: A): A
}

/**
 * Uniformity combines normalization with equality
 */
trait Uniformity[A] extends Normalization[A] with Equality[A] {
  final def areEqual(a: A, b: Any): Boolean = b match {
    case bAsA: A => normalized(a) == normalized(bAsA)  
    case _ => false
  }
}

/**
 * Pre-built string normalizations
 */
object StringNormalizations {
  /**
   * Normalize by trimming whitespace
   */
  val trimmed: Normalization[String]
  
  /**
   * Normalize to lowercase
   */
  val lowerCased: Normalization[String]
  
  /**
   * Remove all whitespace  
   */
  val removeAllWhitespace: Normalization[String]
}

Usage Examples:

import org.scalactic._
import StringNormalizations._

// Custom normalization
implicit val trimmedStringEquality = new Uniformity[String] {
  def normalized(s: String): String = s.trim
}

// Using pre-built normalizations
val normalizer = lowerCased and trimmed
val result = normalizer.normalized("  HELLO WORLD  ")
// Result: "hello world"

Requirements and Validation

Assertion-like functionality that returns results instead of throwing exceptions.

trait Requirements {
  /**
   * Require a condition, returning Good(Unit) or Bad with message
   */
  def require(condition: Boolean): Unit Or ErrorMessage
  def require(condition: Boolean, message: => Any): Unit Or ErrorMessage
  
  /**
   * Require non-null value
   */
  def requireNonNull[T](obj: T): T Or ErrorMessage
  def requireNonNull[T](obj: T, message: => Any): T Or ErrorMessage
}

object Requirements extends Requirements

Usage Examples:

import org.scalactic.Requirements._

// Validation with requirements
def createUser(name: String, age: Int): User Or ErrorMessage = {
  for {
    _ <- require(name.nonEmpty, "Name cannot be empty")
    _ <- require(age >= 0, "Age cannot be negative")  
    _ <- require(age <= 150, "Age must be realistic")
  } yield User(name, age)
}

val validUser = createUser("John", 25)    // Good(User("John", 25))
val invalidUser = createUser("", -5)      // Bad("Name cannot be empty")

Types

/**
 * Type alias for error messages
 */
type ErrorMessage = String

/**
 * Container for a single error message
 */
final case class One[+T](value: T) extends AnyVal

/**
 * Container for multiple error messages
 */  
final case class Many[+T](values: Iterable[T]) extends AnyVal

/**
 * Union of One and Many for error accumulation
 */
type Every[+T] = One[T] Or Many[T]