Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplify coulomb's implicit type system #537

Open
wants to merge 9 commits into
base: scala3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 }
47 changes: 31 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 @@ -61,7 +62,6 @@ lazy val root = tlCrossRootProject
runtime,
parser,
pureconfig,
spire,
refined,
testkit,
unidocs
Expand All @@ -73,6 +73,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 @@ -150,14 +151,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 @@ -195,7 +188,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 @@ -225,7 +217,6 @@ lazy val docs = project
runtime.jvm,
parser.jvm,
pureconfig.jvm,
spire.jvm,
refined.jvm
)
.enablePlugins(TypelevelSitePlugin)
Expand All @@ -248,11 +239,35 @@ lazy val docs = project
"https://scala-lang.org/api/3.x/"
).withPackagePrefix("scala")
)
.addApiLinks(
ApiLinks(
"https://javadoc.io/doc/org.typelevel/algebra_3/latest/"
).withPackagePrefix("algebra")
)
.addApiLinks(
ApiLinks(
"https://javadoc.io/doc/org.typelevel/cats-core_3/latest/"
).withPackagePrefix("cats")
)
.addApiLinks(
ApiLinks(
"https://javadoc.io/doc/org.typelevel/spire_3/latest/"
).withPackagePrefix("spire")
)
.addApiLinks(
ApiLinks(
"https://javadoc.io/doc/com.github.pureconfig/pureconfig-core_3/latest/"
).withPackagePrefix("pureconfig")
)
.addApiLinks(
// the refined api link isn't super helpful because @:api(...)
// doesn't work for type defs or methods, which is most of
// what refined provides
ApiLinks(
// refined is not publishing scaladoc for scala 3 yet
"https://javadoc.io/doc/eu.timepit/refined_2.13/latest/"
).withPackagePrefix("eu.timepit.refined")
)
.addTargets(
// Target names need to be all lowercase.
// Note, this does not align with Laika docs.
Expand All @@ -265,6 +280,10 @@ lazy val docs = project
"quantitytypedef",
"https://www.javadoc.io/doc/com.manyangled/coulomb-docs_3/latest/coulomb.html#Quantity[V,U]=V"
),
TargetDefinition.external(
"refinedapidocs",
"https://javadoc.io/doc/eu.timepit/refined_2.13/latest/eu/timepit/refined/index.html"
),
TargetDefinition.internal(
"coulomb-introduction",
VirtualPath.parse("README.md")
Expand All @@ -277,10 +296,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