or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions-matchers.mdasync-testing.mdindex.mdscalactic-utilities.mdtest-execution.mdtest-styles.md

scalactic-utilities.mddocs/

0

# Scalactic Utilities

1

2

Scalactic provides functional programming utilities and better error handling mechanisms for Scala applications. The library focuses on functional idioms, type-safe error handling with the Or type, and pluggable equality systems.

3

4

## Capabilities

5

6

### Or Type System

7

8

Railway-oriented programming with Good/Bad disjunction type for error handling.

9

10

```scala { .api }

11

import org.scalactic._

12

13

// Core Or type

14

sealed abstract class Or[+G, +B] {

15

// Type checking methods

16

def isGood: Boolean

17

def isBad: Boolean

18

19

// Value extraction (unsafe)

20

def get: G // Throws if Bad

21

22

// Safe value extraction

23

def getOrElse[H >: G](alternative: => H): H

24

25

// Transformation methods

26

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

27

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

28

def filter[C >: B](p: G => Boolean): G Or (B Or C)

29

def foreach(f: G => Unit): Unit

30

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

31

32

// Collection-like operations

33

def toOption: Option[G]

34

def toSeq: Seq[G]

35

def iterator: Iterator[G]

36

37

// Combination methods

38

def orElse[H >: G, C >: B](alternative: => H Or C): H Or C

39

def recover[H >: G](pf: PartialFunction[B, H]): H Or B

40

def recoverWith[H >: G, C >: B](pf: PartialFunction[B, H Or C]): H Or C

41

42

// Validation methods

43

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

44

def swap: B Or G

45

def exists(p: G => Boolean): Boolean

46

def forall(p: G => Boolean): Boolean

47

}

48

49

// Concrete implementations

50

case class Good[+G](value: G) extends Or[G, Nothing]

51

case class Bad[+B](value: B) extends Or[Nothing, B]

52

53

// Companion object methods

54

object Or {

55

def apply[G, B](value: G): G Or B = Good(value)

56

def good[G, B](value: G): G Or B = Good(value)

57

def bad[G, B](value: B): G Or B = Bad(value)

58

}

59

```

60

61

**Or Type Examples:**

62

```scala

63

import org.scalactic._

64

65

// Basic usage

66

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

67

if (y == 0) Bad("Cannot divide by zero")

68

else Good(x / y)

69

}

70

71

val result1 = divide(10, 2) // Good(5)

72

val result2 = divide(10, 0) // Bad("Cannot divide by zero")

73

74

// Pattern matching

75

result1 match {

76

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

77

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

78

}

79

80

// Functional operations

81

val doubled = result1.map(_ * 2) // Good(10)

82

val recovered = result2.getOrElse(-1) // -1

83

84

// Chaining operations

85

def parseAndDivide(xStr: String, yStr: String): Int Or String = {

86

for {

87

x <- parseIntSafely(xStr)

88

y <- parseIntSafely(yStr)

89

result <- divide(x, y)

90

} yield result

91

}

92

93

def parseIntSafely(str: String): Int Or String = {

94

try Good(str.toInt)

95

catch { case _: NumberFormatException => Bad(s"Not a valid integer: $str") }

96

}

97

```

98

99

### Attempt Function

100

101

Safe execution wrapper that catches exceptions and returns Or.

102

103

```scala { .api }

104

import org.scalactic._

105

106

/**

107

* Returns the result of evaluating the given block f, wrapped in a Good, or

108

* if an exception is thrown, the Throwable, wrapped in a Bad.

109

*/

110

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

111

```

112

113

**Attempt Examples:**

114

```scala

115

import org.scalactic._

116

117

// Safe division

118

val result1 = attempt { 10 / 2 } // Good(5)

119

val result2 = attempt { 10 / 0 } // Bad(ArithmeticException)

120

121

// Safe file operations

122

def readFileSafely(filename: String): String Or Throwable = {

123

attempt {

124

scala.io.Source.fromFile(filename).mkString

125

}

126

}

127

128

// Chaining with other Or operations

129

val processedResult = for {

130

content <- readFileSafely("data.txt")

131

lines = content.split("\n")

132

firstLine <- if (lines.nonEmpty) Good(lines.head) else Bad("File is empty")

133

} yield firstLine.toUpperCase

134

```

135

136

### Equality System

137

138

Pluggable equality for custom comparison logic.

139

140

```scala { .api }

141

import org.scalactic._

142

143

// Core equality trait

144

trait Equality[T] {

145

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

146

}

147

148

// Normalization trait

149

trait Uniformity[T] extends Equality[T] {

150

def normalized(a: T): T

151

def normalizedCanHandle(b: Any): Boolean

152

def normalizedOrSame(b: Any): Any

153

}

154

155

// Explicitly object for controlling implicit conversions

156

object Explicitly {

157

val decided: Decided.type = Decided

158

val after: After.type = After

159

}

160

161

// Custom equality examples

162

implicit val stringEquality = new Equality[String] {

163

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

164

b match {

165

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

166

case _ => false

167

}

168

}

169

```

170

171

**Equality Examples:**

172

```scala

173

import org.scalactic._

174

import org.scalactic.StringNormalizations._

175

176

// Case-insensitive string equality

177

implicit val caseInsensitiveEquality = new Equality[String] {

178

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

179

b match {

180

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

181

case _ => false

182

}

183

}

184

185

// Using with ScalaTest matchers

186

"Hello" should equal("HELLO")(decided by caseInsensitiveEquality)

187

188

// Normalization-based equality

189

implicit val trimmedStringEquality = StringNormalizations.trimmed

190

191

" hello " should equal("hello")(after being trimmed)

192

193

// Custom numeric tolerance

194

implicit val doubleEquality = TolerantNumerics.tolerantDoubleEquality(0.01)

195

3.14159 should equal(3.14)(decided by doubleEquality)

196

```

197

198

### Validated Types (AnyVals)

199

200

Type-safe wrappers for constrained values.

201

202

```scala { .api }

203

import org.scalactic.anyvals._

204

205

// Positive integers

206

final class PosInt private (val value: Int) extends AnyVal

207

object PosInt {

208

def from(value: Int): PosInt Or One[ErrorMessage]

209

def ensuringValid(value: Int): PosInt

210

}

211

212

// Non-zero integers

213

final class NonZeroInt private (val value: Int) extends AnyVal

214

object NonZeroInt {

215

def from(value: Int): NonZeroInt Or One[ErrorMessage]

216

def ensuringValid(value: Int): NonZeroInt

217

}

218

219

// Positive doubles

220

final class PosDouble private (val value: Double) extends AnyVal

221

object PosDouble {

222

def from(value: Double): PosDouble Or One[ErrorMessage]

223

def ensuringValid(value: Double): PosDouble

224

}

225

226

// Non-empty strings

227

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

228

object NonEmptyString {

229

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

230

def ensuringValid(value: String): NonEmptyString

231

}

232

233

// Non-empty collections

234

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

235

object NonEmptyList {

236

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

237

def apply[T](head: T, tail: T*): NonEmptyList[T]

238

}

239

```

240

241

**Validated Types Examples:**

242

```scala

243

import org.scalactic.anyvals._

244

245

// Safe construction with validation

246

val posInt1 = PosInt.from(5) // Good(PosInt(5))

247

val posInt2 = PosInt.from(-5) // Bad(One("-5 was not greater than 0"))

248

249

// Unsafe construction (throws on invalid)

250

val posInt3 = PosInt.ensuringValid(10) // PosInt(10)

251

252

// Using in function parameters

253

def calculateArea(width: PosDouble, height: PosDouble): Double = {

254

width.value * height.value

255

}

256

257

val width = PosDouble.from(5.0).getOrElse(PosDouble.ensuringValid(1.0))

258

val height = PosDouble.from(3.0).getOrElse(PosDouble.ensuringValid(1.0))

259

val area = calculateArea(width, height)

260

261

// Non-empty collections

262

val nonEmptyList = NonEmptyList(1, 2, 3, 4) // NonEmptyList(List(1, 2, 3, 4))

263

val fromList = NonEmptyList.from(List(1, 2)) // Good(NonEmptyList(List(1, 2)))

264

val fromEmpty = NonEmptyList.from(List.empty) // Bad(One("List was empty"))

265

266

// Working with validated strings

267

def processName(name: NonEmptyString): String = {

268

s"Hello, ${name.value}!"

269

}

270

271

NonEmptyString.from("Alice") match {

272

case Good(name) => processName(name)

273

case Bad(error) => s"Invalid name: ${error.head}"

274

}

275

```

276

277

### Requirements and Assertions

278

279

Design by contract utilities for preconditions and postconditions.

280

281

```scala { .api }

282

import org.scalactic._

283

284

object Requirements {

285

// Precondition checking

286

def require(condition: Boolean): Unit

287

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

288

289

// Null checking

290

def requireNonNull(reference: AnyRef): Unit

291

def requireNonNull(reference: AnyRef, message: => Any): Unit

292

293

// State checking

294

def requireState(condition: Boolean): Unit

295

def requireState(condition: Boolean, message: => Any): Unit

296

}

297

298

// Chain type (deprecated, use NonEmptyList)

299

@deprecated("Use NonEmptyList instead")

300

type Chain[+T] = NonEmptyList[T]

301

```

302

303

**Requirements Examples:**

304

```scala

305

import org.scalactic.Requirements._

306

307

class BankAccount(initialBalance: Double) {

308

require(initialBalance >= 0, "Initial balance cannot be negative")

309

310

private var balance = initialBalance

311

312

def withdraw(amount: Double): Unit = {

313

require(amount > 0, "Withdrawal amount must be positive")

314

requireState(balance >= amount, "Insufficient funds")

315

balance -= amount

316

}

317

318

def deposit(amount: Double): Unit = {

319

require(amount > 0, "Deposit amount must be positive")

320

balance += amount

321

}

322

323

def getBalance: Double = balance

324

}

325

326

// Usage

327

val account = new BankAccount(100.0)

328

account.deposit(50.0) // OK

329

account.withdraw(75.0) // OK

330

// account.withdraw(100.0) // Would throw IllegalStateException

331

```

332

333

### Snapshots and Pretty Printing

334

335

Enhanced error messages and value formatting.

336

337

```scala { .api }

338

import org.scalactic._

339

340

// Pretty printing customization

341

trait Prettifier {

342

def apply(o: Any): String

343

}

344

345

object Prettifier {

346

val default: Prettifier

347

val truncateAt: Int => Prettifier

348

val lineSeparator: String => Prettifier

349

}

350

351

// Position tracking for better error messages

352

package object source {

353

case class Position(fileName: String, filePath: String, lineNumber: Int)

354

355

implicit def here: Position = macro PositionMacro.genPosition

356

}

357

```

358

359

### Collection Utilities

360

361

Additional collection operations and utilities.

362

363

```scala { .api }

364

// Every type for expressing "all elements match"

365

final class Every[+T] private (underlying: Vector[T]) {

366

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

367

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

368

def filter(p: T => Boolean): Seq[T]

369

def exists(p: T => Boolean): Boolean

370

def forall(p: T => Boolean): Boolean

371

def toVector: Vector[T]

372

def toList: List[T]

373

def toSeq: Seq[T]

374

}

375

376

object Every {

377

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

378

def from[T](seq: Seq[T]): Every[T] Or One[ErrorMessage]

379

}

380

381

// One type for single-element collections

382

final class One[+T](element: T) {

383

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

384

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

385

def head: T

386

def toSeq: Seq[T]

387

def toList: List[T]

388

def toVector: Vector[T]

389

}

390

```

391

392

## Package Object Utilities

393

394

```scala { .api }

395

package object scalactic {

396

// Type aliases

397

type ErrorMessage = String

398

399

// Utility functions

400

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

401

402

// Version information

403

val ScalacticVersion: String

404

405

// Deprecated aliases (use new names instead)

406

@deprecated("Use NonEmptyList instead")

407

type Chain[+T] = NonEmptyList[T]

408

409

@deprecated("Use NonEmptyList instead")

410

val Chain = NonEmptyList

411

412

@deprecated("Use anyvals.End instead")

413

val End = anyvals.End

414

}

415

```

416

417

## Integration with ScalaTest

418

419

Scalactic integrates seamlessly with ScalaTest for enhanced testing capabilities:

420

421

```scala

422

import org.scalatest.flatspec.AnyFlatSpec

423

import org.scalatest.matchers.should.Matchers

424

import org.scalactic._

425

426

class ScalacticIntegrationSpec extends AnyFlatSpec with Matchers {

427

"Or type" should "work with ScalaTest matchers" in {

428

val result: Int Or String = Good(42)

429

430

result shouldBe a[Good[_]]

431

result.isGood should be(true)

432

result.get should be(42)

433

}

434

435

"Custom equality" should "be usable in tests" in {

436

implicit val caseInsensitive = new Equality[String] {

437

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

438

b match {

439

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

440

case _ => false

441

}

442

}

443

444

"Hello" should equal("HELLO")(decided by caseInsensitive)

445

}

446

447

"Validated types" should "ensure constraints" in {

448

val posInt = PosInt.from(5)

449

posInt shouldBe a[Good[_]]

450

posInt.get.value should be(5)

451

452

val invalidPosInt = PosInt.from(-1)

453

invalidPosInt shouldBe a[Bad[_]]

454

}

455

}

456

```