or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

scalactic.mddocs/

0

# Scalactic

1

2

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.

3

4

## Capabilities

5

6

### Or Type (Union Types)

7

8

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

9

10

```scala { .api }

11

/**

12

* Union type representing either a good value or a bad value

13

*/

14

sealed abstract class Or[+G, +B] extends Product with Serializable {

15

def isGood: Boolean

16

def isBad: Boolean

17

def get: G

18

def getBadOrElse[BB >: B](default: => BB): BB

19

20

/**

21

* Transform the good value while preserving bad values

22

*/

23

def map[H](f: G => H): H Or B

24

25

/**

26

* FlatMap operation for chaining Or operations

27

*/

28

def flatMap[H, C >: B](f: G => H Or C): H Or C

29

30

/**

31

* Apply a function to transform bad values

32

*/

33

def badMap[C](f: B => C): G Or C

34

35

/**

36

* Fold the Or value by applying functions to both cases

37

*/

38

def fold[T](fa: B => T, fb: G => T): T

39

}

40

41

/**

42

* Represents a successful result

43

*/

44

final case class Good[+G](get: G) extends Or[G, Nothing] {

45

def isGood: Boolean = true

46

def isBad: Boolean = false

47

}

48

49

/**

50

* Represents an error result

51

*/

52

final case class Bad[+B](get: B) extends Or[Nothing, B] {

53

def isGood: Boolean = false

54

def isBad: Boolean = true

55

}

56

```

57

58

**Usage Examples:**

59

60

```scala

61

import org.scalactic._

62

63

// Creating Or values

64

val goodResult: Int Or String = Good(42)

65

val badResult: Int Or String = Bad("Error occurred")

66

67

// Safe operations that return Or instead of throwing exceptions

68

def divide(x: Int, y: Int): Int Or String = {

69

if (y == 0) Bad("Division by zero")

70

else Good(x / y)

71

}

72

73

// Chaining operations with map

74

val result = Good(10) map (_ * 2) map (_ + 1)

75

// Result: Good(21)

76

77

// FlatMap for chaining operations that return Or

78

val chainedResult = for {

79

a <- divide(10, 2) // Good(5)

80

b <- divide(a, 3) // Bad("Division by zero") if a was 0

81

c <- divide(b, 1) // Only executes if previous succeeded

82

} yield c

83

84

// Pattern matching

85

goodResult match {

86

case Good(value) => println(s"Success: $value")

87

case Bad(error) => println(s"Error: $error")

88

}

89

```

90

91

### Attempt Function

92

93

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

94

95

```scala { .api }

96

/**

97

* Execute code safely, wrapping exceptions in Bad and results in Good

98

*/

99

def attempt[R](f: => R): R Or Throwable

100

```

101

102

**Usage Examples:**

103

104

```scala

105

import org.scalactic._

106

107

// Safe string to integer conversion

108

val parseResult = attempt { "42".toInt }

109

// Result: Good(42)

110

111

val parseError = attempt { "not-a-number".toInt }

112

// Result: Bad(NumberFormatException)

113

114

// Chaining with other operations

115

val computation = for {

116

num <- attempt { "42".toInt }

117

doubled <- Good(num * 2)

118

result <- attempt { s"Result: $doubled" }

119

} yield result

120

```

121

122

### Value Classes (AnyVals)

123

124

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

125

126

```scala { .api }

127

/**

128

* A string that cannot be empty

129

*/

130

final class NonEmptyString private (val value: String) extends AnyVal {

131

override def toString: String = value

132

}

133

134

object NonEmptyString {

135

/**

136

* Create a NonEmptyString from a regular string

137

*/

138

def from(value: String): NonEmptyString Or One[ErrorMessage]

139

def apply(value: String): NonEmptyString // Throws if empty

140

def unapply(nonEmptyString: NonEmptyString): Some[String]

141

}

142

143

/**

144

* A list that cannot be empty

145

*/

146

final class NonEmptyList[+T] private (val toList: List[T]) extends AnyVal {

147

def head: T

148

def tail: List[T]

149

def length: Int

150

def map[U](f: T => U): NonEmptyList[U]

151

def flatMap[U](f: T => NonEmptyList[U]): NonEmptyList[U]

152

}

153

154

object NonEmptyList {

155

def apply[T](firstElement: T, otherElements: T*): NonEmptyList[T]

156

def from[T](list: List[T]): NonEmptyList[T] Or One[ErrorMessage]

157

}

158

159

/**

160

* Similar value classes for other collections

161

*/

162

final class NonEmptyVector[+T] private (val toVector: Vector[T]) extends AnyVal

163

final class NonEmptyArray[T] private (val toArray: Array[T]) extends AnyVal

164

final class NonEmptySet[T] private (val toSet: Set[T]) extends AnyVal

165

final class NonEmptyMap[K, +V] private (val toMap: Map[K, V]) extends AnyVal

166

```

167

168

**Usage Examples:**

169

170

```scala

171

import org.scalactic.anyvals._

172

173

// Safe construction with error handling

174

val nameResult = NonEmptyString.from("John")

175

// Result: Good(NonEmptyString("John"))

176

177

val emptyResult = NonEmptyString.from("")

178

// Result: Bad(One(""))

179

180

// Direct construction (throws if constraint violated)

181

val name = NonEmptyString("John Doe")

182

println(name.value) // "John Doe"

183

184

// Working with NonEmptyList

185

val numbers = NonEmptyList(1, 2, 3, 4, 5)

186

val doubled = numbers.map(_ * 2)

187

val summed = numbers.flatMap(n => NonEmptyList(n, n * 10))

188

189

// Pattern matching

190

name match {

191

case NonEmptyString(value) => println(s"Name: $value")

192

}

193

```

194

195

### Equality and Constraints

196

197

Customizable equality comparisons and type constraints.

198

199

```scala { .api }

200

/**

201

* Trait for defining custom equality

202

*/

203

trait Equality[A] {

204

def areEqual(a: A, b: Any): Boolean

205

}

206

207

/**

208

* Trait for defining equivalence relations

209

*/

210

trait Equivalence[T] {

211

def equiv(a: T, b: T): Boolean

212

}

213

214

/**

215

* Default equality implementations

216

*/

217

object Equality {

218

/**

219

* Default equality based on universal equality

220

*/

221

implicit def default[A]: Equality[A]

222

223

/**

224

* Tolerance-based equality for floating point numbers

225

*/

226

def tolerantDoubleEquality(tolerance: Double): Equality[Double]

227

def tolerantFloatEquality(tolerance: Float): Equality[Float]

228

}

229

230

/**

231

* Triple equals with constraint checking

232

*/

233

trait TripleEquals {

234

def ===[T](right: T)(implicit constraint: CanEqual[T, T]): Boolean

235

def !==[T](right: T)(implicit constraint: CanEqual[T, T]): Boolean

236

}

237

238

/**

239

* Type constraint for equality comparisons

240

*/

241

trait CanEqual[-A, -B] {

242

def areEqual(a: A, b: B): Boolean

243

}

244

```

245

246

**Usage Examples:**

247

248

```scala

249

import org.scalactic._

250

251

// Custom equality for case-insensitive strings

252

implicit val stringEquality = new Equality[String] {

253

def areEqual(a: String, b: Any): Boolean = b match {

254

case s: String => a.toLowerCase == s.toLowerCase

255

case _ => false

256

}

257

}

258

259

// Triple equals with constraints

260

import TripleEquals._

261

"Hello" === "HELLO" // true with custom equality

262

263

// Tolerance-based floating point comparison

264

implicit val doubleEquality = Equality.tolerantDoubleEquality(0.01)

265

3.14159 === 3.14200 // true within tolerance

266

```

267

268

### Normalization

269

270

Transform values before comparison or processing.

271

272

```scala { .api }

273

/**

274

* Trait for normalizing values before operations

275

*/

276

trait Normalization[A] {

277

def normalized(a: A): A

278

}

279

280

/**

281

* Uniformity combines normalization with equality

282

*/

283

trait Uniformity[A] extends Normalization[A] with Equality[A] {

284

final def areEqual(a: A, b: Any): Boolean = b match {

285

case bAsA: A => normalized(a) == normalized(bAsA)

286

case _ => false

287

}

288

}

289

290

/**

291

* Pre-built string normalizations

292

*/

293

object StringNormalizations {

294

/**

295

* Normalize by trimming whitespace

296

*/

297

val trimmed: Normalization[String]

298

299

/**

300

* Normalize to lowercase

301

*/

302

val lowerCased: Normalization[String]

303

304

/**

305

* Remove all whitespace

306

*/

307

val removeAllWhitespace: Normalization[String]

308

}

309

```

310

311

**Usage Examples:**

312

313

```scala

314

import org.scalactic._

315

import StringNormalizations._

316

317

// Custom normalization

318

implicit val trimmedStringEquality = new Uniformity[String] {

319

def normalized(s: String): String = s.trim

320

}

321

322

// Using pre-built normalizations

323

val normalizer = lowerCased and trimmed

324

val result = normalizer.normalized(" HELLO WORLD ")

325

// Result: "hello world"

326

```

327

328

### Requirements and Validation

329

330

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

331

332

```scala { .api }

333

trait Requirements {

334

/**

335

* Require a condition, returning Good(Unit) or Bad with message

336

*/

337

def require(condition: Boolean): Unit Or ErrorMessage

338

def require(condition: Boolean, message: => Any): Unit Or ErrorMessage

339

340

/**

341

* Require non-null value

342

*/

343

def requireNonNull[T](obj: T): T Or ErrorMessage

344

def requireNonNull[T](obj: T, message: => Any): T Or ErrorMessage

345

}

346

347

object Requirements extends Requirements

348

```

349

350

**Usage Examples:**

351

352

```scala

353

import org.scalactic.Requirements._

354

355

// Validation with requirements

356

def createUser(name: String, age: Int): User Or ErrorMessage = {

357

for {

358

_ <- require(name.nonEmpty, "Name cannot be empty")

359

_ <- require(age >= 0, "Age cannot be negative")

360

_ <- require(age <= 150, "Age must be realistic")

361

} yield User(name, age)

362

}

363

364

val validUser = createUser("John", 25) // Good(User("John", 25))

365

val invalidUser = createUser("", -5) // Bad("Name cannot be empty")

366

```

367

368

## Types

369

370

```scala { .api }

371

/**

372

* Type alias for error messages

373

*/

374

type ErrorMessage = String

375

376

/**

377

* Container for a single error message

378

*/

379

final case class One[+T](value: T) extends AnyVal

380

381

/**

382

* Container for multiple error messages

383

*/

384

final case class Many[+T](values: Iterable[T]) extends AnyVal

385

386

/**

387

* Union of One and Many for error accumulation

388

*/

389

type Every[+T] = One[T] Or Many[T]

390

```