Skip to content

Conversation

@ldrygala
Copy link
Contributor

add inductive implicit for chaining angle conversion factors, as referenced in #50

@codecov-io
Copy link

codecov-io commented Dec 10, 2017

Codecov Report

Merging #52 into master will decrease coverage by 0.58%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #52      +/-   ##
==========================================
- Coverage   85.82%   85.24%   -0.59%     
==========================================
  Files          10       10              
  Lines         254      244      -10     
==========================================
- Hits          218      208      -10     
  Misses         36       36
Impacted Files Coverage Δ
src/main/scala/libra/ops/base.scala 100% <100%> (ø) ⬆️
src/main/scala/libra/nonsi/package.scala 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0a2b281...a296c30. Read the comment docs.

}

object ConversionFactor {
implicit def inductiveAngelConversionFactor[A, From <: UnitOfMeasure[Angle], To <: UnitOfMeasure[Angle], Next <: UnitOfMeasure[Angle]](
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this part be generic on any dimension D instead of Angle?

Copy link
Contributor Author

@ldrygala ldrygala Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tried but i didn't work (diverging implicit expansion for type libra.ops.base.ConversionFactor), therefor i used Angle. I will try one more time to make it more generic

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's ok, there's no need if you already tried! I'll take a better look at it later this week. I've debugged a fair few implicit resolution errors by now, so may have more luck.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so i'm waiting for your solution and i hope that i'll be able to understand them :)

implicit def inductiveAngelConversionFactor[A, From <: UnitOfMeasure[Angle], To <: UnitOfMeasure[Angle], Next <: UnitOfMeasure[Angle]](
implicit multiplicativeSemigroup: MultiplicativeSemigroup[A],
fromConversion: ConversionFactor[A, Angle, From, Next],
toConversion: Lazy[ConversionFactor[A, Angle, Next, To]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

object ConversionFactor {
implicit def inductiveAngelConversionFactor[A, From <: UnitOfMeasure[Angle], To <: UnitOfMeasure[Angle], Next <: UnitOfMeasure[Angle]](
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in 'Angel' here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@zainab-ali
Copy link
Contributor

Thanks @ldrygala this looks awesome! It removes the otherwise exponential boilerplate.

Sorry for not getting round to this earlier - I had a nasty cold and couldn't code for a few days.

Would it be possible to make it even more generic in dimension?
Other than that, I'm really looking forward to merging this in! 👍

@ldrygala
Copy link
Contributor Author

@zainab-ali i removed unused compose function on ConversionFactor, previously we use it to compose bigger ConversionFactor base on smaller. Would you like to merge this PR in current form or would you like to make ConversionFactor more generic in dimension ?

@zainab-ali
Copy link
Contributor

@ldrygala I'd like to sort out the implicit resolution issues for the general case. I don't think we're too far off a solution for this.

I've been investigating why there are implicit resolution issues with general dimensions, other than Angle.

What we have is a graph of potential conversions, and we'd like the compiler to pick out a specific path.
In the case of Angle there is only a single possible path, so the compiler can find the appropriate conversion. In the case of SI units, we provide multiple implicit conversions from one point to another using the metricConversion function. This converts from any unit to any other by examining the decimal power and performing an appropriate multiplication. I suspect that if we got rid of this function, and replaced it with one that only derived a factor for P to P + 1 then the inductive derivation would work.

However, even if the derivation works, the approach isn't ideal. This is mainly because:

  1. Graph like implicit searches are seen as a bug, and aren't something that the compiler should be doing. They could very well be removed in future versions.
  2. The diverging implicit error obtained when there are multiple paths is nonsensical. I've seen messages like:
diverging implicit expansion for type libra.ops.base.ConversionFactor[Double,libra.si.Time,libra.si.Metre,Next]

@milessabin suggested the interesting alternative of providing evidence in the form of a HList path of conversions:

// This can only be created with appropriate conversions from each node
implicit val conversionPath : ConversionPath[Degree :: ArcMinute :: ArcSecond :: HNil] = mkConversionPath

Deriving conversion factors would then be a matter of finding two nodes in the path and traversing all the nodes in between.

This would pose a problem in cases where we wanted to define different units for the same base dimensions in different packages.
For example, a user may want to import SI time units, but leave out the units in the imperial package. We could have two paths:

implicit val conversionPathMetric : ConversionPath[Second :: Millisecond :: _ :: HNil] = mkConversionPath
implicit val conversionPathDay : ConversionPath[Second :: Hour :: Day :: HNil] = mkConversionPath

And then somehow relate the two using a lower priority implicit.

This would be a bit more effort, but would provide better messages in the long run.

I'll experiment with this over the next few days, and see if I can come to a solution on it.

@ldrygala
Copy link
Contributor Author

ldrygala commented Dec 16, 2017

@zainab-ali very interesting idea with conversionPath represented as HList. Is there anything I can get involved with this PR ?

@zainab-ali
Copy link
Contributor

Would you like to have a go at it? It involves some fairly heavy induction, which is not everyone's cup of tea, believe me.

The steps I'm envisaging are as follows:

  1. Create a path from a series of base conversion factors. I've done this below

  2. Create derived conversion factors from the path. This involves, for any units From and To:

    a. Getting the indices of the units in the path. Ideally shapeless contains an IndexOf op we can use
    b. Extracting a range of conversion factors between the units. shapeless contains a Range op that we can use here
    c. Multiplying / dividing all of the factors together. Whether we use multiplication or division depends on the order in which the units are defined.

  3. Extrapolating this for units defined over different paths.

Creating the path is fairly straightforward:

package libra
package ops

import base.ConversionFactor
import shapeless._
import shapeless.ops.hlist._

trait ConversionPath[A, D, H <: HList] {
  type Out <: HList
  val path: Out
}

object ConversionPath {

  type Aux[A, D, H <: HList, Out0 <: HList] = ConversionPath[A, D, H] { type Out = Out0 }
  
  implicit def conversionPathBase[A, D, F <: UnitOfMeasure[D], T <: UnitOfMeasure[D]](
    implicit ev: ConversionFactor[A, D, F, T]
  ): ConversionPath.Aux[A, D, F :: T :: HNil, ConversionFactor[A, D, F, T] :: HNil] =
    new ConversionPath[A, D, F :: T :: HNil] {
      type Out = ConversionFactor[A, D, F, T] :: HNil
      val path = ev :: HNil
    }

  implicit def conversionPathRecurse[A, D, F <: UnitOfMeasure[D], T <: UnitOfMeasure[D],
    Tail <: HList, OutTail <: HList](
    implicit ev: ConversionPath.Aux[A, D, T :: Tail, OutTail],
    ev1: ConversionFactor[A, D, F, T]
  ): ConversionPath.Aux[A, D, F :: T :: Tail, ConversionFactor[A, D, F, T] :: OutTail] =
    new ConversionPath[A, D, F :: T :: Tail] {
      type Out = ConversionFactor[A, D, F, T] :: OutTail
      val path = ev1 :: ev.path
    }
}

Step 2 is more challenging. Let me know if you'd like to give it a try.

@ldrygala
Copy link
Contributor Author

i thought that maybe there will be some easy thing to do, but it looks very hard, so therefore i can only wish you good luck.

@zainab-ali
Copy link
Contributor

I've finally finished the package refactor! I'll be able to work on this over the weekend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants