Skip to content

Conversation

Aster89
Copy link

@Aster89 Aster89 commented Sep 29, 2025

Hi there. I found this project on Haskell Weekly.

Is #3 expecting something like this?

I don't really understand the dir structure... As in, what's the relation between the line $(mkUnitNoFactor "Celsius" "°C" ''Temperature) which is about temperature in Celsious degree, and the path Data/Units/SI/Derived/NonAngle.hs of the file that contains it? How is "temperature in celsius" an "SI derived non-angle unit"? I mean, every unit is a non-angle unit if it... a non-angle unit.

@AliceRixte
Copy link
Owner

AliceRixte commented Sep 30, 2025

Hi Aster !

Thanks a lot for proposing a contribution to this library !

Yes it's quite confusing indeed. The reason I separated Angle units from NonAngle units is because this library handles two different unit systems:

  1. International System of units (SI), in which angles have no dimension
  2. What I decided to call AngleSI, which exactly the same as SI except from the fact that there is an additional dimension, called Angle.

In order to avoid duplications, Data.Units.SI.Derived.NonAngle is re-exported both by Data.Units.SI.Derived and Data.Units.AngleSI.Derived.

However, Data.Units.SI.Derived.Angle is only reexported by Data.Units.SI.Derived, because angle units are different in Data.Units.AngleSI (i.e. they are not dimensionless).

@AliceRixte
Copy link
Owner

AliceRixte commented Sep 30, 2025

Is #3 expecting something like this?

It is pretty close, but you can simplify it:

$(mkUnitNoFactor "Celsius" "°C" ''Temperature)

is used specifically for units whose conversion is more complicated than a mere factor conversion. Namely in the case of Celsius degrees there is an offset i.e.

 °C = °K + 273.15

For units like Foot, you can use

$(mkUnit "Foot" "ft" ''Length (1200 / 3937))

which will declare the instances for ConversionFactor and ConvertibleUnits for you.

TL;DR : You should use mkUnitNoFactor only for more complicated conversions like Celsius or Fahrenheit, most of the time you only need mkUnit :-)

@AliceRixte
Copy link
Owner

Also I just realized (I'm french, I'm not used to those units) that there are two different system of units in use in some of the countries of the Common wealth:

In terms of folder organization, I was thinking it would be best to separate them as independant unit systems, meaning there would be a module Data.Units.Imperial and a module Data.Units.UsCustomary which both contain modules for Length, Area, Volume and so on.

And we would keep Data.Units.NonStd for units that don't belong to any system, like Tet for instance.

What do you think @Aster89 ?

@Aster89
Copy link
Author

Aster89 commented Oct 2, 2025

Ok, so this

  1. What I decided to call AngleSI, which exactly the same as SI except from the fact that there is an additional dimension, called Angle.

maps to this:

module Data.Units.AngleSI.System
( module Data.Units.SI.System
, Angle (..)
, Radian (..)
, normalizeRadians
)

And this:

angle units are different in Data.Units.AngleSI (i.e. they are not dimensionless).

maps to this:

$(mkDim "Angle" "A" 1000)

vs, I suppose..., this?

type Angle = NormalizeDim (Length ./. Length)


I was thinking it would be best to

Yeah, I can give that a go, but before that, for my sanity, can I submit a change where I do a few little things like these?

@AliceRixte
Copy link
Owner

AliceRixte commented Oct 3, 2025

maps to this:

$(mkDim "Angle" "A" 1000)

vs, I suppose..., this?

type Angle = NormalizeDim (Length ./. Length)

Yes, exactly. In fact,

type Angle = NormalizeDim (Length ./. Length) 

is just a convoluted way of writing

 type Angle = NoDim

but following the SI derived unit conventions that state that radians are the same as m/m or else put "meters per meters".

As a consequence, Radian are considered a derived unit in SI whereas in AngleSI they are a base unit

Yeah, I can give that a go, but before that, for my sanity, can I submit a change where I do a few little things like these?

* Adding an explicit export list to https://github.com/AliceRixte/convert-units/blob/fc3732c51712168818d405008847dc8456912405/src/Data/Units/NonStd/Frequency.hs#L1
   and whichever other module does not have one.

* Use `mkUnit` instead of `mkUnitNoFactor at https://github.com/AliceRixte/convert-units/blob/fc3732c51712168818d405008847dc8456912405/src/Data/Units/SI/NonStd/Angle.hs#L23
   https://github.com/AliceRixte/convert-units/blob/fc3732c51712168818d405008847dc8456912405/src/Data/Units/SI/NonStd/Angle.hs#L29
   https://github.com/AliceRixte/convert-units/blob/fc3732c51712168818d405008847dc8456912405/src/Data/Units/SI/NonStd/Angle.hs#L35
   based on what you explained?

Sure please go ahead! Thanks a lot for proposing such contributions :-)

@Aster89
Copy link
Author

Aster89 commented Oct 3, 2025

Ehmmmm... in an attempt to cabal build && cabal test to see what breaks with export list () I've got this error on cabal test:

Build profile: -w ghc-9.12.2 -O1
In order, the following will be built (use -v for more details):
 - convert-units-0 (test:convert-units-test) (first run)
Preprocessing test suite 'convert-units-test' for convert-units-0...
Building test suite 'convert-units-test' for convert-units-0...
ghc-9.12.2: could not execute: hspec-discover

but it happens also on fc3732c.

Any idea?

Oh, I've created the .cabal file by executing hpack, so I suppose maybe my question boils down to "how do I build and test with cabal?"


Never mind, found an answer at haskell/actions#254 (comment): adding

  build-tool-depends: hspec-discover:hspec-discover

in

test-suite convert-units-test

allows me to successfully run cabal test.

@Aster89
Copy link
Author

Aster89 commented Oct 3, 2025

Not quite doable, as mkUnit expects a Rational factor, which pi obviously isn't. Not sure why you've only allowed Rational.

@AliceRixte
Copy link
Owner

AliceRixte commented Oct 4, 2025

Not quite doable, as mkUnit expects a Rational factor, which pi obviously isn't. Not sure why you've only allowed Rational.

Yes, this is a bit annoying, I agree. However, I haven't figured it out yet, and I'm not sure it is desirable to authorize anything else as Rational. Here is why:

mkUnit calls the following internal function:

mkConvFactorInstance :: Quote m => Name -> Rational -> m [Dec]

which is implemented as follows:

mkConvFactorInstance unitName fctr = [d|
  instance Fractional a => ConversionFactor $(conT unitName) a where
    factor = $(litE (RationalL fctr))
  |]

For the case of pi, this instance is wrong, we want instead, as it is implemented in Data.Units.AngleSI.NonStd, an instance using Floating:

instance Floating a => ConversionFactor Degree a where
  factor = pi / 180
  {-# INLINE factor #-}

But how do you actually implement this in TH ? Maybe it is doable but I personally don't know how.

First try

mkConvFactorInstance :: (Floating b, Quote m) => Name -> b -> m [Dec]
mkConvFactorInstance unitName fctr =
 [d|
 instance Floating a => ConversionFactor $(conT unitName) a where
   factor = $___ 
 |]

it is unclear to me how to use fctr :: b to produce an expression of type a inside the instance. Using something like fractional coercion realToFrac might do the job but I think it is dirty.

But it would also mean we always ask the Floating constraint to be satisfied, even if it is not needed.

Maybe the solution would be to have two TH constructors: mkUnit and mkUnitFloating.

Second try

The following code compiles fine:

mkConvFactorInstance :: (Real a, Quote m) => Name -> a -> m [Dec]
mkConvFactorInstance unitName fctr =
  let fctrRat = toRational fctr in
  [d|
  instance Fractional a => ConversionFactor $(conT unitName) a where
    factor = $(litE (RationalL fctrRat))
  |]

But this is absolutely wrong, because we are not actually using pi, only its rational representation.

@AliceRixte
Copy link
Owner

AliceRixte commented Oct 4, 2025

Ehmmmm... in an attempt to cabal build && cabal test to see what breaks with export list () I've got this error on cabal test:

Personally I use stack, and if I want to use cabal I first invoke stack to get a cabal file, then use that file.

@Aster89
Copy link
Author

Aster89 commented Oct 4, 2025

Hi, @AliceRixte , can you please give a look at this work in progress? I've left quite a few comments with questions.

@AliceRixte
Copy link
Owner

Hi @Aster89 ! It looks things are advancing! I'm currently in hollydays, I'll make a code review when I'm back (in a bit more than a week)

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.

2 participants