Spire

Continuous Integration Discord codecov.io spire Scala version support

Overview

Spire is a numeric library for Scala which is intended to be generic, fast, and precise.

Using features such as specialization, macros, type classes, and implicits, Spire works hard to defy conventional wisdom around performance and precision trade-offs. A major goal is to allow developers to write efficient numeric code without having to "bake in" particular numeric representations. In most cases, generic implementations using Spire's specialized type classes perform identically to corresponding direct implementations.

Scaladoc

Core Extras Laws Macros

Spire is provided to you as free software under the MIT license.

Organization

There is a #spire channel on the Typelevel Discord. It is the place to go for announcements and discussions around Spire.

We also have a guide on contributing to Spire as well as a guide that provides information on Spire's design.

Spire has maintainers who are responsible for signing-off on and merging pull requests, and for helping to guide the direction of Spire:

Code of Conduct

People are expected to follow the Typelevel Code of Conduct when discussing Spire on the Github page, in Discord, Gitter, the IRC channel, mailing list, and other official venues.

Concerns or issues can be sent to any of Spire's maintainers, or to any of the designated CoC contacts for Typelevel.

Set up

Spire is currently available for Scala 2.13 and 3.1+, for the JVM and JS platforms.

To get started with SBT, simply add the following to your build.sbt file:

libraryDependencies += "org.typelevel" %% "spire" % "0.18.0"

(Previous to 0.14.0, Spire was published under org.spire-math instead of org.typelevel.)

For information on all the available versions, visit the Scaladex entry.

Here is a list of all of Spire's modules:

Spire depends on typelevel/algebra and uses this project to interoperate with Cats and Algebird.

Playing Around

The quickest way to try spire is in the browser with Scastie.

With Ammonite REPL

If you have Ammonite, you can use an interactive REPL session.

Welcome to the Ammonite Repl 2.x.x
@ import $ivy.`org.typelevel::spire:0.18.0`
@ import spire._
@ import spire.math._
@ import spire.implicits._
@ Complex(3.0,5.0).sin
res: Complex[Double] = Complex(real = 10.472508533940392, imag = -73.46062169567367)

With an IDE

You can also play around with IntelliJ or VS Code and Metals.

Create a worksheet file spire.worksheet.sc in your IDE and copy-paste the following code snippet.

import $ivy.`org.typelevel::spire:0.18.0`
import spire._
import spire.math._
import spire.implicits._
Complex(3.0,5.0).sin
res: Complex[Double] = Complex(real = 10.472508533940392, imag = -73.46062169567367)

With git and sbt

If you clone the Spire repo, you can get a taste of what Spire can do using SBT's console. Launch sbt and at the prompt, type coreJVM/console:

> coreJVM/console
[info] Generating spire/std/tuples.scala
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import spire.implicits._
import spire.implicits._

scala> import spire.math._
import spire.math._

scala> Complex(3.0, 5.0).sin
res0: spire.math.Complex[Double] = (10.472508533940392 + -73.46062169567367i)

Number Types

In addition to supporting all of Scala's built-in number types, Spire introduces several new ones, all of which can be found in spire.math:

Detailed treatment of these types can be found in the guide.

Type Classes

Spire provides type classes to support a wide range of unary and binary operations on numbers. The type classes are specialized, do no boxing, and use implicits to provide convenient infix syntax.

The general-purpose type classes can be found in spire.math and consist of:

Some of the general-purpose type classes are built in terms of a set of more fundamental type classes defined in spire.algebra. Many of these correspond to concepts from abstract algebra:

Variants of Semigroup/Monoid/Group/Action with partial operations are defined in the spire.algebra.partial subpackage.

In addition to the type classes themselves, spire.implicits defines many implicits which provide unary and infix operators for the type classes. The easiest way to use these is via a wildcard import of spire.implicits._.

Detailed treatment of these type classes can be found in the guide.

Getting Started

Spire contains a lot of types, as well as other machinery to provide a nice user experience. The easiest way to use spire is via wildcard imports:

import spire.algebra._   // provides algebraic type classes
import spire.math._      // provides functions, types, and type classes
import spire.implicits._ // provides infix operators, instances and conversions

Of course, you can still productively use Spire without wildcard imports, but it may require a bit more work to figure out which functionality you want and where it's coming from.

Operators by Type Class

The following is an outline in more detail of the type classes provided by Spire, as well as the operators that they use. While Spire avoids introducing novel operators when possible, in a few cases it was unavoidable.

Eq, Order and PartialOrder

The type classes provide type-safe equivalence and comparison functions. Orderings can be total (Order) or partial (PartialOrder); although undefined elements like NaN or null will cause problems in the default implementations (for floating-point numbers, alternate implementations that take NaN into account can be imported from spire.optional.totalfloat._).

Semigroup, Monoid, and Group

These general type classes constitute very general operations. The operations range from addition and multiplication to concatenating strings or lists, and beyond!

There are Additive and Multiplicative refinements of these general type classes, which are used in the Ring-family of type classes.

Rings &co

The Ring family of type classes provides the typical arithmetic operations most users will expect.

VectorSpaces &co

The vector space family of type classes provide basic vector operations. They are parameterized on 2 types: the vector type and the scalar type.

Numeric, Integral, and Fractional

These high-level type classes will pull in all of the relevant algebraic type classes. Users who aren't concerned with algebraic properties directly, or who wish for more permissiveness, should prefer these type classes.

The Numeric type class is unique in that it provides the same functionality as Fractional for all number types. Each type will attempt to "do the right thing" as far as possible, and throw errors otherwise. Users who are leery of this behavior are encouraged to use more precise type classes.

Bool

Bool supports Boolean algebras, an abstraction of the familiar bitwise boolean operators.

Bool instances exist not just for Boolean, but also for Byte, Short, Int, Long, UByte, UShort, UInt, and ULong.

Trig

Trig provides an abstraction for any type which defines trigonometric functions. To do this, types should be able to reasonably approximate real values.

Syntax

Using string interpolation and macros, Spire provides convenient syntax for number types. These macros are evaluated at compile-time, and any errors they encounter will occur at compile-time.

For example:

object LiteralsDemo {
  import spire.syntax.literals._
  
  // bytes and shorts
  val x = b"100" // without type annotation!
  val y = h"999"
  val mask = b"255" // unsigned constant converted to signed (-1)
  
  // rationals
  val n1 = r"1/3"
  val n2 = r"1599/115866" // simplified at compile-time to 13/942
}

object RadixDemo {
  // support different radix literals
  import spire.syntax.literals.radix._
  
  // representations of the number 23
  val a = x2"10111" // binary
  val b = x8"27" // octal
  val c = x16"17" // hex
}

object SIDemo {
  // SI notation for large numbers
  import spire.syntax.literals.si._ // .us and .eu also available
  
  val w = i"1 944 234 123" // Int
  val x = j"89 234 614 123 234 772" // Long
  val y = big"123 234 435 456 567 678 234 123 112 234 345" // BigInt
  val z = dec"1 234 456 789.123456789098765" // BigDecimal
}

Spire also provides a loop macro called cfor (previously known as cfor) whose syntax bears a slight resemblance to a traditional for-loop from C or Java. This macro expands to a tail-recursive function, which will inline literal function arguments.

The macro can be nested in itself and compares favorably with other looping constructs in Scala such as for and while:

import spire.syntax.cfor._

// print numbers 1 through 10
cfor(0)(_ < 10, _ + 1) { i =>
  println(i)
}

// naive sorting algorithm
def selectionSort(ns: Array[Int]) = {
  val limit = ns.length -1
  cfor(0)(_ < limit, _ + 1) { i =>
    var k = i
    val n = ns(i)
    cfor(i + 1)(_ <= limit, _ + 1) { j =>
      if (ns(j) < ns(k)) k = j
    }
    ns(i) = ns(k)
    ns(k) = n
  }
}

Sorting, Selection, and Searching

Since Spire provides a specialized ordering type class, it makes sense that it also provides its own methods for doing operations based on order. These methods are defined on arrays and occur in-place, mutating the array. Other collections can take advantage of sorting by converting to an array, sorting, and converting back (which is what the Scala collections framework already does in most cases). Thus, Spire supports both mutable arrays and immutable collections.

Sorting methods can be found in the spire.math.Sorting object. They are:

Both mergeSort and quickSort delegate to insertionSort when dealing with arrays (or slices) below a certain length. So, it would be more accurate to describe them as hybrid sorts.

Selection methods can be found in an analogous spire.math.Selection object. Given an array and an index k these methods put the kth largest element at position k, ensuring that all preceding elements are less-than or equal-to, and all succeeding elements are greater-than or equal-to, the kth element.

There are two methods defined:

Searching methods are located in the spire.math.Searching object. Given a sorted array (or indexed sequence), these methods will locate the index of the desired element (or return -1 if it is not found).

Searching also supports a more esoteric method: minimalElements. This method returns the minimal elements of a partially-ordered set.

Pseudo-Random Number Generators

Spire comes with many different PRNG implementations, which extends the spire.random.Generator interface. Generators are mutable RNGs that support basic operations like nextInt. Unlike Java, generators are not threadsafe by default; synchronous instances can be attained by calling the .sync method.

Spire supports generating random instances of arbitrary types using the spire.random.Dist[A] type class. These instances represent a strategy for getting random values using a Generator instance. For instance:

object DistDemo {
  import spire.random.Dist
  
  val rng = spire.random.rng.Cmwc5()
  
  // produces a map with ~10-20 entries
  implicit val nextmap: Dist[Map[Int, Complex[Double]]] = Dist.map[Int, Complex[Double]](10, 20)
  val m = rng.next[Map[Int, Complex[Double]]]

  // produces a double in [0.0, 1.0)
  val n = rng.next[Double]
  
  // produces a complex number, with real and imaginary parts in [0.0, 1.0)
  val q = rng.next[Complex[Double]]
}

Unlike generators, Dist[A] instances are immutable and composable, supporting operations like map, flatMap, and filter. Many default instances are provided, and it's easy to create custom instances for user-defined types.

Miscellany

In addition, Spire provides many other methods which are "missing" from java.Math (and scala.math), such as:

Benchmarks

In addition to unit tests, Spire comes with a relatively fleshed-out set of JMH micro-benchmarks. To run the benchmarks from within SBT, change to the benchmark subproject and then Jmh / run -l to see a list of benchmarks:

$ sbt
> project benchmark
> Jmh / run -l

[info] Benchmarks:
[info] spire.benchmark.AddBenchmarks.addComplexDoubleStateDirect
[info] spire.benchmark.AddBenchmarks.addComplexDoubleStateGeneric
[info] spire.benchmark.AddBenchmarks.addComplexFloatStateDirect
...

To run all available benchmarks:

> Jmh / run

To run a specific benchmark method:

> Jmh / run spire.benchmark.AddBenchmarks.addComplexDoubleStateDirect

To run all benchmarks in a specific class:

> Jmh / run spire.benchmark.AddBenchmarks

To see all available JMH usage options:

> Jmh / run -h

If you plan to contribute to Spire, please make sure to run the relevant benchmarks to be sure that your changes don't impact performance. Benchmarks usually include comparisons against equivalent Scala or Java classes to try to measure relative as well as absolute performance.

Caveats

Code is offered as-is, with no implied warranty of any kind. Comments, criticisms, and/or praise are welcome, especially from numerical analysts! ;)

Copyright 2011-2017 Erik Osheim, Tom Switzer

A full list of contributors can be found in AUTHORS.md.

The MIT software license is attached in the COPYING file.