Skip to content

Commit

Permalink
simplify coulomb's typeclass system
Browse files Browse the repository at this point in the history
  • Loading branch information
erikerlandson committed May 18, 2024
1 parent 56b34cd commit c6b3979
Show file tree
Hide file tree
Showing 128 changed files with 3,008 additions and 6,430 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
run: mkdir -p runtime/.js/target spire/.js/target spire/.jvm/target testkit/.native/target units/.jvm/target runtime/.native/target testkit/.js/target parser/.jvm/target unidocs/target parser/.js/target core/.native/target pureconfig/.jvm/target spire/.native/target parser/.native/target core/.js/target units/.native/target runtime/.jvm/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target
run: mkdir -p runtime/.js/target testkit/.native/target units/.jvm/target runtime/.native/target testkit/.js/target parser/.jvm/target unidocs/target parser/.js/target core/.native/target pureconfig/.jvm/target parser/.native/target core/.js/target units/.native/target runtime/.jvm/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
run: tar cf targets.tar runtime/.js/target spire/.js/target spire/.jvm/target testkit/.native/target units/.jvm/target runtime/.native/target testkit/.js/target parser/.jvm/target unidocs/target parser/.js/target core/.native/target pureconfig/.jvm/target spire/.native/target parser/.native/target core/.js/target units/.native/target runtime/.jvm/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target
run: tar cf targets.tar runtime/.js/target testkit/.native/target units/.jvm/target runtime/.native/target testkit/.js/target parser/.jvm/target unidocs/target parser/.js/target core/.native/target pureconfig/.jvm/target parser/.native/target core/.js/target units/.native/target runtime/.jvm/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
Expand Down
42 changes: 19 additions & 23 deletions benchmarks/src/main/scala/coulomb/benchmarks/quantity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ import java.util.concurrent.TimeUnit

import org.openjdk.jmh.annotations.*

object algebras:
import algebra.ring.*
// inlining typeclass methods can be leveraged by inline code
// however so far, only for these "static" typeclass objects.
// For cases where the typeclass has to be constructed per invocation,
// it seems to be impossible for scala to make "full" use of the inlining,
// so I am not going to try to in-line methods for typeclasses that
// must be non-static functions of their types, for example UnitConversion
given DoubleIsField: Field[Double] with
inline def zero: Double = 0.0
inline def one: Double = 1.0
inline def plus(x: Double, y: Double): Double = x + y
inline def negate(x: Double): Double = -x
override inline def minus(x: Double, y: Double): Double = x - y
inline def times(x: Double, y: Double): Double = x * y
inline def div(x: Double, y: Double): Double = x / y

@State(Scope.Thread)
@Fork(1)
@BenchmarkMode(Array(Mode.Throughput))
Expand All @@ -32,9 +49,6 @@ class QuantityBenchmark:
import coulomb.syntax.*
import coulomb.testing.units.{*, given}
import algebra.instances.all.given
import coulomb.ops.algebra.all.given

import coulomb.policy.standard.given

var data: Vector[Quantity[Double, Meter]] =
Vector.empty[Quantity[Double, Meter]]
Expand All @@ -49,7 +63,7 @@ class QuantityBenchmark:

@Benchmark
def add1V1U_opt(): Quantity[Double, Meter] =
import coulomb.ops.standard.optimizations.all.given
import coulomb.benchmarks.algebras.given
data.foldLeft(0d.withUnit[Meter]) { (s, x) => s + x }

@Benchmark
Expand All @@ -58,23 +72,5 @@ class QuantityBenchmark:

@Benchmark
def add1V2U_opt(): Quantity[Double, Kilo * Meter] =
import coulomb.ops.standard.optimizations.all.given
import coulomb.benchmarks.algebras.given
data.foldLeft(0d.withUnit[Kilo * Meter]) { (s, x) => s + x }

@Benchmark
def add2V1U(): Quantity[Float, Meter] =
data.foldLeft(0f.withUnit[Meter]) { (s, x) => s + x }

@Benchmark
def add2V1U_opt(): Quantity[Float, Meter] =
import coulomb.ops.standard.optimizations.all.given
data.foldLeft(0f.withUnit[Meter]) { (s, x) => s + x }

@Benchmark
def add2V2U(): Quantity[Float, Kilo * Meter] =
data.foldLeft(0f.withUnit[Kilo * Meter]) { (s, x) => s + x }

@Benchmark
def add2V2U_opt(): Quantity[Float, Kilo * Meter] =
import coulomb.ops.standard.optimizations.all.given
data.foldLeft(0f.withUnit[Kilo * Meter]) { (s, x) => s + x }
19 changes: 3 additions & 16 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// sbt githubWorkflowGenerate
// and check in the updates to github workflow yamls

// this line to kick off pull request from branch 'simplify-coulomb'
// base version for assessing MIMA
ThisBuild / tlBaseVersion := "0.8"
ThisBuild / tlBaseVersion := "0.9"

// publish settings
// artifacts now publish to s01.oss.sonatype.org, per:
Expand Down Expand Up @@ -48,7 +49,6 @@ lazy val root = tlCrossRootProject
runtime,
parser,
pureconfig,
spire,
refined,
testkit,
unidocs
Expand All @@ -60,6 +60,7 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.settings(name := "coulomb-core")
.settings(commonSettings: _*)
.settings(libraryDependencies += "org.typelevel" %%% "algebra" % "2.10.0")
.settings(libraryDependencies += "org.typelevel" %%% "spire" % "0.18.0")
.platformsSettings(JSPlatform, NativePlatform)(
Test / unmanagedSources / excludeFilter := HiddenFileFilter || "*serde.scala"
)
Expand Down Expand Up @@ -137,14 +138,6 @@ lazy val pureconfig = crossProject(
libraryDependencies += "com.github.pureconfig" %%% "pureconfig-core" % "0.17.6"
)

lazy val spire = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("spire"))
.settings(name := "coulomb-spire")
.dependsOn(core % "compile->compile;test->test", units % Test)
.settings(commonSettings: _*)
.settings(libraryDependencies += "org.typelevel" %%% "spire" % "0.18.0")

lazy val refined = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("refined"))
Expand Down Expand Up @@ -182,7 +175,6 @@ lazy val all = project
runtime.jvm,
parser.jvm,
pureconfig.jvm,
spire.jvm,
refined.jvm
) // scala repl only needs JVMPlatform subproj builds
.settings(name := "coulomb-all")
Expand Down Expand Up @@ -212,7 +204,6 @@ lazy val docs = project
runtime.jvm,
parser.jvm,
pureconfig.jvm,
spire.jvm,
refined.jvm
)
.enablePlugins(TypelevelSitePlugin)
Expand Down Expand Up @@ -264,10 +255,6 @@ lazy val docs = project
"coulomb-units",
VirtualPath.parse("coulomb-units.md")
),
TargetDefinition.internal(
"coulomb-spire",
VirtualPath.parse("coulomb-spire.md")
),
TargetDefinition.internal(
"coulomb-refined",
VirtualPath.parse("coulomb-refined.md")
Expand Down
102 changes: 102 additions & 0 deletions core/src/main/scala/coulomb/conversion/coefficient.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2022 Erik Erlandson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package coulomb.conversion

import scala.annotation.implicitNotFound

/**
* A typeclass representing a unit conversion coefficient.
* @tparam V
* the value type the unit conversion is operating on
* @tparam UF
* the unit being converted from
* @tparam UT
* the unit being converted to
* @param value
* the value of the conversion coefficient
*/
@implicitNotFound(
"No coefficient could be derived for value type ${V}, unit types ${UF} => ${UT}"
)
class Coefficient[V, UF, UT](val value: V)

/** unit conversion companion methods and definitions */
object Coefficient:
import scala.compiletime.*
import algebra.ring.*
import spire.math.*
import coulomb.infra.typeexpr
import coulomb.conversion.coefficients.*

/**
* Obtain the coefficient of conversion from one unit expression to another
* @tparam UF
* the input unit expression
* @tparam UT
* the output unit expression
* @tparam V
* the value type to return
* @return
* The coefficient of conversion from UF to UT.
* @note
* If UF and UT are not convertible, or if a coefficient
* cannot be constructed for value type `V`, then a
* compilation failure will result.
*/
inline def apply[V, UF, UT]: V =
inline erasedValue[V] match
case _: Float => coefficientFloat[UF, UT].asInstanceOf[V]
case _: Double => coefficientDouble[UF, UT].asInstanceOf[V]
case _: BigDecimal => coefficientBigDecimal[UF, UT].asInstanceOf[V]
case _: Rational => coefficientRational[UF, UT].asInstanceOf[V]
case _: java.lang.Float =>
coefficientFloatJ[UF, UT].asInstanceOf[V]
case _: java.lang.Double =>
coefficientDoubleJ[UF, UT].asInstanceOf[V]
case _ =>
summonInline[Coefficient[V, UF, UT]].value

inline given g_Coefficient[V, UF, UT]: Coefficient[V, UF, UT] =
inline erasedValue[V] match
case _: Float =>
new Coefficient[V, UF, UT](
coefficientFloat[UF, UT].asInstanceOf[V]
)
case _: Double =>
new Coefficient[V, UF, UT](
coefficientDouble[UF, UT].asInstanceOf[V]
)
case _: BigDecimal =>
new Coefficient[V, UF, UT](
coefficientBigDecimal[UF, UT].asInstanceOf[V]
)
case _: Rational =>
new Coefficient[V, UF, UT](
coefficientRational[UF, UT].asInstanceOf[V]
)
case _: java.lang.Float =>
new Coefficient[V, UF, UT](
coefficientFloatJ[UF, UT].asInstanceOf[V]
)
case _: java.lang.Double =>
new Coefficient[V, UF, UT](
coefficientDoubleJ[UF, UT].asInstanceOf[V]
)
case _ =>
val (_, vc) =
summonAll[(Fractional[V], ValueConversion[Rational, V])]
new Coefficient[V, UF, UT](vc(coefficientRational[UF, UT]))
Loading

0 comments on commit c6b3979

Please sign in to comment.