typelevel / squants

The Scala API for Quantities, Units of Measure and Dimensional Analysis
https://www.squants.com
Apache License 2.0
922 stars 122 forks source link

Degrees Symbol is not input-friendly #172

Closed ALPSMAC closed 7 years ago

ALPSMAC commented 7 years ago

Any chance we could change the symbol used for Degrees? Right now it appears to be defined like so:

object Degrees extends AngleUnit {
  val symbol = "°"
  val conversionFactor = math.Pi / 180d
}

But if you're reading input from a service and trying to convert to a valid unit (e.g. via Angle.symbolToUnit(str: String) ) it forces your client to specify the UTF-8 "°" symbol. From a cursory glance it seems like most of the other units are specified in simple ascii characters.

Was there a specific reason to not just use "deg" for degrees (like a name conflict or something)?

Perhaps more generally - would it be a logical extension to allow for symbol aliases? Either via implicit typeclass evidence or by just making symbol a Set[String] rather than just a String?

Thanks!

sarahgerweck commented 7 years ago

I would vote against getting rid of ° as a way to specify degrees—it's the standard symbol. I do like the idea of having an alternate way to express it using base ASCII.

cquiroz commented 7 years ago

You could map whatever unit name your client uses to the one used by Squants, like

scala> val unit = "deg"
unit: String = deg

scala> unit match {
     |    case "deg" => Angle.symbolToUnit("°")
     |    case x     => Angle.symbolToUnit(x)
     | }
res5: Option[squants.UnitOfMeasure[squants.space.Angle]] = Some(squants.space.Degrees$@48944352)

It seems to me something for the caller side to decide. I guess we could have multiple symbols as some units may have more than one way to display them

ALPSMAC commented 7 years ago

Hi Carlos,

Thank you for the suggestion.

I had thought of simply mapping the client-side field to the ° symbol (and it is a reasonable work-around), but handling the mapping from what is client input appears to be (partially) handled by Squants via that symbolToUnit function.

It seems logical to me that a library all about the typesafe manipulation of units would also concern itself with interpreting what is a valid specification for units in their naturally expressible ways. It is fair though to argue that this is a concern outside of the bounds of Squants...

I guess I see supporting multiple (or even pluggable/customizable) symbol lookups for arbitrary quantities as being an integration-level concern which could either be handled in a standard way by Squants or ad-hoc by clients of the library. Both approaches are valid, but in the spirit of portability and readability I lean more toward symbol customization as a library-level concern to define a standard mechanism for extension.

Kind Regards, Andy

P.S.: I love Squants - makes the work I do considerably easier! Thanks for such an awesome library!

Sent from my iPad

On Jan 23, 2017, at 6:20 PM, Carlos Quiroz notifications@github.com wrote:

You could map whatever unit name your client uses to the one used by Squants, like

scala> val unit = "deg" unit: String = deg

scala> unit match { | case "deg" => Angle.symbolToUnit("°") | case x => Angle.symbolToUnit(x) | } res5: Option[squants.UnitOfMeasure[squants.space.Angle]] = Some(squants.space.Degrees$@48944352) It seems to me something for the caller side to decide. I guess we could have multiple symbols as some units may have more than one way to display them

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

derekmorr commented 7 years ago

If you're reading numeric data from a file or web service, you might want to use the Degrees() constructor, or the .degrees conversion:

scala> import squants.space._
import squants.space._

scala> import squants.space.AngleConversions._
import squants.space.AngleConversions._

scala> val oneWay = Degrees(90)
oneWay: squants.space.Angle = 90.0 °

scala> val anotherWay = 90.degrees
anotherWay: squants.space.Angle = 90.0 °
ALPSMAC commented 7 years ago

Derekmorr,

I was hoping to be able to support allowing clients of my service to specify the units as part of their request... so often you have to go look in the API to verify the units of measure the service expects and then do conversions client-side to get your request to match. I'd rather allow clients to submit their request in any unit of measure that makes sense provided we can map the symbol to a logical instance, similar to the REST API example in the README.md (also the anti-corruption layer example).

Perhaps I should keep that mapping table separate from Squants (and I will for now)... but my thought process is that having a built-in mechanism as part of the library to convert from a string representation of units to the instance type at all kind-of justifies having a way to represent multiple string symbols for a given unit of measure - otherwise I wouldn't expect to find a symbolToUnit function at all.

I'm not sure what the least intrusive way would be to introduce support for symbolic alternatives/overrides for units of measure... have to think about it a bit more. Would it be worth putting up a PR with a proposed extension if I can come up with something minimally intrusive to the existing design patterns?

Thanks, Andy

derekmorr commented 7 years ago

Sure, happy to look at a PR. A cursory examination of the code shows most units have symbols in simple ASCII, with a few exceptions (angle degrees, the temperature degrees, some exponents in space and volume). My concern is making the change non-invasive and not complicating the API.

ALPSMAC commented 7 years ago

Yeah - absolutely! I wouldn't want to recommend something that's invasive or requires a lot of fundamental changes. I'll fiddle with it and see if I can come up with something. Thanks!

ALPSMAC commented 7 years ago

After fiddling for a bit the best I could come up with was an additional field on the UnitOfMeasure trait:

/**
    * A means by which to specify alternative symbols for a given UnitOfMeasure.
    * Helpful for parsing from string values.
    *
    * @return
    */
  def alternativeSymbols: Set[String] = Set.empty[String]

I then adjusted the symbolToUnit function in Dimension like so:

/**
   * Maps a string representation of a unit symbol into the matching UnitOfMeasure object
   * @param symbol String
   * @return
   */
  def symbolToUnit(symbol: String): Option[UnitOfMeasure[A]] = {
    units.find{u ⇒
      u.symbol == symbol ||
      u.alternativeSymbols.contains(symbol)
    }
  }

I was hoping for something a little more elegant that would also allow consumers of the library to provide their own symbol overrides. Not being terribly thrilled with what I came up with I'm not planning to offer up a PR containing the changes - they are minimal and I don't believe they add value (unless someone on this thread feels strongly otherwise).

I will stick with mapping from the caller side for now as @cquiroz and others have suggested.

Thanks all for your feedback!