Spire

Spire — это числовая библиотека для Scala, которая должна быть универсальной, быстрой и точной.

Используя такие функции, как специализация, макросы, классы типов и неявные выражения, Spire прилагает все усилия, чтобы бросить вызов общепринятым представлениям о компромиссах между производительностью и точностью. Основная цель — позволить разработчикам писать эффективный числовой код без необходимости "встраивать" определенные числовые представления. В большинстве случаев универсальные реализации, использующие специализированные классы типов Spire, работают идентично соответствующим прямым реализациям.

Пример использования библиотеки Spire:

import spire.*
import spire.implicits.*
import spire.math.*

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

Числовые типы

Помимо поддержки всех встроенных числовых типов Scala, Spire представляет несколько новых, все из которых можно найти в spire.math:

Классы типов

Spire предоставляет классы типов для поддержки широкого спектра унарных и двоичных операций с числами. Классы типов являются специализированными, не выполняют упаковку и используют неявные выражения для обеспечения удобного инфиксного синтаксиса.

Классы типов общего назначения можно найти в пакете spire.math:

Некоторые классы типов общего назначения построены на основе набора более фундаментальных классов типов, определенных в spire.algebra. Многие из них соответствуют понятиям абстрактной алгебры:

Варианты Semigroup/Monoid/Group/Action с частичными операциями определены в подпакете spire.algebra.partial.

Помимо самих классов типов, spire.implicits определяет множество неявных функций, которые предоставляют унарные и инфиксные операторы для классов типов. Самый простой способ использовать их — импортировать файлы spire.implicits.*.

С чего начать?

Spire содержит множество типов, а также другие механизмы, обеспечивающие удобство использования. Самый простой способ использовать Spire — импортировать зависимости:

import spire.algebra.*   // предоставляет алгебраические классы типов
import spire.implicits.* // обеспечивает инфиксные операторы, инстансы и конверсию
import spire.math.*      // обеспечивает функции, типа и классы типов

Операции, отсортированные по классам типов

Ниже приводится более подробное описание классов типов, предоставляемых Spire, а также операторов, которые они используют. Хотя Spire по возможности избегает введения новых операторов, в некоторых случаях это было неизбежно.

Числа

Эти классы типов высокого уровня будут включать все соответствующие классы алгебраических типов.

Класс типов Numeric уникален тем, что обеспечивает ту же функциональность, что Fractional и все числовые типы. Каждый тип будет пытаться "поступить правильно", насколько это возможно, в противном случае выдавать ошибки. Пользователям, которые настороженно относятся к такому поведению, рекомендуется использовать более точные классы типов.

Синтаксис

Используя строковую интерполяцию и макросы, Spire обеспечивает удобный синтаксис для числовых типов. Эти макросы оцениваются во время компиляции, и любые ошибки, с которыми они сталкиваются, возникают во время компиляции.

Например:

object LiteralsDemo:
  import spire.syntax.literals.*

  // byte-ы и short-ы
  val x    = b"100" // без аннотации типа!
  val y    = h"999"
  val mask = b"255" // беззнаковая константа сконвертированная в знаковую (-1)

  // дроби
  val n1 = r"1/3"
  val n2 = r"1599/115866" // упроститься при компиляции до 13/942
object SIDemo:
  // SI нотация для больших чисел
  import spire.syntax.literals.si.* // также доступны `.us` и `.eu`
  
  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 также предоставляет макрос цикла cfor, синтаксис которого немного похож на традиционный цикл for из C или Java. Этот макрос расширяется до хвостовой рекурсивной функции, которая встраивает литеральные аргументы функции.

Макрос может быть вложен сам в себя и выгодно отличается от других конструкций цикла в Scala, таких как for и while:

import spire.syntax.cfor.*

// печатает числа от 0 до 9
cfor(0)(_ < 10, _ + 1): i =>
  println(i)

// простой алгоритм сортировки
def selectionSort(ns: Array[Int]): Unit =
  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) then k = j
    ns(i) = ns(k)
    ns(k) = n

Остальные операции

Разное

Кроме того, Spire предоставляет множество других методов, которые "отсутствуют" в java.Mathscala.math), например:

Тесты

Помимо модульных тестов, Spire поставляется с относительно детальным набором микротестов JMH. Чтобы запустить тесты из SBT, перейдите к подпроекту benchmark и затем запустите Jmh / run -l, чтобы просмотреть список тестов:

$ sbt
> project benchmark
> Jmh / run -l

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

Чтобы запустить все доступные тесты:

> Jmh / run

Чтобы запустить определенный метод тестирования:

> Jmh / run spire.benchmark.AddBenchmarks.addComplexDoubleStateDirect

Чтобы запустить все тесты в определенном классе:

> Jmh / run spire.benchmark.AddBenchmarks

Чтобы просмотреть все доступные варианты использования JMH:

> Jmh / run -h

Если вы планируете внести свой вклад в Spire, обязательно запустите соответствующие тесты, чтобы убедиться, что ваши изменения не повлияют на производительность. Тесты обычно включают сравнение с эквивалентными классами Scala или Java, чтобы попытаться измерить как относительную, так и абсолютную производительность.


Ссылки: