packages feed

uom-plugin 0.3.0.0 → 0.4.0.0

raw patch · 31 files changed

+1620/−1029 lines, 31 filesdep +QuickCheckdep +doctestdep +ghc-tcplugin-apidep −ghc-tcplugins-extradep ~basedep ~containersdep ~ghcPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, doctest, ghc-tcplugin-api

Dependencies removed: ghc-tcplugins-extra

Dependency ranges changed: base, containers, ghc, hlint, tasty, tasty-hunit, template-haskell, units-parser

API changes (from Hackage documentation)

- Data.UnitsOfMeasure.Internal: instance (GHC.Enum.Enum a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Enum.Enum (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Internal: instance (GHC.Float.Floating a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Float.Floating (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Internal: instance (GHC.Float.RealFloat a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Float.RealFloat (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Internal: instance (GHC.Num.Num a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Num.Num (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Internal: instance (GHC.Real.Fractional a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.Fractional (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Internal: instance (GHC.Real.Integral a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.Integral (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Internal: instance (GHC.Real.Real a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.Real (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Internal: instance (GHC.Real.RealFrac a, u ~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.RealFrac (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Read: instance (Data.UnitsOfMeasure.Singleton.KnownUnit (Data.UnitsOfMeasure.Internal.Unpack u), u ~ Data.UnitsOfMeasure.Internal.Pack (Data.UnitsOfMeasure.Internal.Unpack u), GHC.Read.Read a) => GHC.Read.Read (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure: infixr 8 ^:
+ Data.UnitsOfMeasure: type family Unpack u
+ Data.UnitsOfMeasure.Convert: -- <tt><a>CanonicalBaseUnit</a> b</tt> must itself be canonical.
+ Data.UnitsOfMeasure.Convert: -- canonical, then <tt><a>CanonicalBaseUnit</a> b = b</tt>. Otherwise,
+ Data.UnitsOfMeasure.Convert: -- | The canonical base unit for this base unit. If <tt>b</tt> is
+ Data.UnitsOfMeasure.Defs: type family MkUnit s
+ Data.UnitsOfMeasure.Internal: infix 4 ~~
+ Data.UnitsOfMeasure.Internal: infixr 8 ^:
+ Data.UnitsOfMeasure.Internal: instance (GHC.Enum.Enum a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Enum.Enum (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: instance (GHC.Float.Floating a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Float.Floating (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: instance (GHC.Float.RealFloat a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Float.RealFloat (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: instance (GHC.Num.Num a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Num.Num (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: instance (GHC.Real.Fractional a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.Fractional (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: instance (GHC.Real.Integral a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.Integral (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: instance (GHC.Real.Real a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.Real (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: instance (GHC.Real.RealFrac a, u GHC.Types.~ Data.UnitsOfMeasure.Internal.One) => GHC.Real.RealFrac (Data.UnitsOfMeasure.Internal.Quantity a u)
+ Data.UnitsOfMeasure.Internal: type family MkUnit s
+ Data.UnitsOfMeasure.Read: instance (Data.UnitsOfMeasure.Singleton.KnownUnit (Data.UnitsOfMeasure.Internal.Unpack u), u GHC.Types.~ Data.UnitsOfMeasure.Internal.Pack (Data.UnitsOfMeasure.Internal.Unpack u), GHC.Read.Read a) => GHC.Read.Read (Data.UnitsOfMeasure.Internal.Quantity a u)
- Data.UnitsOfMeasure.Convert: conversionBase :: (HasCanonicalBaseUnit b, (Base b ~ CanonicalBaseUnit b)) => proxy b -> Quantity Rational (Base b /: CanonicalBaseUnit b)
+ Data.UnitsOfMeasure.Convert: conversionBase :: (HasCanonicalBaseUnit b, Base b ~ CanonicalBaseUnit b) => proxy b -> Quantity Rational (Base b /: CanonicalBaseUnit b)
- Data.UnitsOfMeasure.Convert: ratio :: forall a u v (proxy :: Unit -> *) proxy'. (Fractional a, Convertible u v) => proxy' (proxy u) -> proxy' (proxy v) -> Quantity a (u /: v)
+ Data.UnitsOfMeasure.Convert: ratio :: forall a u v (proxy :: Unit -> Type) proxy'. (Fractional a, Convertible u v) => proxy' (proxy u) -> proxy' (proxy v) -> Quantity a (u /: v)
- Data.UnitsOfMeasure.Convert: type family CanonicalBaseUnit b :: Unit;
+ Data.UnitsOfMeasure.Convert: type family HasCanonical u
- Data.UnitsOfMeasure.Internal: MkQuantity :: a -> Quantity a
+ Data.UnitsOfMeasure.Internal: MkQuantity :: a -> Quantity a (u :: Unit)
- Data.UnitsOfMeasure.Singleton: [SCons] :: KnownSymbol x => proxy x -> SList xs -> SList (x : xs)
+ Data.UnitsOfMeasure.Singleton: [SCons] :: KnownSymbol x => proxy x -> SList xs -> SList (x ': xs)

Files

+ CHANGELOG.md view
@@ -0,0 +1,37 @@+# Changelog++## [0.4.0.0] 2022-10-08+* Support building on GHC 9.0, 9.2 and 9.4 (thanks to Phil de Joux and Sam Derbyshire)+* Drop support for all previous versions of GHC+* Doctest the tutorial+* Various bug fixes and minor tweaks++## [0.3.0.0] 2018-07-13+### Added+* Support building on GHC 8.2 (but not yet 8.4)+* Expose toRational' in Data.UnitsOfMeasure+* An hlint test suite (thanks to Phil de Joux)+* Packaging improvements++### Fixed+* Fix unit safety bug in GHC 8.0 and later (see #22)++## [0.2.0.1] 2016-05-10+* Support building on GHC 8.0++## [0.2.0.0] 2015-12-31+### Added+* Data.UnitsOfMeasure.Read module and a Read instance for Quantity+* Make it possible to declare derived compound units+* Define litres, hectares, radians and steradians++### Fixed+* Prevent cyclic definitions of convertible units++## [0.1.1.0] 2015-11-27+### Added+* More conversion ratios (thanks to Joe Hermaszewski)+* Storable and NFData instances for Quantity (thanks to Marcin Mrotek)++## [0.1.0.0] 2015-08-19+* First public release
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2014, Adam Gundry+Copyright (c) 2014-2022, Adam Gundry  All rights reserved. 
+ README.md view
@@ -0,0 +1,10 @@+# Units of measure as a GHC type-checker plugin++:warning: This library is experimental, and may lead to unexpected type-checking failures or even type soundness bugs.++The `uom-plugin` library adds support for units of measure as a GHC type-checker plugin.  See [Data.UnitsOfMeasure.Tutorial](https://hackage.haskell.org/package/uom-plugin/docs/Data-UnitsOfMeasure-Tutorial.html) for an introduction to the library, and [the accompanying paper](http://adam.gundry.co.uk/pub/typechecker-plugins/) for more background.  An example of a package that uses the library is given in [uom-plugin-examples](https://github.com/adamgundry/uom-plugin/tree/master/uom-plugin-examples).++The latest version of the library is tested with GHC 9.0 to 9.4. Older versions of `uom-plugin` (0.3 and earlier) work with the GHC 7.10, 8.0 and 8.2 series. There are no versions supporting GHC 8.4 to 8.10 ([#43][i43]). Running `cabal haddock` on this library requires GHC 9.4 (see [#66][i66]).++[i43]: https://github.com/adamgundry/uom-plugin/issues/43+[i66]: https://github.com/adamgundry/uom-plugin/issues/66
− changelog
@@ -1,24 +0,0 @@--*-change-log-*---0.3.0.0 Adam Gundry <adam@well-typed.com> June 2018-	* Support building on GHC 8.2 (but not yet 8.4)-	* Fix unit safety bug in GHC 8.0 and later (see #22)-	* Expose toRational' in Data.UnitsOfMeasure-	* Add hlint test suite (thanks to Phil de Joux)-	* Packaging improvements--0.2.0.1 Adam Gundry <adam@well-typed.com> May 2016-	* Support building on GHC 8.0--0.2.0.0 Adam Gundry <adam@well-typed.com> December 2015-	* Add Data.UnitsOfMeasure.Read module and a Read instance for Quantity-	* Make it possible to declare derived compound units-	* Define litres, hectares, radians and steradians-	* Prevent cyclic definitions of convertible units--0.1.1.0 Adam Gundry <adam@well-typed.com> November 2015-	* Add more conversion ratios (thanks to Joe Hermaszewski)-	* Add Storable and NFData instances for Quantity (thanks to Marcin Mrotek)--0.1.0.0 Adam Gundry <adam@well-typed.com> August 2015-	* First public release
+ doc/Data/UnitsOfMeasure/Tutorial.hs view
@@ -0,0 +1,351 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}++-- | This module gives a brief introduction to the @uom-plugin@+-- library.+module Data.UnitsOfMeasure.Tutorial+  (+  -- * Introduction+  -- $intro++  -- * Setup+  -- $ghc-setup++  -- * Interactive Setup+  -- $ghci-setup++  -- * The 'Unit' Kind+  -- $units++  -- * Declaring Units+  -- $decls++  -- * Creating Quantities+  -- $create++  -- ** Literals+  -- $literal++  -- ** Dimensionless Literals+  -- $dimless++  -- ** Multiplication by One+  -- $multiplication-by-one++  -- ** Attaching Units+  -- $attach++  -- ** Detaching Units+  -- $detach++  -- * Operations on Quantities+  -- $ops++  -- * Unit Polymorphism+  -- $polymorphism++  -- * Further Reading+  -- $reading+  ) where++import Data.UnitsOfMeasure++-- $setup+-- >>> import Data.UnitsOfMeasure.Defs ()++-- $intro+--+-- The @uom-plugin@ adds support for type safe units of measure. Its+-- typechecker plugin automatically solves equality constraints between units+-- of measure.++-- $ghc-setup+--+-- To use the @uom-plugin@ library, import "Data.UnitsOfMeasure" after making+-- GHC aware of the plugin and enabling language extensions.+--+-- >>> {-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}+-- >>> {-# LANGUAGE DataKinds, QuasiQuotes, TypeOperators #-}+-- >>> import Data.UnitsOfMeasure+--+-- In a module that imports the library but has not enabled the plugin or+-- enabled the required extensions you will likely get mysterious unsolved+-- constraint errors when working with units. It is only with the plugin+-- enabled that GHC can solve these.++-- $ghci-setup+--+-- To start experimenting with @uom-plugin@ in GHCi you will need to do the+-- equivalent setup.+--+-- >>> :seti -fplugin Data.UnitsOfMeasure.Plugin -XDataKinds -XQuasiQuotes -XTypeOperators+-- >>> import Data.UnitsOfMeasure++-- $units+--+-- Units of measure, such as kilograms or metres per second, are represented by+-- the abstract kind 'Unit'.  They can be built out of 'One', 'Base',+-- ('Data.UnitsOfMeasure.Internal.*:'), ('Data.UnitsOfMeasure.Internal./:') and+-- ('Data.UnitsOfMeasure.Internal.^:').  Base units are represented as+-- type-level strings (with kind 'Symbol').+--+-- >>> :kind One+-- One :: Unit+-- >>> :kind Base "m" /: Base "s"+-- Base "m" /: Base "s" :: Unit+--+-- The template Haskell quasiquoter 'u' gives a nice syntax for units (see+-- module+-- @[Text.Parse.Units](http://hackage.haskell.org/package/units-parser/docs/Text-Parse-Units.html)@+-- from the @[units-parser](http://hackage.haskell.org/package/units-parser)@+-- package for details of the syntax).  When used in a type, the quasiquoter+-- produces an expression of kind 'Unit'.+--+-- >>> :kind! [u| m^2 |]+-- [u| m^2 |] :: Unit+-- = Base "m" *: Base "m"+-- >>> :kind! [u| kg m/s |]+-- [u| kg m/s |] :: Unit+-- = (Base "kg" *: Base "m") /: Base "s"++-- $decls+--+-- Base and derived units need to be declared before use, otherwise you will+-- get unsolved constraints like @'KnownUnit' ('Unpack' ('MkUnit' "m"))@.  When+-- the TH quasiquoter 'u' is used in a declaration context, it creates new base+-- or derived units.  Alternatively, 'declareBaseUnit' and 'declareDerivedUnit'+-- can be used as top-level TH declaration splices. Where declaring new units,+-- you will also need a couple more extensions.+--+-- >>> {-# LANGUAGE TypeFamilies, UndecidableInstances #-}+--+-- > declareBaseUnit "m"+-- > declareDerivedUnit "N" "kg m / s^2"+-- > [u| kg, s |]+--+-- Note that these lines must appear in a module, not GHCi.  For+-- experimenting interactively, "Data.UnitsOfMeasure.Defs" provides+-- definitions of common units, but is subject to change.++-- $create+--+-- A numeric value @__a__@ annotated with units @__u__@ is a @'Quantity' __a__+-- __u__@. Without using the internal @'MkQuantity'@ constructor, we can use+-- literals, multiplication by 1 and unit attaching functions to create+-- quantities.++-- $literal+--+-- For literal quantities use the 'u' quasiquoter, putting the number before+-- the unit. The most general polymorphic type will be inferred.+--+-- >>> :type [u| 1 m |]+-- [u| 1 m |] :: Num a => Quantity a (Base "m")+-- >>> :type [u| 1.0 m |]+-- [u| 1.0 m |] :: Fractional a => Quantity a (Base "m")+-- >>> :type [u| 1 % 1 m |]+-- [u| 1 % 1 m |] :: Fractional a => Quantity a (Base "m")+--+-- Adding a full or partial type signature can make the underlying+-- representational type more concrete.+--+-- >>> :seti -XPartialTypeSignatures -fno-warn-partial-type-signatures+--+-- >>> :type [u| 1 m |] :: _ Int _+-- [u| 1 m |] :: _ Int _ :: Quantity Int (Base "m")+-- >>> :type [u| 1 m |] :: _ Double _+-- [u| 1 m |] :: _ Double _ :: Quantity Double (Base "m")+-- >>> :type [u| 1 m |] :: _ Rational _+-- [u| 1 m |] :: _ Rational _ :: Quantity Rational (Base "m")+--+-- Note how the 'u' quasiquoter can be used for the units in the type too. This+-- is redundant repetition with a literal but is useful when adding type+-- signatures elsewhere.+--+-- >>> [u| 1.1 m / s |] :: Quantity Double [u| m / s |]+-- [u| 1.1 m / s |]+--+-- The units parser handles various number formats.+--+-- >>> [u| 0o1327 Hz |]+-- [u| 727 s^-1 |]+-- >>> [u| 0x2d7 Hz |]+-- [u| 727 s^-1 |]+-- >>> [u| 325e-2 in |]+-- [u| 3.25 in |]+-- >>> [u| 36E+2 s |]+-- [u| 3600.0 s |]+-- >>> [u| 14.67e1 mi |]+-- [u| 146.7 mi |]++-- $dimless+--+-- Without putting the numeric value in a quotation and without templating+-- altogether we can create dimensionless units, those with units of 'One'.+--+-- >>> 1 :: Quantity Int One+-- [u| 1 |]+-- >>> [u| 1 |]+-- [u| 1 |]+-- >>> :type [u| 1 |]+-- [u| 1 |] :: Num a => Quantity a One+-- >>> :type 1 :: Quantity _ [u| 1 |]+-- 1 :: Quantity _ [u| 1 |] :: Num w => Quantity w One+-- >>> :type 1.0 :: Quantity _ One+-- 1.0 :: Quantity _ One :: Fractional w => Quantity w One+--+-- Quoted rationals are fine but type annotating them as dimensionless+-- quantities will not work.+--+-- >>> :type [u| 1 % 1 |]+-- [u| 1 % 1 |] :: Fractional a => Quantity a One+-- >>> import Data.Ratio ((%))+-- >>> :type (1 % 1) :: Quantity _ One+-- <BLANKLINE>+-- ...+-- ...Couldn't match expected type...Quantity w1 One...+-- ...with actual type...GHC.Real.Ratio...+-- ...+--+-- Things get a little weird when not being explicit about Quantity.+--+-- >>> :type 1 :: _ _ [u| 1 |]+-- 1 :: _ _ [u| 1 |]+--   :: ...+--      Num (w1 w2 One) =>+--      w1 w2 One+-- >>> :type 1 :: _ _ One+-- 1 :: _ _ One+--   :: ...+--      Num (w1 w2 One) =>+--      w1 w2 One+-- >>> :type 1.0 :: _ _ One+-- 1.0 :: _ _ One+--   :: ...+--      Fractional (w1 w2 One) =>+--      w1 w2 One++-- $multiplication-by-one+--+-- The product of a numeric value and one of a unit will attach those units to+-- the value.+--+-- >>> [u| 1 m s^-2 |] *: 9.8+-- [u| 9.8 m / s^2 |]+-- >>> 9.8 *: [u| 1 m s^-2 |]+-- [u| 9.8 m / s^2 |]+--+-- The same trick works with dimensionless values.+--+-- >>> [u| 1 m s^-2 |] *: [u| 9.8 |]+-- [u| 9.8 m / s^2 |]+-- >>> [u| 9.8 |] *: [u| 1 m s^-2 |]+-- [u| 9.8 m / s^2 |]++-- $attach+--+-- Quoted units without a value specialise to functions we can use to attach+-- units to unitless numeric values.+--+-- >>> [u| m / s^2 |] 9.8+-- [u| 9.8 m / s^2 |]+-- >>> [u| m s^-2 |] 9.8+-- [u| 9.8 m / s^2 |]+-- >>> [u| m / s / s |] 9.8+-- [u| 9.8 m / s^2 |]+-- >>> [u| s^-2 m |] 9.8+-- [u| 9.8 m / s^2 |]+--+-- Composition of these functions doesn't work as expected. It doesn't apply+-- composed units to the numeric value.+--+-- >>> [u| m |] $ [u| s^-2 |] 9.8+-- [u| [u| 9.8 s^-2 |] m |]+-- >>> [u| s^-2 |] $ [u| m |] 9.8+-- [u| [u| 9.8 m |] s^-2 |]+-- >>> [u| m |] . [u| s^-2 |] $ 9.8+-- [u| [u| 9.8 s^-2 |] m |]+-- >>> [u| s^-2 |] . [u| m |] $ 9.8+-- [u| [u| 9.8 m |] s^-2 |]+-- >>> [u| m |] [u| 9.8 s^-2 |]+-- [u| [u| 9.8 s^-2 |] m |]+-- >>> [u| s^-2 |] [u| 9.8 m |]+-- [u| [u| 9.8 m |] s^-2 |]+-- >>> [u| m |] . [u| s^-1 |] . [u| s^-1 |] $ 9.8+-- [u| [u| [u| 9.8 s^-1 |] s^-1 |] m |]++-- $detach+--+-- The underlying numeric value of a quantity may be extracted with+-- 'unQuantity', detaching the units:+--+-- >>> unQuantity [u| 15 kg |]+-- 15+-- >>> unQuantity [u| 9.8 m / s^2 |]+-- 9.8+-- >>> unQuantity <$> [[u| 1 kg |], [u| 2 kg |]]+-- [1,2]+-- >>> unQuantity . [u| kg |] <$> [1,2]+-- [1,2]+-- >>> :type unQuantity . [u| kg |]+-- unQuantity . [u| kg |] :: c -> c++-- $ops+--+-- The usual arithmetic operators from 'Num' and related typeclasses+-- are restricted to operating on dimensionless quantities.  Thus+-- using them directly on quantities with units will result in errors:+--+-- >>> 2 * [u| 5 m |]+-- <BLANKLINE>+-- ...+-- ... Couldn't match type...Base "m"...with...One...+-- ...+--+--+-- >>> [u| 2 m/s |] + [u| 5 m/s |]+-- <BLANKLINE>+-- ...+-- ... Couldn't match type...Base "m" /: Base "s"...with...One...+-- ...+--+-- Instead, "Data.UnitsOfMeasure" provides more general arithmetic+-- operators including ('+:'), ('-:'), ('*:') and ('/:').  These may+-- be used to perform unit-safe arithmetic:+--+-- >>> 2 *: [u| 5 m |]+-- [u| 10 m |]+--+-- >>> [u| 2 m / s |] +: [u| 5 m / s |]+-- [u| 7 m / s |]+--+-- However, unit errors will be detected by the type system:+--+-- >>>  [u| 3 m |] -: [u| 1 s |]+-- <BLANKLINE>+-- ...+-- ... Couldn't match type...Base "s"+-- ... with...Base "m"...+-- ...++-- $polymorphism+--+-- It is easy to work with arbitrary units (type variables of kind+-- 'Unit') rather than particular choices of unit.  The typechecker+-- plugin ensures that type inference is well-behaved and+-- automatically solves equations between units (e.g. making unit+-- multiplication commutative):+--+-- >>> let cube x = x *: x *: x+-- >>> :t cube+-- cube :: Num a => Quantity a v -> Quantity a (v *: (v *: v))+--+-- >>> let f x y = (x *: y) +: (y *: x)+-- >>> :t f+-- f :: Num a => Quantity a v -> Quantity a u -> Quantity a (u *: v)+++-- $reading+--+--  * <http://adam.gundry.co.uk/pub/typechecker-plugins/ Paper about uom-plugin>+--+--  * <https://ghc.haskell.org/trac/ghc/wiki/Plugins/TypeChecker Plugins on the GHC wiki>
− hlint/HLint.hs
@@ -1,19 +0,0 @@-module Main (main) where--import Language.Haskell.HLint (hlint)-import System.Exit (exitFailure, exitSuccess)--arguments :: [String]-arguments =-    [ "lint"-    , "--ignore=Parse error"-    , "--ignore=Use fewer imports"  -- This is a pain for the CPP in TcPluginExtras-    , "src"-    , "tests"-    , "hlint"-    ]--main :: IO ()-main = do-    hints <- hlint arguments-    if null hints then exitSuccess else exitFailure
− package.yaml
@@ -1,75 +0,0 @@-# This YAML file describes your package. Stack will automatically generate a-# Cabal file when you run `stack build`. See the hpack website for help with-# this file: <https://github.com/sol/hpack>.-author: Adam Gundry <adam@well-typed.com>-maintainer: Adam Gundry <adam@well-typed.com>-name: uom-plugin-synopsis: Units of measure as a GHC typechecker plugin-description: |--    The @uom-plugin@ library adds support for units of measure to GHC-    using the new experimental facility for typechecker plugins, which-    is available in GHC 7.10 and later.  See-    "Data.UnitsOfMeasure.Tutorial" for an introduction to the library.-category: Type System-license: BSD3-license-file: LICENSE-stability: experimental-github: adamgundry/uom-plugin-copyright: Copyright (c) 2014-2018, Adam Gundry-tested-with: >+-  GHC == 7.10.3,-  GHC == 8.0.2,-  GHC == 8.2.2-extra-source-files:-- package.yaml-- changelog-ghc-options:-- -Wall-- -fno-warn-unticked-promoted-constructors-library:-  dependencies:-  - base >=4.7 && <5-  - deepseq >=1.3 && <1.5-  - ghc >= 7.9 && <8.4-  - ghc-tcplugins-extra >=0.1 && <0.3-  - template-haskell >=2.9 && <2.13-  - containers >=0.5 && <0.6-  - units-parser >=0.1 && <0.2-  source-dirs: src-  exposed-modules:-  - Data.UnitsOfMeasure-  - Data.UnitsOfMeasure.Convert-  - Data.UnitsOfMeasure.Defs-  - Data.UnitsOfMeasure.Internal-  - Data.UnitsOfMeasure.Plugin-  - Data.UnitsOfMeasure.Read-  - Data.UnitsOfMeasure.Show-  - Data.UnitsOfMeasure.Singleton-  - Data.UnitsOfMeasure.Tutorial-tests:-  units:-    dependencies:-    - base-    - uom-plugin-    - tasty >=0.10 && <1.1-    - tasty-hunit >=0.9 && <0.10.1-    ghc-options:-    - -O0-    other-extensions: TemplateHaskell-    main: Tests.hs-    source-dirs:-    - tests-  hlint:-    dependencies:-    - base-    - hlint >= 1.7 && <2.2-    ghc-options:-    - -Wall-    - -O0-    - -rtsopts-    - -threaded-    - -with-rtsopts=-N-    main: HLint.hs-    source-dirs:-    - hlint-version: '0.3.0.0'
src/Data/UnitsOfMeasure.hs view
@@ -5,7 +5,6 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}  -- | See "Data.UnitsOfMeasure.Tutorial" for how to use this module.
src/Data/UnitsOfMeasure/Convert.hs view
@@ -1,18 +1,15 @@ {-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}--#if __GLASGOW_HASKELL__ > 710 {-# LANGUAGE UndecidableSuperClasses #-}-#endif  {-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-} @@ -73,8 +70,8 @@ import Data.UnitsOfMeasure.Internal import Data.UnitsOfMeasure.Singleton -import GHC.Exts ( Constraint )-import GHC.TypeLits+import Data.Kind (Type, Constraint)+import GHC.TypeLits (Symbol)   -- | Class to capture the dimensions to which base units belong.  For@@ -95,29 +92,35 @@  -- | Convert a unit into its canonical representation, where units are -- represented syntactically.-type family ToCBU (u :: UnitSyntax Symbol) :: Unit where+type ToCBU :: UnitSyntax Symbol -> Unit+type family ToCBU u where   ToCBU (xs :/ ys) = ListToCBU xs /: ListToCBU ys -type family ListToCBU (xs :: [Symbol]) :: Unit where+type ListToCBU :: [Symbol] -> Unit+type family ListToCBU xs where   ListToCBU '[]       = One   ListToCBU (x ': xs) = CanonicalBaseUnit x *: ListToCBU xs  -- | This constraint will be satisfied if all the base units in a -- syntactically represented unit have associated canonical -- representations.-type family HasCanonical (u :: UnitSyntax Symbol) :: Constraint where+type HasCanonical :: UnitSyntax Symbol -> Constraint+type family HasCanonical u where   HasCanonical (xs :/ ys) = (AllHasCanonical xs, AllHasCanonical ys) -type family AllHasCanonical (xs :: [Symbol]) :: Constraint where+type AllHasCanonical :: [Symbol] -> Constraint+type family AllHasCanonical xs where   AllHasCanonical '[] = ()   AllHasCanonical (x ': xs) = (HasCanonicalBaseUnit x, AllHasCanonical xs)  -- | This constraint will be satisfied if all the base units in a -- syntactically represented unit are in their canonical form.-type family IsCanonical (u :: UnitSyntax Symbol) :: Constraint where+type IsCanonical :: UnitSyntax Symbol -> Constraint+type family IsCanonical u where   IsCanonical (xs :/ ys) = (AllIsCanonical xs, AllIsCanonical ys) -type family AllIsCanonical (xs :: [Symbol]) :: Constraint where+type AllIsCanonical :: [Symbol] -> Constraint+type family AllIsCanonical xs where   AllIsCanonical '[] = ()   AllIsCanonical (x ': xs) = (CanonicalBaseUnit x ~ Base x, AllIsCanonical xs) @@ -125,6 +128,7 @@ conversionRatio :: forall proxy u . Good u                => proxy u -> Quantity Rational (u /: ToCBU (Unpack u)) conversionRatio _ = help (unitSing :: SUnit (Unpack u))+{-# INLINABLE conversionRatio #-}  help :: forall u . HasCanonical u => SUnit u -> Quantity Rational (Pack u /: ToCBU u) help (SUnit xs ys) = help' xs /: help' ys@@ -149,12 +153,14 @@ -- are @v@, provided @u@ and @v@ have the same dimension. convert :: forall a u v . (Fractional a, Convertible u v) => Quantity a u -> Quantity a v convert = (ratio (undefined :: proxy' (proxy v)) (undefined :: proxy' (proxy u)) *:)+{-# INLINABLE convert #-}  -- | Calculate the conversion ratio between two units with the same -- dimension.  The slightly unusual proxy arguments allow this to be -- called using quasiquoters to specify the units, for example -- @'ratio' [u| ft |] [u| m |]@.-ratio :: forall a u v (proxy :: Unit -> *) proxy' .+ratio :: forall a u v (proxy :: Unit -> Type) proxy' .          (Fractional a, Convertible u v)       => proxy' (proxy u) -> proxy' (proxy v) -> Quantity a (u /: v) ratio _ _ = fromRational' $ conversionRatio (undefined :: proxy u) /: conversionRatio (undefined :: proxy v)+{-# INLINABLE ratio #-}
src/Data/UnitsOfMeasure/Defs.hs view
@@ -52,4 +52,7 @@   , au = 149597870700 m |]  -- Some random other units-[u| ft = 100 % 328 m, in = 0.0254 m, mi = 1609.344 m, mph = mi/h |]+[u| ft = 100 % 328 m+  , in = 0.0254 m+  , mi = 1609.344 m+  , mph = mi/h |]
src/Data/UnitsOfMeasure/Internal.hs view
@@ -1,10 +1,9 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE KindSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}@@ -68,32 +67,24 @@ data Unit  -- | Dimensionless unit (identity element)-type family One :: Unit-#if __GLASGOW_HASKELL__ >= 711-  where-#endif+type family One :: Unit where  -- | Base unit-type family Base (b :: Symbol) :: Unit-#if __GLASGOW_HASKELL__ >= 711-  where-#endif+type Base :: Symbol -> Unit+type family Base b where  -- | Multiplication for units of measure-type family (u :: Unit) *: (v :: Unit) :: Unit-#if __GLASGOW_HASKELL__ >= 711-  where-#endif+type (*:) :: Unit -> Unit -> Unit+type family u *: v where  -- | Division for units of measure-type family (u :: Unit) /: (v :: Unit) :: Unit-#if __GLASGOW_HASKELL__ >= 711-  where-#endif+type (/:) :: Unit -> Unit -> Unit+type family u /: v where  -- | Exponentiation (to a positive power) for units of measure; -- negative exponents are not yet supported (they require an Integer kind)-type family (u :: Unit) ^: (n :: Nat)  :: Unit where+type (^:) :: Unit -> Nat -> Unit+type family u ^: n where   u ^: 0 = One   u ^: 1 = u   u ^: n = u *: (u ^: (n-1))@@ -152,53 +143,65 @@ -- | Addition ('+') of quantities requires the units to match. (+:) :: Num a => Quantity a u -> Quantity a u -> Quantity a u MkQuantity x +: MkQuantity y = MkQuantity (x + y)+{-# INLINE (+:) #-}  -- | Multiplication ('*') of quantities multiplies the units. (*:) :: (Num a, w ~~ u *: v) => Quantity a u -> Quantity a v -> Quantity a w MkQuantity x *: MkQuantity y = MkQuantity (x * y)+{-# INLINE (*:) #-}  -- | Subtraction ('-') of quantities requires the units to match. (-:) :: Num a => Quantity a u -> Quantity a u -> Quantity a u MkQuantity x -: MkQuantity y = MkQuantity (x - y)+{-# INLINE (-:) #-}  -- | Negation ('negate') of quantities is polymorphic in the units. negate' :: Num a => Quantity a u -> Quantity a u negate' (MkQuantity x) = MkQuantity (negate x)+{-# INLINE negate' #-}  -- | Absolute value ('abs') of quantities is polymorphic in the units. abs' :: Num a => Quantity a u -> Quantity a u abs' (MkQuantity x) = MkQuantity (abs x)+{-# INLINE abs' #-}  -- | The sign ('signum') of a quantity gives a dimensionless result. signum' :: Num a => Quantity a u -> Quantity a One signum' (MkQuantity x) = MkQuantity (signum x)+{-# INLINE signum' #-}  -- | Convert an 'Integer' quantity into any 'Integral' type ('fromInteger'). fromInteger' :: Integral a => Quantity Integer u -> Quantity a u fromInteger' (MkQuantity x) = MkQuantity (fromInteger x)+{-# INLINE fromInteger' #-}   -- | Division ('/') of quantities divides the units. (/:) :: (Fractional a, w ~~ u /: v) => Quantity a u -> Quantity a v -> Quantity a w MkQuantity x /: MkQuantity y = MkQuantity (x / y)+{-# INLINE (/:) #-}  -- | Reciprocal ('recip') of quantities reciprocates the units. recip' :: (Fractional a, w ~~ One /: u) => Quantity a u -> Quantity a w recip' (MkQuantity x) = MkQuantity (recip x)+{-# INLINE recip' #-}  -- | Convert a 'Rational' quantity into any 'Fractional' type ('fromRational'). fromRational' :: Fractional a => Quantity Rational u -> Quantity a u fromRational' (MkQuantity x) = MkQuantity (fromRational x)+{-# INLINE fromRational' #-}  -- | Convert any 'Real' quantity into a 'Rational' type ('toRational'). toRational' :: Real a => Quantity a u -> Quantity Rational u toRational' (MkQuantity x) = MkQuantity (toRational x)+{-# INLINE toRational' #-}   -- | Taking the square root ('sqrt') of a quantity requires its units -- to be a square.  Fractional units are not currently supported. sqrt' :: (Floating a, w ~~ u ^: 2) => Quantity a w -> Quantity a u sqrt' (MkQuantity x) = MkQuantity (sqrt x)+{-# INLINE sqrt' #-}   -- | Syntactic representation of a unit as a pair of lists of base@@ -217,11 +220,13 @@ -- inverse of 'Unpack' up to the equational theory of units, but it is -- not a right inverse (because there are multiple list -- representations of the same unit).-type family Pack (u :: UnitSyntax Symbol) :: Unit where+type Pack :: UnitSyntax Symbol -> Unit+type family Pack u where   Pack (xs :/ ys) = Prod xs /: Prod ys  -- | Take the product of a list of base units.-type family Prod (xs :: [Symbol]) :: Unit where+type Prod :: [Symbol] -> Unit+type family Prod xs where   Prod '[]       = One   Prod (x ': xs) = Base x *: Prod xs @@ -237,19 +242,15 @@ -- it does not allow the structure of the unit to be observed.  The -- reduction behaviour is implemented by the plugin, because we cannot -- define it otherwise.-type family Unpack (u :: Unit) :: UnitSyntax Symbol-#if __GLASGOW_HASKELL__ >= 711-  where-#endif+type Unpack :: Unit -> UnitSyntax Symbol+type family Unpack u where   -- | This is a bit of a hack, honestly, but a good hack.  Constraints -- @u ~~ v@ are just like equalities @u ~ v@, except solving them will -- be delayed until the plugin.  This may lead to better inferred types.-type family (u :: Unit) ~~ (v :: Unit) :: Constraint-#if __GLASGOW_HASKELL__ >= 711-  where-#endif+type (~~) :: Unit -> Unit -> Constraint+type family u ~~ v where  infix 4 ~~ @@ -260,4 +261,5 @@ -- -- The instances displayed by Haddock are available only if -- "Data.UnitsOfMeasure.Defs" is imported.-type family MkUnit (s :: Symbol) :: Unit+type MkUnit :: Symbol -> Unit+type family MkUnit s
src/Data/UnitsOfMeasure/Plugin.hs view
@@ -1,7 +1,4 @@-{-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ > 710-{-# LANGUAGE PatternSynonyms #-}-#endif+{-# LANGUAGE DataKinds #-}  -- | This module defines a typechecker plugin that solves equations -- involving units of measure.  To use it, add@@ -15,160 +12,195 @@   ( plugin   ) where -import Plugins--import TcEvidence-import TcRnTypes-import TcType-import TcPluginM--import Coercion-import DataCon-import Type-import TyCon-import TysWiredIn--import FastString-import Outputable+import GhcApi (TcCoercion, ctEvPred, ctEvTerm, typeKind, heqDataCon, evDFunApp, dataConName, dataConWrapId, occName, occNameFS, tyConDataCons, (<+>), isWanted, isGivenCt, isGiven, UnivCoProvenance(PluginProv), mkPrimEqPred, Type(TyConApp), heqTyCon) -import OccName ( occName, occNameFS, mkTcOcc )-import Module+import qualified GHC.Plugins as Plugins+import GHC.TcPlugin.API as PluginAPI  import Data.Either-import Data.List  import Data.UnitsOfMeasure.Plugin.Convert import Data.UnitsOfMeasure.Plugin.NormalForm import Data.UnitsOfMeasure.Plugin.Unify-import TcPluginExtras -#if __GLASGOW_HASKELL__ > 710-import TyCoRep-#else-import TypeRep-#endif--import GHC.TcPluginM.Extra ( evByFiat, tracePlugin, lookupModule, lookupName )- -- | The plugin that GHC will load when this module is used with the -- @-fplugin@ option.-plugin :: Plugin-plugin = defaultPlugin { tcPlugin = const $ Just uomPlugin }+plugin :: Plugins.Plugin+plugin =+    Plugins.defaultPlugin+        { Plugins.tcPlugin = const $ Just $ PluginAPI.mkTcPlugin uomPlugin+        , Plugins.pluginRecompile = const $ pure Plugins.NoForceRecompile+        } -uomPlugin :: TcPlugin-uomPlugin = tracePlugin-                "uom-plugin"-                TcPlugin { tcPluginInit  = lookupUnitDefs-                         , tcPluginSolve = unitsOfMeasureSolver-                         , tcPluginStop  = const $ return ()-                         }+uomPlugin :: PluginAPI.TcPlugin+uomPlugin =+    PluginAPI.TcPlugin+        { PluginAPI.tcPluginInit    = lookupUnitDefs+        , PluginAPI.tcPluginSolve   = unitsOfMeasureSolver+        , PluginAPI.tcPluginRewrite = unitsOfMeasureRewrite+        , PluginAPI.tcPluginStop    = const $ return ()+        }  -unitsOfMeasureSolver :: UnitDefs -> [Ct] -> [Ct] -> [Ct] -> TcPluginM TcPluginResult-unitsOfMeasureSolver uds givens _deriveds []      = do-    zonked_cts <- mapM zonkCt givens-    let (unit_givens , _) = partitionEithers $ zipWith foo givens $ map (toUnitEquality uds) zonked_cts++unitsOfMeasureSolver :: UnitDefs -> [Ct] -> [Ct] -> PluginAPI.TcPluginM PluginAPI.Solve PluginAPI.TcPluginSolveResult+unitsOfMeasureSolver uds givens []      = do+    PluginAPI.tcPluginTrace "unitsOfMeasureSolver simplifying givens" $ ppr givens+    let (unit_givens0 , _) = partitionEithers $ zipWith foo givens $ map (toUnitEquality uds) givens+    let unit_givens = filter is_useful unit_givens0     case unit_givens of-      []    -> return $ TcPluginOk [] []+      []    -> return $ PluginAPI.TcPluginOk [] []       (_:_) -> do         sr <- simplifyUnits uds $ map snd unit_givens-        tcPluginTrace "unitsOfMeasureSolver simplified givens only" $ ppr sr+        PluginAPI.tcPluginTrace "unitsOfMeasureSolver simplified givens only" $ ppr sr         case sr of-          -- Simplified tvs []    evs eqs -> TcPluginOk (map (solvedGiven . fst) unit_givens) []-          Simplified _    -> return $ TcPluginOk [] []+          -- TODO: givens simplification is currently disabled, because if we emit a given+          -- constraint like x[sk] ~ Base "kg" then GHC will "simplify" all occurrences+          -- of the type family application Base "kg" to the skolem variable x[sk].+          -- This can then result in loops as the rewriter will turn the fam app into+          -- the variable, then the plugin will "solve" it again.+          Simplified _ -> pure $ PluginAPI.TcPluginOk [] []+          Simplified ss   -> do+              -- TODO: we ought to generate evidence that depends on the+              -- previous givens (and similarly when simplifying wanteds, the+              -- evidence we generate should depend on the new wanteds).+              -- Otherwise we could potentially have a soundness issue e.g. if a+              -- GADT pattern match brings a unit equality into scope, but we+              -- later float out something that depends on it.+              let usefuls = simplifySubst ss+              xs <- mapM (substItemToCt uds) usefuls+              pure $ PluginAPI.TcPluginOk (map (solvedGiven . siCt) usefuls) xs+          -- Simplified _    -> return $ PluginAPI.TcPluginOk [] []           Impossible eq _ -> reportContradiction uds eq   where     foo :: Ct -> Either UnitEquality Ct -> Either (Ct, UnitEquality) Ct     foo ct (Left x)    = Left (ct, x)     foo _  (Right ct') = Right ct' -    -- solvedGiven ct = (ctEvTerm (ctEvidence ct), ct)+    solvedGiven ct = (ctEvTerm (ctEvidence ct), ct) +    -- TODO: if the simplify givens stage makes progress, we want to emit new+    -- givens in case GHC can substitute into constraints other than unit+    -- equalities.  However, we don't want to cause a loop by repeatedly+    -- re-simplifying the same givens.  We currently have a conservative check+    -- to see if it is useful to simplify a unit equality: if neither side of+    -- the original equality was a single variable.  There are "useful" cases+    -- this misses, however, e.g. v^2 ~ v.+    is_useful (_, ue) = isUsefulUnitEquality ue -unitsOfMeasureSolver uds givens _deriveds wanteds = do-  xs <- lookForUnpacks uds givens wanteds-  if not $ null xs then return $ TcPluginOk [] xs else do+unitsOfMeasureSolver uds givens wanteds = do     let (unit_wanteds, _) = partitionEithers $ map (toUnitEquality uds) wanteds     case unit_wanteds of-      []    -> return $ TcPluginOk [] []+      []    -> return $ PluginAPI.TcPluginOk [] []       (_:_) -> do-        (unit_givens , _) <- partitionEithers . map (toUnitEquality uds) <$> mapM zonkCt givens+        let (unit_givens , _) = partitionEithers $ map (toUnitEquality uds) givens         sr <- simplifyUnits uds unit_givens-        tcPluginTrace "unitsOfMeasureSolver simplified givens" $ ppr sr+        PluginAPI.tcPluginTrace "unitsOfMeasureSolver simplified givens" $ ppr sr+        -- TODO: it is somewhat questionable to simplify the givens again+        -- here. In principle we should be able to simplify them at the+        -- simplify-givens stage, turn them into a substitution, and have GHC+        -- apply the substitution.         case sr of           Impossible eq _ -> reportContradiction uds eq           Simplified ss   -> do sr' <- simplifyUnits uds $ map (substsUnitEquality (simplifySubst ss)) unit_wanteds-                                tcPluginTrace "unitsOfMeasureSolver simplified wanteds" $ ppr sr'+                                PluginAPI.tcPluginTrace "unitsOfMeasureSolver simplified wanteds" $ ppr sr'                                 case sr' of-                                  Impossible _eq _ -> return $ TcPluginOk [] [] -- Don't report a contradiction, see #22-                                  Simplified ss'  -> TcPluginOk [ (evMagic uds ct, ct) | eq <- simplifySolved ss', let ct = fromUnitEquality eq ]+                                  Impossible _eq _ -> return $ PluginAPI.TcPluginOk [] [] -- Don't report a contradiction, see #22+                                  Simplified ss'  -> PluginAPI.TcPluginOk [ (evMagic uds ct, ct) | eq <- simplifySolved ss', let ct = fromUnitEquality eq ]                                                          <$> mapM (substItemToCt uds) (filter (isWanted . ctEvidence . siCt) (substsSubst (simplifyUnsubst ss) (simplifySubst ss')))  -reportContradiction :: UnitDefs -> UnitEquality -> TcPluginM TcPluginResult-reportContradiction uds eq = TcPluginContradiction . pure <$> fromUnitEqualityForContradiction uds eq+reportContradiction :: UnitDefs -> UnitEquality -> PluginAPI.TcPluginM PluginAPI.Solve PluginAPI.TcPluginSolveResult+reportContradiction uds eq = PluginAPI.TcPluginContradiction . pure <$> fromUnitEqualityForContradiction uds eq  -- See #22 for why we need this-fromUnitEqualityForContradiction :: UnitDefs -> UnitEquality -> TcPluginM Ct+fromUnitEqualityForContradiction :: UnitDefs -> UnitEquality -> PluginAPI.TcPluginM PluginAPI.Solve Ct fromUnitEqualityForContradiction uds (UnitEquality ct u v) = case classifyPredType $ ctEvPred $ ctEvidence ct of     EqPred NomEq _ _ -> return ct-    _ | isGivenCt ct -> newGivenCt  (ctLoc ct) (mkEqPred u' v') (mkFunnyEqEvidence (ctPred ct) u' v')-      | otherwise    -> newWantedCt (ctLoc ct) (mkEqPred u' v')+    _ | isGivenCt ct -> PluginAPI.mkNonCanonical <$> PluginAPI.newGiven  (ctLoc ct) (mkPrimEqPred u' v') (evTermToExpr (mkFunnyEqEvidence (ctPred ct) u' v'))+      | otherwise    -> PluginAPI.mkNonCanonical <$> PluginAPI.newWanted (ctLoc ct) (mkPrimEqPred u' v')   where     u' = reifyUnit uds u     v' = reifyUnit uds v  -substItemToCt :: UnitDefs -> SubstItem -> TcPluginM Ct+substItemToCt :: UnitDefs -> SubstItem -> PluginAPI.TcPluginM PluginAPI.Solve Ct substItemToCt uds si-      | isGiven (ctEvidence ct) = newGivenCt loc prd $ evByFiat "units" ty1 ty2-      | otherwise               = newWantedCt loc prd+      | isGiven (ctEvidence ct) = PluginAPI.mkNonCanonical <$> PluginAPI.newGiven loc prd (evByFiatExpr "units" ty1 ty2)+      | otherwise               = PluginAPI.mkNonCanonical <$> PluginAPI.newWanted loc prd       where-        prd  = mkEqPred ty1 ty2+        prd  = mkPrimEqPred ty1 ty2         ty1  = mkTyVarTy (siVar si)         ty2  = reifyUnit uds (siUnit si)         ct   = siCt si         loc  = ctLoc ct  -lookForUnpacks :: UnitDefs -> [Ct] -> [Ct] -> TcPluginM [Ct]-lookForUnpacks uds givens wanteds = mapM unpackCt unpacks-  where-    unpacks = concatMap collectCt $ givens ++ wanteds--    collectCt ct = collectType ct $ ctEvPred $ ctEvidence ct--    collectType ct (AppTy f s)      = collectType ct f ++ collectType ct s-    collectType ct (TyConApp tc [a])-      | tc == unpackTyCon uds       = case maybeConstant =<< normaliseUnit uds a of-                                        Just xs -> [(ct,a,xs)]-                                        _       -> []-    collectType ct (TyConApp _ as)  = concatMap (collectType ct) as-    collectType ct (FunTy t v)      = collectType ct t ++ collectType ct v-    collectType ct (ForAllTy _ t)   = collectType ct t-    collectType _  _                = [] -- TyVarTy, LitTy from 7.10, plus CastTy and CoercionTy in 8.0+{-+TODO: this leads to errors like this on GHC 9.2, but seems to work on 9.4? -    unpackCt (ct,a,xs) = newGivenCt loc (mkEqPred ty1 ty2) (evByFiat "units" ty1 ty2)-      where-        ty1 = TyConApp (unpackTyCon uds) [a]-        ty2 = mkTyConApp (unitSyntaxPromotedDataCon uds)-               [ typeSymbolKind-               , foldr promoter nil ys-               , foldr promoter nil zs ]-        loc = ctLoc ct+*** Core Lint errors : in result of Desugar (before optimization) ***+src/Data/UnitsOfMeasure/Defs.hs:19:4: warning:+    Trans coercion mis-match: (IsCanonical+                                 Univ(nominal plugin "units"+                                      :: Unpack (Base "m"), '["m"] ':/ '[]))_N+                              ; Sym (D:R:IsCanonical[0] <'["m"]>_N <'[]>_N)+      IsCanonical (Unpack (Base "m")) ~ IsCanonical ('["m"] ':/ '[])+      (AllIsCanonical '["m"], AllIsCanonical '[]) ~ IsCanonical+                                                      ('["m"] ':/ '[])+    In the RHS of $cp1HasCanonicalBaseUnit_alno :: IsCanonical+                                                     (Unpack (CanonicalBaseUnit "m"))+    In the body of letrec with binders $d(%%)_alnP :: () :: Constraint+    In the body of letrec with binders $d(%%)_alnN :: () :: Constraint+    In the body of letrec with binders $d~_alnO :: Base "m" ~ Base "m"+    In the body of letrec with binders $d(%,%)_alnM :: (Base "m"+                                                        ~ Base "m",+                                                        () :: Constraint)+    In the body of letrec with binders $d(%,%)_alnL :: ((Base "m"+                                                         ~ Base "m",+                                                         () :: Constraint),+                                                        () :: Constraint)+    Substitution: [TCvSubst+                     In scope: InScope {}+                     Type env: []+                     Co env: []]+-} -        ys = concatMap (\ (s, i) -> if i > 0 then genericReplicate i s       else []) xs-        zs = concatMap (\ (s, i) -> if i < 0 then genericReplicate (abs i) s else []) xs+unitsOfMeasureRewrite+  :: UnitDefs ->+    PluginAPI.UniqFM+        TyCon+        ([Ct] -> [Type] -> PluginAPI.TcPluginM PluginAPI.Rewrite PluginAPI.TcPluginRewriteResult)+unitsOfMeasureRewrite uds = PluginAPI.listToUFM [(unpackTyCon uds, unpackRewriter uds)] -    nil = mkTyConApp (promoteDataCon nilDataCon) [typeSymbolKind]+unpackRewriter :: UnitDefs -> [Ct] -> [Type] -> PluginAPI.TcPluginM PluginAPI.Rewrite PluginAPI.TcPluginRewriteResult+unpackRewriter uds _givens [ty] = do+  case maybeConstant =<< normaliseUnit uds ty of+    Nothing -> do PluginAPI.tcPluginTrace "unpackRewriter: no rewrite" (ppr ty)+                  pure PluginAPI.TcPluginNoRewrite+    Just u  -> do PluginAPI.tcPluginTrace "unpackRewriter: rewrite" (ppr ty <+> ppr u)+                  pure $ let reduct = reifyUnitUnpacked uds u+                         in let co = PluginAPI.mkPluginUnivCo "units" Nominal (mkTyConApp (unpackTyCon uds) [ty]) reduct+                            in PluginAPI.TcPluginRewriteTo (PluginAPI.Reduction co reduct) []+unpackRewriter _ _ tys = do+    PluginAPI.tcPluginTrace "unpackRewriter: wrong number of arguments?" (ppr tys)+    pure PluginAPI.TcPluginNoRewrite -    promoter x t = mkTyConApp cons_tycon [typeSymbolKind, mkStrLitTy x, t]-    cons_tycon = promoteDataCon consDataCon+-- TODO: the following is nonsense+lookupModule' :: PluginAPI.MonadTcPlugin m => PluginAPI.ModuleName -> p -> m PluginAPI.Module+lookupModule' modname _pkg = do+  r <- PluginAPI.findImportedModule modname PluginAPI.NoPkgQual --  (PluginAPI.OtherPkg pkg)+  case r of+    PluginAPI.Found _ md -> pure md+    _ -> do r' <- PluginAPI.findImportedModule modname PluginAPI.NoPkgQual+            case r' of+              PluginAPI.Found _ md -> pure md+              _ -> error "lookupModule: not Found"  -lookupUnitDefs :: TcPluginM UnitDefs+lookupUnitDefs :: PluginAPI.TcPluginM PluginAPI.Init UnitDefs lookupUnitDefs = do-    md <- lookupModule myModule myPackage+    md <- lookupModule' myModule myPackage     u <- look md "Unit"     b <- look md "Base"     o <- look md "One"@@ -184,11 +216,43 @@                        [d] -> promoteDataCon d                        _   -> error $ "lookupUnitDefs/getDataCon: missing " ++ s -    look md s = tcLookupTyCon =<< lookupName md (mkTcOcc s)+    look md s = PluginAPI.tcLookupTyCon =<< PluginAPI.lookupOrig md (mkTcOcc s)     myModule  = mkModuleName "Data.UnitsOfMeasure.Internal"     myPackage = fsLit "uom-plugin"  +-- | Make up evidence for a fake equality constraint @t1 ~~ t2@ by coercing+-- bogus evidence of type @t1 ~ t2@.+mkFunnyEqEvidence :: Type -> Type -> Type -> EvTerm+mkFunnyEqEvidence t t1 t2 =+    castFrom `evCast'` castTo+    where+        castFrom :: EvTerm+        castFrom = evDFunApp funId tys terms+            where+                funId :: Id+                funId = dataConWrapId heqDataCon++                tys :: [Kind]+                tys = [typeKind t1, typeKind t2, t1, t2]++                terms :: [EvExpr]+                terms = [evByFiatExpr "units" t1 t2]++        castTo :: TcCoercion+        castTo =+            mkUnivCo from Representational tySource t+            where+                from :: UnivCoProvenance+                from = PluginProv "units"++                tySource :: Type+                tySource = mkHEqPred t1 t2++mkHEqPred :: Type -> Type -> Type+mkHEqPred t1 t2 = TyConApp heqTyCon [typeKind t1, typeKind t2, t1, t2]++ -- | Produce bogus evidence for a constraint, including actual -- equality constraints and our fake '(~~)' equality constraints. evMagic :: UnitDefs -> Ct -> EvTerm@@ -199,29 +263,15 @@       , tc == equivTyCon uds -> mkFunnyEqEvidence t t1 t2     _                    -> error "evMagic" --- | Make up evidence for a fake equality constraint @t1 ~~ t2@ by--- coercing bogus evidence of type @t1 ~ t2@ (or its heterogeneous--- variant, in GHC 8.0).-mkFunnyEqEvidence :: Type -> Type -> Type -> EvTerm-#if __GLASGOW_HASKELL__ >= 800-mkFunnyEqEvidence t t1 t2 = EvDFunApp (dataConWrapId heqDataCon) [typeKind t1, typeKind t2, t1, t2] [evByFiat "units" t1 t2]-                       `EvCast` mkUnivCo (PluginProv "units") Representational (mkHEqPred t1 t2) t-#else-mkFunnyEqEvidence t t1 t2 = evByFiat "units" t1 t2-                       `EvCast` TcCoercion (mkUnivCo (fsLit "units") Representational (mkTyConApp eqTyCon [typeKind t1, t1, t2]) t)-#endif---#if __GLASGOW_HASKELL__ >= 800+evByFiat :: String -> PluginAPI.TcType -> PluginAPI.TcType -> EvTerm+evByFiat s t1 t2 = PluginAPI.mkPluginUnivEvTerm s Nominal t1 t2 -#if __GLASGOW_HASKELL__ < 802-pattern FunTy :: Type -> Type -> Type-pattern FunTy t v = ForAllTy (Anon t) v-#endif+evByFiatExpr :: String -> PluginAPI.TcType -> PluginAPI.TcType -> EvExpr+evByFiatExpr s t1 t2 = evTermToExpr $ PluginAPI.mkPluginUnivEvTerm s Nominal t1 t2 -mkEqPred :: Type -> Type -> Type-mkEqPred = mkPrimEqPred+evTermToExpr :: EvTerm -> EvExpr+evTermToExpr (EvExpr e) = e+evTermToExpr _ = error "evTermToExpr" -mkHEqPred :: Type -> Type -> Type-mkHEqPred t1 t2 = TyConApp heqTyCon [typeKind t1, typeKind t2, t1, t2]-#endif+evCast' :: EvTerm -> TcCoercion -> EvTerm+evCast' = evCast . evTermToExpr
src/Data/UnitsOfMeasure/Plugin/Convert.hs view
@@ -1,28 +1,17 @@-{-# LANGUAGE CPP #-} module Data.UnitsOfMeasure.Plugin.Convert   ( UnitDefs(..)   , unitKind   , isUnitKind   , normaliseUnit   , reifyUnit+  , reifyUnitUnpacked   ) where -import TyCon-import Type-#if __GLASGOW_HASKELL__ > 802-import TcType ()-#else-import TcType-#endif--#if __GLASGOW_HASKELL__ > 710-import TyCoRep-#else-import TypeRep-#endif-+import GhcApi (Type(..), typeSymbolKind, nilDataCon, consDataCon, tcSplitTyConApp_maybe, coreView, isFamilyTyCon) import Data.List +import GHC.TcPlugin.API+ import Data.UnitsOfMeasure.Plugin.NormalForm  -- | Contains references to the basic unit constructors declared in@@ -42,7 +31,7 @@  -- | 'Unit' promoted to a kind unitKind :: UnitDefs -> Kind-unitKind uds = TyConApp (promoteTyCon $ unitKindCon uds) []+unitKind uds = TyConApp (unitKindCon uds) []  -- | Is this the 'Unit' kind? isUnitKind :: UnitDefs -> Kind -> Bool@@ -89,7 +78,20 @@     reifyAtom (FamAtom f tys) = mkTyConApp f tys  -#if __GLASGOW_HASKELL__ > 710-promoteTyCon :: TyCon -> TyCon-promoteTyCon = id-#endif+-- | Convert a constant unit normal form into a type expression of kind+-- @UnitSyntax Symbol@.+reifyUnitUnpacked  :: UnitDefs -> [(BaseUnit, Integer)] -> Type+reifyUnitUnpacked uds xs =+  mkTyConApp (unitSyntaxPromotedDataCon uds)+             [ typeSymbolKind+             , foldr promoter nil ys+             , foldr promoter nil zs+             ]+  where+    ys = concatMap (\ (s, i) -> if i > 0 then genericReplicate i s       else []) xs+    zs = concatMap (\ (s, i) -> if i < 0 then genericReplicate (abs i) s else []) xs++    nil = mkTyConApp (promoteDataCon nilDataCon) [typeSymbolKind]++    promoter x t = mkTyConApp cons_tycon [typeSymbolKind, mkStrLitTy x, t]+    cons_tycon = promoteDataCon consDataCon
src/Data/UnitsOfMeasure/Plugin/NormalForm.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE TupleSections #-}+ module Data.UnitsOfMeasure.Plugin.NormalForm   ( Atom(..)   , BaseUnit@@ -18,6 +19,7 @@      -- * Predicates   , isOne+  , maybeSingleVariable   , isConstant   , maybeConstant   , isBase@@ -31,23 +33,17 @@   , substUnit   ) where -import Type-import TyCon-import VarSet+import Prelude hiding ((<>))+import GhcApi (elemVarSet, tyCoVarsOfType, tyCoVarsOfTypes, text, (<>))+import GhcApi.Compare (cmpType, cmpTypes, cmpTyCon, thenCmp) -import FastString-import Outputable-import Util ( thenCmp )+import GHC.TcPlugin.API  import qualified Data.Foldable as Foldable import qualified Data.Map as Map-import Data.List ( sortBy )+import Data.List ( sortOn ) import Data.Maybe-import Data.Ord -import TcPluginExtras-- -- | Base units are just represented as strings, for simplicity type BaseUnit = FastString @@ -139,6 +135,12 @@ isOne :: NormUnit -> Bool isOne = Map.null . _NormUnit +-- | Test whether a unit consists of a single variable with multiplicity 1.+maybeSingleVariable :: NormUnit -> Maybe TyVar+maybeSingleVariable x = case Map.toList (_NormUnit x) of+    [(VarAtom v, 1)] -> Just v+    _                -> Nothing+ -- | Test whether a unit is constant (contains only base literals) isConstant :: NormUnit -> Bool isConstant = all isBaseLiteral . Map.keys . _NormUnit@@ -147,7 +149,7 @@ maybeConstant :: NormUnit -> Maybe [(BaseUnit, Integer)] maybeConstant = mapM getBase . Map.toList . _NormUnit   where-    getBase (BaseAtom ty, i) = (\ b -> (b, i)) <$> isStrLitTy ty+    getBase (BaseAtom ty, i) = (, i) <$> isStrLitTy ty     getBase _                = Nothing  -- | Test whether an atom is a base unit (but not necessarily a@@ -170,14 +172,14 @@ occurs :: TyVar -> NormUnit -> Bool occurs a = any occursAtom . Map.keys . _NormUnit   where-    occursAtom (BaseAtom ty)   = elemVarSet a $ tyVarsOfType ty+    occursAtom (BaseAtom ty)   = elemVarSet a $ tyCoVarsOfType ty     occursAtom (VarAtom b)     = a == b-    occursAtom (FamAtom _ tys) = elemVarSet a $ tyVarsOfTypes tys+    occursAtom (FamAtom _ tys) = elemVarSet a $ tyCoVarsOfTypes tys   -- | View a unit as a list of atoms in order of ascending absolute exponent ascending :: NormUnit -> [(Atom, Integer)]-ascending = sortBy (comparing (abs . snd)) . Map.toList . _NormUnit+ascending = sortOn (abs . snd) . Map.toList . _NormUnit  -- | Drop a variable from a unit leftover :: TyVar -> NormUnit -> NormUnit@@ -192,12 +194,3 @@ substUnit a v u = case Map.lookup (VarAtom a) $ _NormUnit u of                     Nothing -> u                     Just i  -> (v ^: i) *: leftover a u---#if __GLASGOW_HASKELL__ > 710-tyVarsOfType :: Type -> TyCoVarSet-tyVarsOfType = tyCoVarsOfType--tyVarsOfTypes :: [Type] -> TyCoVarSet-tyVarsOfTypes = tyCoVarsOfTypes-#endif
src/Data/UnitsOfMeasure/Plugin/Unify.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DataKinds #-}+ module Data.UnitsOfMeasure.Plugin.Unify   ( SubstItem(..)   , substsSubst@@ -5,23 +7,23 @@   , UnitEquality(..)   , toUnitEquality   , fromUnitEquality+  , isUsefulUnitEquality   , SimplifyState(..)   , SimplifyResult(..)   , simplifyUnits+  , initialState   ) where -import FastString-import Name-import Outputable-import TcRnMonad ( Ct, isGiven, ctEvidence, ctEvPred )-import TcType-import Type-import Var+import GhcApi (text, (<+>), ($$), typeKind, ctEvPred, isGiven, mkSysTvName) +import GHC.TcPlugin.API as PluginAPI+import qualified GHC.TcPlugin.API.Internal as PluginAPI.Internal++import qualified GHC.Tc.Utils.Monad as GHC+import qualified GHC.Tc.Utils.TcMType as GHC+ import Data.UnitsOfMeasure.Plugin.Convert import Data.UnitsOfMeasure.Plugin.NormalForm-import TcPluginExtras-import TcPluginM   -- | A substitution is essentially a list of (variable, unit) pairs,@@ -68,34 +70,34 @@ -- | Attempt to unify two normalised units to produce a unifying -- substitution.  The 'Ct' is the equality between the non-normalised -- (and perhaps less substituted) unit type expressions.-unifyUnits :: UnitDefs -> UnitEquality -> TcPluginM UnifyResult-unifyUnits uds (UnitEquality ct u0 v0) = do tcPluginTrace "unifyUnits" (ppr u0 $$ ppr v0)+unifyUnits :: UnitDefs -> UnitEquality -> PluginAPI.TcPluginM PluginAPI.Solve UnifyResult+unifyUnits uds (UnitEquality ct u0 v0) = do PluginAPI.tcPluginTrace "unifyUnits" (ppr u0 $$ ppr v0)                                             unifyOne uds ct [] [] [] (u0 /: v0) -unifyOne :: UnitDefs -> Ct -> [TyVar] -> TySubst -> TySubst -> NormUnit -> TcPluginM UnifyResult+unifyOne :: UnitDefs -> Ct -> [TyVar] -> TySubst -> TySubst -> NormUnit -> PluginAPI.TcPluginM PluginAPI.Solve UnifyResult unifyOne uds ct tvs subst unsubst u       | isOne u           = return $ Win tvs subst unsubst       | isConstant u      = return   Lose-      | otherwise         = tcPluginTrace "unifyOne" (ppr u) >> go [] (ascending u)+      | otherwise         = {- tcPluginTrace "unifyOne" (ppr u) >> -} go [] (ascending u)        where-        go :: [(Atom, Integer)] -> [(Atom, Integer)] -> TcPluginM UnifyResult+        go :: [(Atom, Integer)] -> [(Atom, Integer)] -> PluginAPI.TcPluginM PluginAPI.Solve UnifyResult         go _  []                       = return $ Draw tvs subst unsubst         go ls (at@(VarAtom a, i) : xs) = do-            tch <- if given_mode then return True else isTouchableTcPluginM a+            tch <- if given_mode then return True else PluginAPI.isTouchableTcPluginM a             let r = divideExponents (-i) $ leftover a u             case () of                 () | tch && divisible i u -> return $ if occurs a r then Draw tvs subst unsubst                                                                     else Win tvs (extendSubst (SubstItem a r ct) subst) unsubst-                   | tch && any (not . isBase . fst) xs -> do beta <- newUnitVar+                   | tch && not (all (isBase . fst) xs) -> do beta <- newUnitVar                                                               let subst'   = extendSubst (SubstItem a    (varUnit beta *: r) ct) subst                                                                   unsubst' = extendSubst (SubstItem beta (varUnit a    /: r) ct) unsubst                                                               unifyOne uds ct (beta:tvs) subst' unsubst' $ substUnit a (varUnit beta *: r) u                    | otherwise            -> go (at:ls) xs          go ls (at@(FamAtom f tys, i) : xs) = do-          mb <- matchFam f tys-          case normaliseUnit uds . snd =<< mb of+          mb <- PluginAPI.matchFam f tys+          case normaliseUnit uds . PluginAPI.reductionReducedType =<< mb of             Just v  -> unifyOne uds ct tvs subst unsubst $ mkNormUnit (ls ++ xs) *: v ^: i             Nothing -> go (at:ls) xs         go ls (at@(BaseAtom  _, _) : xs) = go (at:ls) xs@@ -103,15 +105,16 @@          given_mode = isGiven (ctEvidence ct) -        newUnitVar | given_mode = newSkolemTyVar $ unitKind uds-                   | otherwise  = newFlexiTyVar  $ unitKind uds+        newUnitVar | given_mode = PluginAPI.Internal.unsafeLiftTcM $ newSkolemTyVar $ unitKind uds+                   | otherwise  = PluginAPI.Internal.unsafeLiftTcM $ GHC.newFlexiTyVar  $ unitKind uds          newSkolemTyVar kind = do-            x <- newUnique+            x <- GHC.newUnique             let name = mkSysTvName x (fsLit "beta")-            return $ mkTcTyVar name kind vanillaSkolemTv+            return $ PluginAPI.mkTyVar name kind -- mkTcTyVar name kind vanillaSkolemTv  + data UnitEquality = UnitEquality Ct NormUnit NormUnit  instance Outputable UnitEquality where@@ -135,6 +138,14 @@ fromUnitEquality (UnitEquality ct _ _) = ct  +isUsefulUnitEquality :: UnitEquality -> Bool+isUsefulUnitEquality (UnitEquality _ lhs rhs) =+    case (maybeSingleVariable lhs, maybeSingleVariable rhs) of+        (Nothing, Nothing) -> True+        (Just v, _)        -> occurs v rhs+        (_, Just v)        -> occurs v lhs++ data SimplifyState   = SimplifyState { simplifyFreshVars :: [TyVar]                   , simplifySubst     :: TySubst@@ -163,14 +174,14 @@   ppr (Simplified ss)     = text "Simplified" $$ ppr ss   ppr (Impossible eq eqs) = text "Impossible" <+> ppr eq <+> ppr eqs -simplifyUnits :: UnitDefs -> [UnitEquality] -> TcPluginM SimplifyResult-simplifyUnits uds eqs0 = tcPluginTrace "simplifyUnits" (ppr eqs0) >> simples initialState eqs0+simplifyUnits :: UnitDefs -> [UnitEquality] -> PluginAPI.TcPluginM PluginAPI.Solve SimplifyResult+simplifyUnits uds eqs0 = PluginAPI.tcPluginTrace "simplifyUnits" (ppr eqs0) >> simples initialState eqs0   where-    simples :: SimplifyState -> [UnitEquality] -> TcPluginM SimplifyResult+    simples :: SimplifyState -> [UnitEquality] -> PluginAPI.TcPluginM PluginAPI.Solve SimplifyResult     simples ss [] = return $ Simplified ss     simples ss (eq:eqs) = do         ur <- unifyUnits uds (substsUnitEquality (simplifySubst ss) eq)-        tcPluginTrace "unifyUnits result" (ppr ur)+        PluginAPI.tcPluginTrace "unifyUnits result" (ppr ur)         case ur of           Win  tvs subst unsubst -> let (ss', xs) = win eq tvs subst unsubst ss                                     in simples ss' (xs ++ eqs)
src/Data/UnitsOfMeasure/Read.hs view
@@ -4,6 +4,8 @@ {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} {-# OPTIONS_GHC -fno-warn-orphans #-} 
src/Data/UnitsOfMeasure/Show.hs view
@@ -4,7 +4,6 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}  {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-}
src/Data/UnitsOfMeasure/Singleton.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RoleAnnotations #-}@@ -76,12 +75,75 @@   | normaliseUnitSyntax (forgetSUnit su) == normaliseUnitSyntax (forgetSUnit sv) = Just (unsafeCoerce Refl)   | otherwise = Nothing --- | Calculate a normal form of a syntactic unit: a map from base unit--- names to non-zero integers.+-- | Calculate a normal form of a syntactic unit: a map from base unit names to+-- non-zero integers.+--+-- >>> normaliseUnitSyntax ([] :/ [])+-- fromList []+--+-- >>> normaliseUnitSyntax (["m"] :/ [])+-- fromList [("m",1)]+--+-- >>> normaliseUnitSyntax (["m", "m"] :/ [])+-- fromList [("m",2)]+--+-- >>> normaliseUnitSyntax (["m", "m", "m"] :/ [])+-- fromList [("m",3)]+--+-- >>> normaliseUnitSyntax ([] :/ ["m"])+-- fromList [("m",-1)]+--+-- >>> normaliseUnitSyntax ([] :/ ["m", "m"])+-- fromList [("m",-2)]+--+-- >>> normaliseUnitSyntax ([] :/ ["m", "m", "m"])+-- fromList [("m",-3)]+--+-- >>> normaliseUnitSyntax (["m"] :/ ["m"])+-- fromList []+--+-- >>> normaliseUnitSyntax (["m", "m"] :/ ["m"])+-- fromList [("m",1)]+--+-- >>> normaliseUnitSyntax (["m"] :/ ["m", "m"])+-- fromList [("m",-1)]+--+-- >>> normaliseUnitSyntax (["m", "m"] :/ ["m", "m"])+-- fromList []+--+-- >>> normaliseUnitSyntax (["m", "m", "m"] :/ ["m", "m"])+-- fromList [("m",1)]+--+-- >>> normaliseUnitSyntax (["m", "m"] :/ ["m", "m", "m"])+-- fromList [("m",-1)]+--+-- >>> normaliseUnitSyntax (["m", "m", "m"] :/ ["m", "m", "m"])+-- fromList []+--+-- >>> normaliseUnitSyntax (replicate 3 "m" :/ [])+-- fromList [("m",3)]+--+-- >>> normaliseUnitSyntax ([] :/ replicate 3 "m")+-- fromList [("m",-3)]+--+-- >>> normaliseUnitSyntax (replicate 3 "m" :/ replicate 3 "m")+-- fromList []+--+-- >>> normaliseUnitSyntax (["m"] :/ ["s"])+-- fromList [("m",1),("s",-1)]+--+-- >>> normaliseUnitSyntax (["m"] :/ ["s", "s"])+-- fromList [("m",1),("s",-2)]+--+-- >>> normaliseUnitSyntax (["m", "m"] :/ []) == normaliseUnitSyntax (["m", "m"] :/ ["m", "m"])+-- False+--+-- >>> normaliseUnitSyntax (["m"] :/ []) == normaliseUnitSyntax (["m"] :/ ["m", "m"])+-- False normaliseUnitSyntax :: UnitSyntax String -> Map.Map String Integer normaliseUnitSyntax (xs :/ ys) =     Map.filter (/= 0)-        (foldl' (\ m x -> Map.insertWith (-) x 1 m)+        (foldl' (\ m x -> Map.insertWith (+) x (negate 1) m)             (foldl' (\ m x -> Map.insertWith (+) x 1 m) Map.empty xs) ys)  
− src/Data/UnitsOfMeasure/Tutorial.hs
@@ -1,171 +0,0 @@--- | This module gives a brief introduction to the @uom-plugin@--- library.--{-# OPTIONS_GHC -fno-warn-unused-imports #-}-module Data.UnitsOfMeasure.Tutorial-  ( -- $tutorial-  ) where--import Data.UnitsOfMeasure---- $tutorial------ === Prerequisites------ To use the @uom-plugin@ library, simply import "Data.UnitsOfMeasure"--- and pass the option @-fplugin Data.UnitsOfMeasure.Plugin@ to GHC, for--- example by adding the following above the module header of your source--- files:------ > {-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}------ This will enable the typechecker plugin, which automatically solves--- equality constraints between units of measure.  You will also need--- some language extensions:------ > {-# LANGUAGE DataKinds, QuasiQuotes, TypeOperators #-}------ In order to declare new units, you will need:------ > {-# LANGUAGE TypeFamilies, UndecidableInstances #-}--------- === Interactive use------ If experimenting with @uom-plugin@ in GHCi you will need to--- activate the plugin with the command------ >>> :seti -fplugin Data.UnitsOfMeasure.Plugin------ otherwise you will get mysterious unsolved constraint errors.  You--- will probably also need the extensions:------ >>> :seti -XDataKinds -XQuasiQuotes -XTypeOperators--------- === The 'Unit' kind------ Units of measure, such as kilograms or metres per second, are--- represented by the abstract kind 'Unit'.  They can be built out of--- 'One', 'Base', ('Data.UnitsOfMeasure.Internal.*:'),--- ('Data.UnitsOfMeasure.Internal./:') and--- ('Data.UnitsOfMeasure.Internal.^:').  Base units are represented as--- type-level strings (with kind 'Symbol').  For example,------ >>> :kind One--- One :: Unit------ >>> :kind Base "m" /: Base "s"--- Base "m" /: Base "s" :: Unit------ The TH quasiquoter 'u' is provided to give a nice syntax for units--- (see @Text.Parse.Units@ from the @units-parser@ package for details--- of the syntax).  When used in a type, the quasiquoter produces an--- expression of kind 'Unit', for example------ >>> :kind! [u| m^2 |]--- [u| m^2 |] :: Unit--- = Base "m" ^: 2------ >>> :kind! [u| kg m/s |]--- [u|kg m/s|] :: Unit--- = (Base "kg" *: Base "m") /: Base "s"--------- === Declaring base and derived units------ Base and derived units need to be declared before use, otherwise--- you will get unsolved constraints like @'KnownUnit' ('Unpack' ('MkUnit' "m"))@.--- When the TH quasiquoter 'u' is used as in a declaration context, it--- creates new base or derived units.  Alternatively,--- 'declareBaseUnit' and 'declareDerivedUnit' can be used as top-level--- TH declaration splices.  For example:------ > declareBaseUnit "m"--- > declareDerivedUnit "N" "kg m / s^2"--- > [u| kg, s |]------ Note that these lines must appear in a module, not GHCi.  For--- experimenting interactively, "Data.UnitsOfMeasure.Defs" provides--- definitions of common units, but is subject to change.--------- === Creating quantities------ A 'Quantity' is a numeric value annotated with its units.--- Quantities can be created using the 'u' quasiquoter in an--- expression, for example @[u| 5 m |]@ or @[u| 2.2 m/s^2 |]@.  The--- syntax consists of an integer or decimal number, followed by a--- unit.------ The type of a quantity includes the underlying representation type and--- the unit, for example:------ > [u| 5 m |] :: Quantity Int (Base "m")------ or using the 'u' quasiquoter in the type as well:------ > [u| 1.1 m/s |] :: Quantity Double [u| m/s |]------ Numeric literals may be used to produce dimensionless quantities--- (i.e. those with unit 'One'):------ > 2 :: Quantity Int One------ The underlying numeric value of a quantity may be extracted with--- 'unQuantity':------ >>> unQuantity [u| 15 kg |]--- 15--------- === Operations on quantities------ The usual arithmetic operators from 'Num' and related typeclasses--- are restricted to operating on dimensionless quantities.  Thus--- using them directly on quantities with units will result in errors:------ >>> 2 * [u| 5 m |]---   Couldn't match type ‘Base "m"’ with ‘One’...------ >>> [u| 2 m/s |] + [u| 5 m/s |]---   Couldn't match type ‘Base "m" /: Base "s"’ with ‘One’...------ Instead, "Data.UnitsOfMeasure" provides more general arithmetic--- operators including ('+:'), ('-:'), ('*:') and ('/:').  These may--- be used to perform unit-safe arithmetic:------ >>> 2 *: [u| 5 m |]--- [u| 10 m |]------ >>> [u| 2 m / s |] +: [u| 5 m / s |]--- [u| 7 m / s |]------ However, unit errors will be detected by the type system:------ >>>  [u| 3 m |] -: [u| 1 s |]---   Couldn't match type ‘Base "s"’ with ‘Base "m"’...--------- === Unit polymorphism------ It is easy to work with arbitrary units (type variables of kind--- 'Unit') rather than particular choices of unit.  The typechecker--- plugin ensures that type inference is well-behaved and--- automatically solves equations between units (e.g. making unit--- multiplication commutative):------ >>> let cube x = x *: x *: x--- >>> :t cube--- cube :: Num a => Quantity a v -> Quantity a (v ^: 3)------ >>> let f x y = (x *: y) +: (y *: x)--- >>> :t f--- f :: Num a => Quantity a v -> Quantity a u -> Quantity a (u *: v)--------- == Further reading------  * <http://adam.gundry.co.uk/pub/typechecker-plugins/ Paper about uom-plugin>------  * <https://ghc.haskell.org/trac/ghc/wiki/Plugins/TypeChecker Plugins on the GHC wiki>
+ src/GhcApi.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE CPP #-}++module GhcApi+    ( module X+    ) where++import GHC.Core.Coercion as X (mkUnivCo, Coercion)++import GHC.Core.DataCon as X (dataConName, promoteDataCon, dataConWrapId)+import GHC.Data.FastString as X (FastString(..), fsLit)+import GHC as X (mkModuleName)+import GHC.Types.Name as X (mkSysTvName)+import GHC.Types.Name.Occurrence as X (occName, occNameFS, mkTcOcc)+import GHC.Utils.Outputable as X (Outputable(..), (<>), (<+>), ($$), text)+import GHC.Driver.Plugins as X (Plugin(..), defaultPlugin)++import GHC.Tc.Types.Evidence as X+    ( EvTerm(..)+    , TcCoercion+    , TcCoercionR+    , EvExpr+    , evCast+    , evDFunApp+    )++import GHC.Tc.Plugin as X+    ( TcPluginM+    , tcPluginTrace, matchFam, newFlexiTyVar, isTouchableTcPluginM+    , tcLookupTyCon, zonkCt+    , newUnique+    )+-- import GHC.Tc.Types as X (TcPlugin(..), TcPluginResult(..))+import GHC.Tc.Types.Constraint as X+    ( Ct(..), CtLoc+    , ctLoc, ctEvidence, ctEvPred, ctPred, ctEvExpr, ctEvTerm+    , isGiven, isWanted, isGivenCt+    , mkNonCanonical+    )+import GHC.Tc.Utils.TcType as X (tcSplitTyConApp_maybe {-, vanillaSkolemTv -})+import GHC.Core.TyCon as X (TyCon(..), Role(..), isFamilyTyCon, tyConDataCons)++import GHC.Core.TyCo.Rep as X+    ( UnivCoProvenance(PluginProv)+    , Kind+    , Type(TyConApp, TyVarTy, AppTy, ForAllTy, FunTy, LitTy)+    , mkTyVarTy+    , mkFunTy+#if __GLASGOW_HASKELL__ > 900+    , cmpTyLit+#endif+    )++import GHC.Core.Coercion as X (mkPrimEqPred)+import GHC.Core.Predicate as X+    ( EqRel(..)+    , Pred(..)+    , classifyPredType+    )+import GHC as X (PredType)+import GHC.Core.Type as X+    ( splitTyConApp_maybe, typeKind+    , tyCoVarsOfType, tyCoVarsOfTypes+    , mkNumLitTy, mkTyConApp+    , isNumLitTy, isStrLitTy+    , coreView+    , mkStrLitTy+    , nonDetCmpType, nonDetCmpTypes, nonDetCmpTc+    , mkAppTy+    )++import GHC.Builtin.Types as X (typeSymbolKind, nilDataCon, consDataCon, heqTyCon, heqDataCon)++import GHC.Types.Unique as X+    ( getUnique+    , nonDetCmpUnique+    )++import GHC.Types.Var as X+    ( TyVar+    , DFunId+    , Id+    , mkTcTyVar+    )++import GHC.Types.Var.Set as X (TyCoVarSet, elemVarSet)++import GHC.Driver.Plugins as X (PluginRecompile(..))
+ src/GhcApi/Compare.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE CPP #-}++module GhcApi.Compare+  ( cmpType+  , cmpTypes+  , cmpTyCon+  , thenCmp+  ) where++import GhcApi++import GHC.Utils.Misc (thenCmp)++-- TODO: all this is deeply dodgy!  These comparison functions are+-- non-deterministic, so we may end up getting different results on different+-- runs.  Really we should replace them with deterministic versions.++cmpTyCon :: TyCon -> TyCon -> Ordering+cmpTyCon = nonDetCmpTc++cmpType :: Type -> Type -> Ordering+#if __GLASGOW_HASKELL__ > 900+cmpType (LitTy x) (LitTy y) = cmpTyLit x y+cmpType t1 t2 = nonDetCmpType t1 t2+#else+cmpType = nonDetCmpType+#endif++cmpTypes :: [Type] -> [Type] -> Ordering+cmpTypes [] [] = EQ+cmpTypes (t1:ts1) (t2:ts2) = cmpType t1 t2 `thenCmp` cmpTypes ts1 ts2+cmpTypes [] _ = LT+cmpTypes _ [] = GT
− src/TcPluginExtras.hs
@@ -1,62 +0,0 @@-{-# LANGUAGE CPP #-}--module TcPluginExtras-  ( -- * Wrappers-    newUnique-  , newWantedCt-  , newGivenCt--    -- * GHC API changes-  , cmpType-  , cmpTypes-  , cmpTyCon-  ) where--import TcEvidence ( EvTerm )-import TcRnTypes  ( mkNonCanonical )-import TcRnMonad  ( Ct, CtLoc )-import Type       ( PredType )-import TyCon      ( TyCon )--import GHC.TcPluginM.Extra--#if __GLASGOW_HASKELL__ < 711-import Unique     ( Unique )-import qualified TcRnMonad-import TcPluginM ( TcPluginM, unsafeTcPluginTcM )-#else-import TcPluginM ( TcPluginM, newUnique )-#endif--#if __GLASGOW_HASKELL__ < 802-import Type ( cmpType, cmpTypes )-#else-import Type ( Type, nonDetCmpType, nonDetCmpTypes )-import Unique ( getUnique, nonDetCmpUnique )-#endif---#if __GLASGOW_HASKELL__ < 711-newUnique :: TcPluginM Unique-newUnique = unsafeTcPluginTcM TcRnMonad.newUnique-#endif--newWantedCt :: CtLoc -> PredType -> TcPluginM Ct-newWantedCt loc = fmap mkNonCanonical . newWanted loc--newGivenCt :: CtLoc -> PredType -> EvTerm -> TcPluginM Ct-newGivenCt loc prd ev = mkNonCanonical <$> newGiven loc prd ev--#if __GLASGOW_HASKELL__ < 802-cmpTyCon :: TyCon -> TyCon -> Ordering-cmpTyCon = compare-#else-cmpType :: Type -> Type -> Ordering-cmpType = nonDetCmpType--cmpTypes :: [Type] -> [Type] -> Ordering-cmpTypes = nonDetCmpTypes--cmpTyCon :: TyCon -> TyCon -> Ordering-cmpTyCon a b = getUnique a `nonDetCmpUnique` getUnique b-#endif
+ test-suite-doctest/DocTest.hs view
@@ -0,0 +1,12 @@+module Main (main) where++import Test.DocTest (doctest)++arguments :: [String]+arguments =+    [ "-isrc"+    , "src/Data/UnitsOfMeasure/Singleton.hs"+    ]++main :: IO ()+main = doctest arguments
+ test-suite-hlint/HLint.hs view
@@ -0,0 +1,19 @@+module Main (main) where++import Language.Haskell.HLint (hlint)+import System.Exit (exitFailure, exitSuccess)++arguments :: [String]+arguments =+    [ "lint"+    , "--ignore=Parse error"+    , "--ignore=Use fewer imports"  -- This is a pain for the CPP in TcPluginExtras+    , "src"+    , "test-suite-units"+    , "test-suite-hlint"+    ]++main :: IO ()+main = do+    hints <- hlint arguments+    if null hints then exitSuccess else exitFailure
+ test-suite-units/Defs.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Defs where++import Data.UnitsOfMeasure+import Data.UnitsOfMeasure.Defs ()++-- Declarations.+declareBaseUnit "byte"+declareDerivedUnit "bps" "byte / s"+declareConvertibleUnit "kilobyte" 1024 "byte"+declareConvertibleUnit "squiggle" 2 "m/s"++-- This declares a dimensionless unit that requires explicit conversion.+[u| dime = 1 1 |]+dime :: Fractional a => Quantity a [u|dime|] -> Quantity a [u|1|]+dime = convert+++try :: Quantity Double [u| ft^3 m^3 |]+try = convert [u| 2 l^2  |]++++{-++-- These tests demonstrate the need for simplification of givens.  This doesn't+-- currently work, however.++type family F (u :: Unit)++thingy :: (x *: y ~ Base "m") => proxy x y -> F x -> F (Base "m" /: y)+thingy _ x = x++thingy2 :: (x *: x ~ x) => proxy x -> F x -> F One+thingy2 _ x = x++thingy3 :: (x *: x ~ x *: x *: x) => proxy x -> F x -> F One+thingy3 _ x = x++-}
+ test-suite-units/ErrorTests.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QuasiQuotes #-}++{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -fdefer-type-errors #-}+{-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}++#if __GLASGOW_HASKELL__ > 710+{-# OPTIONS_GHC -fno-warn-deferred-type-errors #-}+#endif++module ErrorTests where++import Data.UnitsOfMeasure+import Data.UnitsOfMeasure.Defs ()++import GHC.TypeLits++mismatch1 :: Quantity Double [u| s/m |]+mismatch1 = [u| 3 m/s |]++mismatch1_errors :: [[String]]+mismatch1_errors = couldn'tMatchErrors "Base \"m\" /: Base \"s\"" "Base \"s\" /: Base \"m\""++mismatch2 :: Quantity Int [u| s |]+mismatch2 = [u| 2 m |] +: ([u| 2 s |] :: Quantity Int [u| s |])++mismatch2_errors :: [[String]]+mismatch2_errors = couldn'tMatchErrors "Base \"s\"" "Base \"m\""++couldn'tMatchErrors :: String -> String -> [[String]]+couldn'tMatchErrors t1 t2 =+    [ [ "Couldn't match type ‘" ++ t1 ++ "’", "with ‘" ++ t2 ++ "’" ]+    , [ "Couldn't match type ‘" ++ t2 ++ "’", "with ‘" ++ t1 ++ "’" ]+    , [ "Couldn't match type: " ++ t1, "with: " ++ t2 ]+    , [ "Couldn't match type: " ++ t2, "with: " ++ t1 ]+    ]+++given1 :: ((One *: a) ~ (a *: One)) => Quantity Double a -> Quantity Double [u|kg|]+given1 = id++given1_errors :: [[String]]+given1_errors = [ [ "Could not deduce (a ~ Base \"kg\")"+                  , "from the context ((One *: a) ~ (a *: One))" ]+                , [ "Could not deduce (Base \"kg\" ~ a)"+                  , "from the context: (One *: a) ~ (a *: One)" ]+                , [ "Could not deduce: a ~ Base \"kg\""+                  , "from the context: (One *: a) ~ (a *: One)" ]+                , [ "Could not deduce: Base \"kg\" ~ a"+                  , "from the context: (One *: a) ~ (a *: One)" ]+                ]+++given2 :: ((One *: a) ~ (b *: One)) => Quantity Double a -> Quantity Double [u|kg|]+given2 = id++given2_errors :: [[String]]+given2_errors = [ [ "Could not deduce (a ~ Base \"kg\")"+                  , "from the context ((One *: a) ~ (b *: One))" ]+                , [ "Could not deduce (Base \"kg\" ~ a)"+                  , "from the context: (One *: a) ~ (b *: One)" ]+                , [ "Could not deduce: a ~ Base \"kg\""+                  , "from the context: (One *: a) ~ (b *: One)" ]+                , [ "Could not deduce: Base \"kg\" ~ a"+                  , "from the context: (One *: a) ~ (b *: One)" ]+                ]+++given3 :: ((a ^: 2) ~ (b ^: 3)) => Quantity Integer b -> Quantity Integer a+given3 _ = [u| 3 s |]++given3_errors :: [[String]]+given3_errors = [ [ "Could not deduce (a ~ Base \"s\")"+                  , "from the context ((a ^: 2) ~ (b ^: 3))" ]+                , [ "Could not deduce (Base \"s\" ~ a)"+                  , "from the context: (a ^: 2) ~ (b ^: 3)" ]+                , [ "Could not deduce: a ~ Base \"s\""+                  , "from the context: (a ^: 2) ~ (b ^: 3)" ]+                , [ "Could not deduce: Base \"s\" ~ a"+                  , "from the context: (a ^: 2) ~ (b ^: 3)" ]+                ]++op_a1 :: Quantity Double [u| m |]+op_a1 = (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Double (Base "m")))++op_a2 :: Quantity Double [u| m |]+op_a2 = (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Double (Base "m")))++op_a3 :: Quantity Double [u| m |]+op_a3 = (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Double (Base "m")))++op_b1 :: Quantity Int [u| m |]+op_b1 = (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Int (Base "m")))++op_b2 :: Quantity Int [u| m |]+op_b2 = (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Int (Base "m")))++op_b3 :: Quantity Int [u| m |]+op_b3 = (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Int (Base "m")))++op_c1 :: Quantity Integer [u| m |]+op_c1 = (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Integer (Base "m")))++op_c2 :: Quantity Integer [u| m |]+op_c2 = (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Integer (Base "m")))++op_c3 :: Quantity Integer [u| m |]+op_c3 = (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Integer (Base "m")))++op_d1 :: Quantity Rational [u| m |]+op_d1 = (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Rational (Base "m")))++op_d2 :: Quantity Rational [u| m |]+op_d2 = (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Rational (Base "m")))++op_d3 :: Quantity Rational [u| m |]+op_d3 = (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Rational (Base "m")))++opErrors :: String -> String -> String -> [[String]]+opErrors a b c = matchErrors a b c "One" ++ matchErrors a b c "(Base \"m\")"++matchErrors :: String -> String -> String -> String -> [[String]]+matchErrors a b c d =+#if __GLASGOW_HASKELL__ >= 900+  [ [ "Couldn't match type ‘" ++ a ++ "’ with ‘" ++ b ++ "’"+    , "Actual: Quantity " ++ c ++ " " ++ d+    ]+  , [ "Couldn't match type ‘" ++ a ++ "’ with ‘" ++ b ++ "’"+    , "Expected: Quantity " ++ c ++ " " ++ d+    ]+  , [ "Couldn't match type ‘" ++ b ++ "’ with ‘" ++ a ++ "’"+    , "Expected: Quantity " ++ c ++ " " ++ d+    ]+  , [ "Couldn't match type ‘" ++ b ++ "’ with ‘" ++ a ++ "’"+    , "Actual: Quantity " ++ c ++ " " ++ d+    ]+  , [ "Couldn't match type: " ++ a, "with: " ++ b+    , "Actual: Quantity " ++ c ++ " " ++ d+    ]+  ]+#else+  [ [ "Couldn't match type ‘" ++ b ++ "’ with ‘" ++ a ++ "’"+    ]+  ]+#endif+++exponentDoesn'tDistribute :: Quantity Double ([u| m |] ^: (x + y)) -> Quantity Double (([u| m |] ^: x) *: [u| m |] ^: y)+exponentDoesn'tDistribute x = x
+ test-suite-units/Tests.hs view
@@ -0,0 +1,362 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}++-- WARNING: It would be a lot of work to add type annotations to avoid type-default+-- warnings and what is more this leads to type checking failures;+--+-- {-# LANGUAGE PartialTypeSignatures #-}+--+--   , testGroup "read normalisation"+--     [ testCase "1 m/m"+--         $ (read "[u| 1 m/m |]" :: _ Integer _) @?= [u| 1 |]+--     , testCase "-0.3 m s^-1"+--         $ (read "[u| -0.3 m s^-1 |]" :: _ Double _) @?= [u| -0.3 m/s |]+--     , testCase "42 s m s"+--         $ (read "[u| 42 s m s |]" :: _ Integer _)  @?= [u| 42 m s^2 |]+--     ]+--+-- > cabal new-repl uom-plugin:units+-- solveSimpleWanteds: too many iterations (limit = 4)+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++module Main+    ( main++    -- * Exported to avoid -Wunused-top-binds.+    , attract+    , foo+    , foo'+    , angularSpeed+    , associativity+    , commutativity+    , unit+    , inverse+    , inverse2+    , f+    , g+    , givens+    , givens2+    , givens3+    , baz+    , baf+    , patternSplice+    , pow+    , dimensionless+    ) where++import Data.UnitsOfMeasure+import Data.UnitsOfMeasure.Convert+import Data.UnitsOfMeasure.Defs ()+import Data.UnitsOfMeasure.Show++import Control.Monad (unless)+import Control.Exception+import Data.List+import Data.Ratio ((%))++import Test.Tasty+import Test.Tasty.HUnit++import Defs ()+import ErrorTests+import Z (z)+import qualified Z (tests)++-- Some basic examples++myMass :: Quantity Double (Base "kg")+myMass = [u| 65 kg |]++gravityOnEarth :: Quantity Double [u| m/s^2 |]+gravityOnEarth = [u| 9.808 m/(s*s) |]++readMass :: Read a => String -> Quantity a (Base "kg")+readMass = fmap [u| kg |] read++forceOnGround :: Quantity Double [u| N |]+forceOnGround = gravityOnEarth *: myMass++inMetresPerSecond :: a -> Quantity a [u| m/s |]+inMetresPerSecond = [u| m/s |]++attract+    :: Fractional a+    => Quantity a [u| kg |]+    -> Quantity a [u| kg |]+    -> Quantity a [u| m |]+    -> Quantity a [u| N |]+attract+    (m1 :: Quantity a [u| kg |])+    (m2 :: Quantity a [u| kg |])+    (r :: Quantity a [u| m |])+    = _G *: m1 *: m2 /: (r *: r) :: Quantity a [u| N |]+  where+    _G = [u| 6.67384e-11 N*m^2/kg^2 |]++sum' :: [Quantity Double u] -> Quantity Double u+sum' = foldr (+:) zero++mean :: [Quantity Double u] -> Quantity Double u+mean xs = sum' xs /: mk (genericLength xs)++foo :: Num a => Quantity a u -> Quantity a v -> Quantity a (u *: v)+foo x y = x *: y +: y *: x++foo' :: Num a => Quantity a u -> Quantity a v -> Quantity a (u *: v)+foo' = foo++-- thanks to expipiplus1, https://github.com/adamgundry/uom-plugin/issues/14+angularSpeed :: Quantity Rational [u|rad/s|]+angularSpeed = z x+  where x :: Quantity Rational [u|s^-1|]+        x = undefined+++-- Check that the abelian group laws hold++associativity :: Quantity a (u *: (v *: w)) -> Quantity a ((u *: v) *: w)+associativity = id++commutativity :: Quantity a (u *: v) -> Quantity a (v *: u)+commutativity = id++unit :: Quantity a (u *: One) -> Quantity a u+unit = id++inverse :: Quantity a (u *: (One /: u)) -> Quantity a One+inverse = id++inverse2 :: proxy b -> Quantity a (Base b /: Base b) -> Quantity a One+inverse2 _ = id+++-- Gingerly now...++-- w^-2 ~ kg^-2  =>  w ~ kg+f :: (One /: (w ^: 2)) ~ (One /: [u| kg^2 |])  => Quantity a w -> Quantity a [u| kg |]+f = id++-- u ~ v * w, v^2 ~ v  =>  u ~ w+g :: (u ~ (v *: w), (v ^: 2) ~ v) => Quantity a u -> Quantity a w+g = id++-- a*a ~ 1  =>  a ~ 1+givens :: ((a *: a) ~ One) => Quantity Double a -> Quantity Double One+givens = id++-- a^2 ~ b^3, b^6 ~ 1 => a ~ 1+givens2 :: ((a ^: 2) ~ (b ^: 3), (b ^: 6) ~ One) => Quantity Double a -> Quantity Double One+givens2 = id++-- a^2 ~ b^3, b^37 ~ 1 => b ~ 1+givens3 :: ((a ^: 2) ~ (b ^: 3), (b ^: 37) ~ One) => Quantity Double b -> Quantity Double One+givens3 = id++-- in baf, c is uniquely determined to be a^3 (or b^2)+baz :: (a ~ (c ^: 3), b ~ (c ^: 2)) => Quantity Double a -> Quantity Double b -> Quantity Double c -> Int+baz _ _ _ = 3+baf :: ((a ^: 2) ~ (b ^: 3)) => Quantity Double a -> Quantity Double b -> Int+baf qa qb = baz qa qb undefined+++-- Miscellaneous bits and bobs++-- Pattern splices are supported, albeit with restricted types+patternSplice :: Quantity Integer [u| m |] -> Quantity Rational [u| kg/s |] -> Bool+patternSplice [u| 2 m |] [u| 0.0 kg / s |] = True+patternSplice [u| 1 m |] [u| 0.1 kg / s |] = True+patternSplice _          _                 = False++-- Andrew's awkward generalisation example is accepted only with a+-- type signature, even with NoMonoLocalBinds+tricky+    :: forall a u . Num a+    => Quantity a u+    -> (Quantity a (u *: Base "m"), Quantity a (u *: Base "kg"))+tricky x =+    let h :: Quantity a v -> Quantity a (u *: v)+        h = (x *:)+    in (h [u| 3 m |], h [u| 5 kg |])+++-- Test that basic constraints involving exponentiation work+pow :: Quantity a (u *: (v ^: i)) -> Quantity a ((v ^: i) *: u)+pow = id+++-- This declares a synonym for One+[u| dimensionless = 1 |]+dimensionless :: Quantity a [u|dimensionless|] -> Quantity a [u|1|]+dimensionless = id+++-- Runtime testsuite++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests = testGroup "uom-plugin"+  [ testGroup "Get the underlying value with unQuantity"+    [ testCase "unQuantity 3 m"                $ unQuantity [u| 3 m |]            @?= 3+    , testCase "unQuantity 3 s^2"              $ unQuantity [u| 3 s^2 |]          @?= 3+#if __GLASGOW_HASKELL__ > 802+    -- TODO: Find out why unQuantity (3 m s^-1) fails with ghc-8.0.2.+    -- solveSimpleWanteds: too many iterations (limit = 4)+    , testCase "unQuantity 3 m s^-1"           $ unQuantity [u| 3 m s^-1 |]       @?= 3+#endif+    , testCase "unQuantity 3.0 kg m^2 / m s^2" $ unQuantity [u| 3.0 kg m / s^2 |] @?= 3+    , testCase "unQuantity 1"                  $ unQuantity (mk 1)                @?= 1+    , testCase "unQuantity 1 (1/s)"            $ unQuantity [u| 1 (1/s) |]        @?= 1+    , testCase "unQuantity 1 1/s"              $ unQuantity [u| 1 1/s |]          @?= 1+    , testCase "unQuantity 1 s^-1"             $ unQuantity [u| 1 s^-1 |]         @?= 1+    , testCase "unQuantity 2 1 / kg s"         $ unQuantity [u| 2 1 / kg s |]     @?= 2+    , testCase "unQuantity (1 % 2) kg"         $ unQuantity [u| 1 % 2 kg |]       @?= 0.5+    ]+  , testGroup "Attach units by applying the quasiquoter without a numeric value"+    [ testCase "m 3"                           $ [u| m |] 3           @?= [u| 3 m |]+    , testCase "m <$> [3..5]"                  $ [u| m |] <$> [3..5]  @?= [[u| 3 m |],[u| 4 m |],[u| 5 m |]]+    , testCase "m/s 3"                         $ [u| m/s |] 3         @?= [u| 3 m/s |]+#if __GLASGOW_HASKELL__ > 802+    -- TODO: Find out why (m s^-1 3) fails with ghc-8.0.2.+    -- solveSimpleWanteds: too many iterations (limit = 4)+    , testCase "m s^-1 3"                      $ [u| m s^-1 |] 3      @?= [u| 3 m s^-1 |]+#endif+    , testCase "s^2 3"                         $ [u| s^2 |] 3         @?= [u| 3 s^2 |]+    , testCase "1 $ 3"                         $ [u|dimensionless|] 3 @?= [u| 3 |]+    , testCase "fmap [u| kg |] read $ \"3\""   $ readMass "3"         @?= [u| 3 kg |]+    , testCase "fmap [u| kg |] read $ \"3.0\"" $ readMass "3"         @?= [u| 3.0 kg |]+    ]+  , testGroup "Showing constants"+    [ testCase "show 3m"                 $ show [u| 3 m |]                @?= "[u| 3 m |]"+    , testCase "show 3m/s"               $ show [u| 3 m/s |]              @?= "[u| 3 m / s |]"+    , testCase "show 3.2 s^2"            $ show [u| 3.2 s^2 |]            @?= "[u| 3.2 s^2 |]"+    , testCase "show 3.0 kg m^2 / m s^2" $ show [u| 3.0 kg m^2 / m s^2 |] @?= "[u| 3.0 kg m / s^2 |]"+    , testCase "show 1"                  $ show (mk 1)                    @?= "[u| 1 |]"+    , testCase "show 1 s^-1"             $ show [u| 1 s^-1 |]             @?= "[u| 1 s^-1 |]"+    , testCase "show 2 1 / kg s"         $ show [u| 2 1 / kg s |]         @?= "[u| 2 kg^-1 s^-1 |]"+    , testCase "show (1 % 2) kg"         $ show [u| 1 % 2 kg |]           @?= "[u| 0.5 kg |]"+    ]+  , testGroup "Basic operations"+    [ testCase "2 + 2"                   $ [u| 2 s |] +: [u| 2 s |]        @?= [u| 4 s |]+    , testCase "in m/s"                  $ inMetresPerSecond 5             @?= [u| 5 m/s |]+    , testCase "mean"                    $ mean [ [u| 2 N |], [u| 4 N |] ] @?= [u| 3 N |]+    , testCase "tricky generalisation"   $ tricky [u| 2 s |]               @?= ([u| 6 m s |], [u| 10 kg s |])+    , testCase "polymorphic zero"        $ [u| 0 |] @?= [u| 0 m |]+    , testCase "polymorphic frac zero"   $ [u| 0.0 |] @?= [u| 0.0 N / m |]+    ]+  , testGroup "Literal 1 (*:) Quantity _ u"+    [ testCase "_ = Double"+        $ 1 *: ([u| 1 m |] :: (Quantity Double (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Int"+        $ 1 *: ([u| 1 m |] :: (Quantity Int (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Integer"+        $ 1 *: ([u| 1 m |] :: (Quantity Integer (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Rational, 1 *: [u| 1 m |]"+        $ 1 *: ([u| 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Rational, mk (1 % 1) *: [u| 1 m |]"+        $ mk (1 % 1) *: ([u| 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Rational, 1 *: [u| 1 % 1 m |]"+        $ 1 *: ([u| 1 % 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Rational, mk (1 % 1) *: [u| 1 % 1 m |]"+        $ mk (1 % 1) *: ([u| 1 % 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]+    ]+  , testGroup "(1 :: Quantity _ One) (*:) Quantity _ u"+    [ testCase "_ = Double"+        $ (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Double (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Int"+        $ (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Int (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Integer"+        $ (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Integer (Base "m"))) @?= [u| 1 m |]+    , testCase "_ = Int"+        $ (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]+    ]+  , testGroup "errors when a /= b, (1 :: Quantity a One) (*:) Quantity b u"+    [ testGroup "b = Double"+      [ testCase "a = Int" $ op_a1 `throws` opErrors "Double" "Int" "Int"+      , testCase "a = Integer" $ op_a2 `throws` opErrors "Double" "Integer" "Integer"+      , testCase "a = Rational" $ op_a3 `throws` opErrors "Double" "GHC.Real.Ratio Integer" "Rational"+      ]+    , testGroup "b = Int"+      [ testCase "a = Double" $ op_b1 `throws` opErrors "Int" "Double" "Double"+      , testCase "a = Integer" $ op_b2 `throws` opErrors "Int" "Integer" "Integer"+      , testCase "a = Rational" $ op_b3 `throws` opErrors "Int" "GHC.Real.Ratio Integer" "Rational"+      ]+    , testGroup "b = Integer"+      [ testCase "a = Double" $ op_c1 `throws` opErrors "Integer" "Double" "Double"+      , testCase "a = Int" $ op_c2 `throws` opErrors "Integer" "Int" "Int"+      , testCase "a = Rational" $ op_c3 `throws` opErrors "Integer" "GHC.Real.Ratio Integer" "Rational"+      ]+    , testGroup "b = Rational"+      [ testCase "a = Double" $ op_d1 `throws` opErrors "GHC.Real.Ratio Integer" "Double" "Double"+      , testCase "a = Int" $ op_d2 `throws` opErrors "GHC.Real.Ratio Integer" "Int" "Int"+      , testCase "a = Integer" $ op_d3 `throws` opErrors "GHC.Real.Ratio Integer" "Integer" "Integer"+      ]+    ]+  , testGroup "showQuantity"+    [ testCase "myMass"         $ showQuantity myMass         @?= "65.0 kg"+    , testCase "gravityOnEarth" $ showQuantity gravityOnEarth @?= "9.808 m / s^2"+    , testCase "forceOnGround"  $ showQuantity forceOnGround  @?= "637.52 kg m / s^2"+    ]+  , testGroup "convert"+    [ testCase "10m in ft"     $ convert [u| 10m |]   @?= [u| 32.8 ft |]+    , testCase "5 km^2 in m^2" $ convert [u| 5km^2 |] @?= [u| 5000000 m m |]+    , testCase "ratio"         $ show (ratio [u| ft |] [u| m |]) @?= "[u| 3.28 ft / m |]"+    , testCase "100l in m^3"   $ convert [u| 100l |]   @?= [u| 0.1 m^3 |]+    , testCase "1l/m in m^2"   $ convert [u| 1l/m |]   @?= [u| 0.001 m^2 |]+    , testCase "1l/m in m^2"   $ convert [u| 1l/m |]   @?= [u| 0.001 m^2 |]+    , testCase "5l in ft^3"    $ convert [u| 5l   |]   @?= [u| 0.17643776 ft^3 |]+    , testCase "2000000l^2 in ft^3 m^3" $ convert [u| 2000000l^2 |] @?= [u| 70.575104 ft^3 m^3 |]+    , testCase "42 rad/s in s^-1" $ convert [u| 42 rad/s |] @?= [u| 42 s^-1 |]+    , testCase "2.4 l/h in m" $ convert [u| 2.4 l/ha |] @?= [u| 2.4e-7 m |]+    , testCase "1 m^4 in l m" $ convert [u| 1 m^4 |] @?= [u| 1000 l m |]+    ]+  , Z.tests+  , testGroup "errors"+    [ testCase "s/m ~ m/s"            $ mismatch1 `throws` mismatch1_errors+    , testCase "m + s"                $ mismatch2 `throws` mismatch2_errors+    , testCase "a ~ a  =>  a ~ kg"    $ given1 undefined `throws` given1_errors+    , testCase "a ~ b  =>  a ~ kg"    $ given2 undefined `throws` given2_errors+    , testCase "a^2 ~ b^3  =>  a ~ s" $ given3 undefined `throws` given3_errors+    , testCase "a^(x + y) ~ a^x a^y"  $ exponentDoesn'tDistribute undefined `throws` matchErrors "Base \"m\" ^: (x + y)" "(Base \"m\" ^: x) *: (Base \"m\" ^: y)" "Double" "(MkUnit \"m\" ^: (x + y))"+    ]+  , testGroup "read . show"+    [ testCase "3 m"     $ read (show [u| 3 m     |]) @?= [u| 3 m     |]+    , testCase "1.2 m/s" $ read (show [u| 1.2 m/s |]) @?= [u| 1.2 m/s |]+    , testCase "0"       $ read (show [u| 1       |]) @?= [u| 1       |]+    ]+  , testGroup "read normalisation"+    [ testCase "1 m/m"       $ read "[u| 1 m/m |]"       @?= [u| 1 |]+    , testCase "-0.3 m s^-1" $ read "[u| -0.3 m s^-1 |]" @?= [u| -0.3 m/s |]+    , testCase "42 s m s"    $ read "[u| 42 s m s |]"    @?= [u| 42 m s^2 |]+    ]+  , testGroup "read equality (avoid false equivalences)"+    [ testCase "1 m/m^2 /= 1 m" $+        (read "[u| 1 m/m^2 |]" :: Quantity Double [u| m |]) `throws` noParse++    , testCase "1 m /= 1 m/m^2" $+        (read "[u| 1 m |]" :: Quantity Double [u| m/m^2 |]) `throws` noParse+    ]+  ]++-- | Assert that evaluation of the first argument (to WHNF) will throw+-- an exception whose string representation contains one of the given+-- lists of substrings.+throws :: a -> [[String]] -> Assertion+throws v xs =+    (evaluate v >> assertFailure "No exception!") `catch` \ (e :: SomeException) ->+        unless (any (all (`isInfixOf` show e)) xs) $+          assertFailure ("Expected:\n" ++ unlines (concat xs) ++ "\nbut got:\n" ++ show e)++noParse :: [[String]]+noParse = [["Prelude.read: no parse"]]
+ test-suite-units/Z.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++module Z (z, tests) where++import Test.Tasty+import Test.Tasty.HUnit++import Data.UnitsOfMeasure (Quantity, u)+import Data.UnitsOfMeasure.Convert (Convertible, convert)+import Data.UnitsOfMeasure.Defs ()+++-- Inferring this type used to lead to unit equations with occur-check+-- failures, because it involves things like Pack (Unpack u) ~ u+-- The type signature is intentionally left off here to check that the+-- compiler can infer it.+-- z :: forall a (u :: Unit) (v :: Unit). (Fractional a, Convertible u v)+--   => Quantity a u+--   -> Quantity a v+{-# ANN z "HLint: ignore Eta reduce" #-}+z q = convert q+++#if __GLASGOW_HASKELL__ >= 902+newtype A a = A a+newtype B a = B a++  -- See https://github.com/adamgundry/uom-plugin/pull/86.  This code works in GHC+-- 9.2 and later because they do not flatten, but is broken in 9.0 because of+-- flattening. For now we skip testing it in 9.0.  In principle we should be+-- able to fix it by having simplify-givens do substitution.+instance (Convertible u [u| m |], q ~ Quantity Double u) => Show (A q) where+    show (A x) = show y+        where+            y :: Quantity Double [u| m |]+            y = convert x++instance (q ~ Quantity Double [u| m |]) => Show (B q) where+    show (B x) = show y+        where+            y :: Quantity Double [u| m |]+            y = convert x++tests :: TestTree+tests = testGroup "show via convert"+    [ testCase "A 1.01km" $ show (A [u| 1.01 km |]) @?= "[u| 1010.0 m |]"+    , testCase "B 1010m" $ show (B [u| 1010.0 m |]) @?= "[u| 1010.0 m |]"+    ]+++#else+tests :: TestTree+tests = testGroup "show via convert" []+#endif
− tests/ErrorTests.hs
@@ -1,120 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE QuasiQuotes #-}--{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}--{-# OPTIONS_GHC -fdefer-type-errors #-}-{-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}--#if __GLASGOW_HASKELL__ > 710-{-# OPTIONS_GHC -fno-warn-deferred-type-errors #-}-#endif--module ErrorTests where--import Data.UnitsOfMeasure-import Data.UnitsOfMeasure.Defs--mismatch1 :: Quantity Double [u| s/m |]-mismatch1 = [u| 3 m/s |]--mismatch1_errors = [ [ "Couldn't match type ‘Base \"s\" /: Base \"m\"’"-                     , "with ‘Base \"m\" /: Base \"s\"’" ]-                   , [ "Couldn't match type ‘Base \"m\" /: Base \"s\"’"-                     , "with ‘Base \"s\" /: Base \"m\"’" ]-                   ]---mismatch2 = [u| 2 m |] +: ([u| 2 s |] :: Quantity Int [u| s |])--mismatch2_errors = [ [ "Couldn't match type ‘Base \"s\"’ with ‘Base \"m\"’" ]-                   , [ "Couldn't match type ‘Base \"m\"’ with ‘Base \"s\"’" ]-                   ]---given1 :: ((One *: a) ~ (a *: One)) => Quantity Double a -> Quantity Double [u|kg|]-given1 = id--given1_errors = [ [ "Could not deduce (a ~ Base \"kg\")"-                  , "from the context ((One *: a) ~ (a *: One))" ]-                , [ "Could not deduce: a ~ Base \"kg\""-                  , "from the context: (One *: a) ~ (a *: One)" ]-                , [ "Could not deduce: Base \"kg\" ~ a"-                  , "from the context: (One *: a) ~ (a *: One)" ]-                ]---given2 :: ((One *: a) ~ (b *: One)) => Quantity Double a -> Quantity Double [u|kg|]-given2 = id--given2_errors = [ [ "Could not deduce (a ~ Base \"kg\")"-                  , "from the context ((One *: a) ~ (b *: One))" ]-                , [ "Could not deduce: a ~ Base \"kg\""-                  , "from the context: (One *: a) ~ (b *: One)" ]-                , [ "Could not deduce: Base \"kg\" ~ a"-                  , "from the context: (One *: a) ~ (b *: One)" ]-                ]---given3 :: ((a ^: 2) ~ (b ^: 3)) => Quantity Integer b -> Quantity Integer a-given3 _ = [u| 3 s |]--given3_errors = [ [ "Could not deduce (a ~ Base \"s\")"-                  , "from the context ((a ^: 2) ~ (b ^: 3))" ]-                , [ "Could not deduce: a ~ Base \"s\""-                  , "from the context: (a ^: 2) ~ (b ^: 3)" ]-                , [ "Could not deduce: Base \"s\" ~ a"-                  , "from the context: (a ^: 2) ~ (b ^: 3)" ]-                ]--op_a1 :: Quantity Double [u| m |]-op_a1 = (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Double (Base "m")))--op_a2 :: Quantity Double [u| m |]-op_a2 = (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Double (Base "m")))--op_a3 :: Quantity Double [u| m |]-op_a3 = (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Double (Base "m")))--op_b1 :: Quantity Int [u| m |]-op_b1 = (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Int (Base "m")))--op_b2 :: Quantity Int [u| m |]-op_b2 = (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Int (Base "m")))--op_b3 :: Quantity Int [u| m |]-op_b3 = (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Int (Base "m")))--op_c1 :: Quantity Integer [u| m |]-op_c1 = (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Integer (Base "m")))--op_c2 :: Quantity Integer [u| m |]-op_c2 = (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Integer (Base "m")))--op_c3 :: Quantity Integer [u| m |]-op_c3 = (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Integer (Base "m")))--op_d1 :: Quantity Rational [u| m |]-op_d1 = (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Rational (Base "m")))--op_d2 :: Quantity Rational [u| m |]-op_d2 = (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Rational (Base "m")))--op_d3 :: Quantity Rational [u| m |]-op_d3 = (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Rational (Base "m")))--opErrors :: String -> String -> String -> [[String]]-opErrors a b c =-#if __GLASGOW_HASKELL__ > 710 -  [ [ "Couldn't match type ‘" ++ a ++ "’ with ‘" ++ b ++ "’"-    , "Expected type: Quantity " ++ c ++ " (Base \"m\")"-    ]-  ]-#else-  [ [ "Couldn't match type ‘" ++ b ++ "’ with ‘" ++ a ++ "’"-    ]-  ]-#endif
− tests/Tests.hs
@@ -1,278 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}--{-# OPTIONS_GHC -fplugin Data.UnitsOfMeasure.Plugin #-}--import Data.UnitsOfMeasure-import Data.UnitsOfMeasure.Convert-import Data.UnitsOfMeasure.Internal (fromRational')-import Data.UnitsOfMeasure.Defs ()-import Data.UnitsOfMeasure.Show--import Control.Monad (unless)-import Control.Exception-import Data.List-import Data.Ratio ((%))-import GHC.Real (Ratio(..))--import Test.Tasty-import Test.Tasty.HUnit--import ErrorTests----- Declarations-declareBaseUnit "byte"-declareDerivedUnit "bps" "byte / s"-declareConvertibleUnit "kilobyte" 1024 "byte"-declareConvertibleUnit "squiggle" 2 "m/s"----- Some basic examples--myMass :: Quantity Double (Base "kg")-myMass = [u| 65 kg |]--gravityOnEarth :: Quantity Double [u| m/s^2 |]-gravityOnEarth = [u| 9.808 m/(s*s) |]--forceOnGround :: Quantity Double [u| N |]-forceOnGround = gravityOnEarth *: myMass--inMetresPerSecond :: a -> Quantity a [u| m/s |]-inMetresPerSecond = [u| m/s |]--attract (m1 :: Quantity a [u| kg |]) (m2 :: Quantity a [u| kg |]) (r :: Quantity a [u| m |])-    = _G *: m1 *: m2 /: (r *: r) :: Quantity a [u| N |]-  where-    _G = [u| 6.67384e-11 N*m^2/kg^2 |]--sum' = foldr (+:) zero-mean xs = sum' xs /: mk (genericLength xs)--foo x y = x *: y +: y *: x--foo' :: Num a => Quantity a u -> Quantity a v -> Quantity a (u *: v)-foo' = foo---- thanks to expipiplus1, https://github.com/adamgundry/uom-plugin/issues/14-angularSpeed :: Quantity Rational [u|rad/s|]-angularSpeed = convert x-  where x :: Quantity Rational [u|s^-1|]-        x = undefined----- Check that the abelian group laws hold--associativity :: Quantity a (u *: (v *: w)) -> Quantity a ((u *: v) *: w)-associativity = id--commutativity :: Quantity a (u *: v) -> Quantity a (v *: u)-commutativity = id--unit :: Quantity a (u *: One) -> Quantity a u-unit = id--inverse :: Quantity a (u *: (One /: u)) -> Quantity a One-inverse = id--inverse2 :: proxy b -> Quantity a (Base b /: Base b) -> Quantity a One-inverse2 _ = id----- Gingerly now...---- w^-2 ~ kg^-2  =>  w ~ kg-f :: (One /: (w ^: 2)) ~ (One /: [u| kg^2 |])  => Quantity a w -> Quantity a [u| kg |]-f = id---- u ~ v * w, v^2 ~ v  =>  u ~ w-g :: (u ~ (v *: w), (v ^: 2) ~ v) => Quantity a u -> Quantity a w-g = id---- a*a ~ 1  =>  a ~ 1-givens :: ((a *: a) ~ One) => Quantity Double a -> Quantity Double One-givens = id---- a^2 ~ b^3, b^6 ~ 1 => a ~ 1-givens2 :: ((a ^: 2) ~ (b ^: 3), (b ^: 6) ~ One) => Quantity Double a -> Quantity Double One-givens2 = id---- a^2 ~ b^3, b^37 ~ 1 => b ~ 1-givens3 :: ((a ^: 2) ~ (b ^: 3), (b ^: 37) ~ One) => Quantity Double b -> Quantity Double One-givens3 = id---- in baf, c is uniquely determined to be a^3 (or b^2)-baz :: (a ~ (c ^: 3), b ~ (c ^: 2)) => Quantity Double a -> Quantity Double b -> Quantity Double c -> Int-baz _ _ _ = 3-baf :: ((a ^: 2) ~ (b ^: 3)) => Quantity Double a -> Quantity Double b -> Int-baf qa qb = baz qa qb undefined----- Miscellaneous bits and bobs---- Inferring this type used to lead to unit equations with occur-check--- failures, because it involves things like Pack (Unpack u) ~ u--- The type signature is intentionally left off here to check that the--- compiler can infer it.--- z :: forall a (u :: Unit) (v :: Unit). (Fractional a, Convertible u v)---   => Quantity a u---   -> Quantity a v-{-# ANN z "HLint: ignore Eta reduce" #-}-z q = convert q---- Pattern splices are supported, albeit with restricted types-patternSplice [u| 2 m |] [u| 0.0 kg / s |] = True-patternSplice [u| 1 m |] [u| 0.1 kg / s |] = True-patternSplice _          _                 = False---- Andrew's awkward generalisation example is accepted only with a--- type signature, even with NoMonoLocalBinds-tricky :: forall a u . Num a => Quantity a u -> (Quantity a (u *: Base "m"), Quantity a (u *: Base "kg"))-tricky x = let f :: Quantity a v -> Quantity a (u *: v)-               f = (x *:)-           in (f [u| 3 m |], f [u| 5 kg |])----- Test that basic constraints involving exponentiation work-pow :: Quantity a (u *: (v ^: i)) -> Quantity a ((v ^: i) *: u)-pow = id----- This declares a synonym for One-[u| dimensionless = 1 |]-dimensionless :: Quantity a [u|dimensionless|] -> Quantity a [u|1|]-dimensionless = id---- This declares a dimensionless unit that requires explicit conversion-[u| dime = 1 1 |]-dime :: Fractional a => Quantity a [u|dime|] -> Quantity a [u|1|]-dime = convert----- Runtime testsuite--main :: IO ()-main = defaultMain tests--tests :: TestTree-tests = testGroup "uom-plugin"-  [ testGroup "Showing constants"-    [ testCase "show 3m"                 $ show [u| 3 m |]                @?= "[u| 3 m |]"-    , testCase "show 3m/s"               $ show [u| 3 m/s |]              @?= "[u| 3 m / s |]"-    , testCase "show 3.2 s^2"            $ show [u| 3.2 s^2 |]            @?= "[u| 3.2 s^2 |]"-    , testCase "show 3.0 kg m^2 / m s^2" $ show [u| 3.0 kg m^2 / m s^2 |] @?= "[u| 3.0 kg m / s^2 |]"-    , testCase "show 1"                  $ show (mk 1)                    @?= "[u| 1 |]"-    , testCase "show 1 s^-1"             $ show [u| 1 s^-1 |]             @?= "[u| 1 s^-1 |]"-    , testCase "show 2 1 / kg s"         $ show [u| 2 1 / kg s |]         @?= "[u| 2 kg^-1 s^-1 |]"-    , testCase "show (1 % 2) kg"         $ show [u| 1 % 2 kg |]           @?= "[u| 0.5 kg |]"-    ]-  , testGroup "Basic operations"-    [ testCase "2 + 2"                   $ [u| 2 s |] +: [u| 2 s |]        @?= [u| 4 s |]-    , testCase "in m/s"                  $ inMetresPerSecond 5             @?= [u| 5 m/s |]-    , testCase "mean"                    $ mean [ [u| 2 N |], [u| 4 N |] ] @?= [u| 3 N |]-    , testCase "tricky generalisation"   $ tricky [u| 2 s |]               @?= ([u| 6 m s |], [u| 10 kg s |])-    , testCase "polymorphic zero"        $ [u| 0 |] @?= [u| 0 m |]-    , testCase "polymorphic frac zero"   $ [u| 0.0 |] @?= [u| 0.0 N / m |]-    ]-  , testGroup "Literal 1 (*:) Quantity _ u"-    [ testCase "_ = Double"-        $ 1 *: ([u| 1 m |] :: (Quantity Double (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Int"-        $ 1 *: ([u| 1 m |] :: (Quantity Int (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Integer"-        $ 1 *: ([u| 1 m |] :: (Quantity Integer (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Rational, 1 *: [u| 1 m |]"-        $ 1 *: ([u| 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Rational, mk (1 % 1) *: [u| 1 m |]"-        $ mk (1 % 1) *: ([u| 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Rational, 1 *: [u| 1 % 1 m |]"-        $ 1 *: ([u| 1 % 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Rational, mk (1 % 1) *: [u| 1 % 1 m |]"-        $ mk (1 % 1) *: ([u| 1 % 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]-    ]-  , testGroup "(1 :: Quantity _ One) (*:) Quantity _ u"-    [ testCase "_ = Double"-        $ (1 :: Quantity Double One) *: ([u| 1 m |] :: (Quantity Double (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Int"-        $ (1 :: Quantity Int One) *: ([u| 1 m |] :: (Quantity Int (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Integer"-        $ (1 :: Quantity Integer One) *: ([u| 1 m |] :: (Quantity Integer (Base "m"))) @?= [u| 1 m |]-    , testCase "_ = Int"-        $ (1 :: Quantity Rational One) *: ([u| 1 m |] :: (Quantity Rational (Base "m"))) @?= [u| 1 m |]-    ]-  , testGroup "errors when a /= b, (1 :: Quantity a One) (*:) Quantity b u"-    [ testGroup "b = Double"-      [ testCase "a = Int" $ op_a1 `throws` opErrors "Double" "Int" "Int"-      , testCase "a = Integer" $ op_a2 `throws` opErrors "Double" "Integer" "Integer"-      , testCase "a = Rational" $ op_a3 `throws` opErrors "Double" "GHC.Real.Ratio Integer" "Rational"-      ]-    , testGroup "b = Int"-      [ testCase "a = Double" $ op_b1 `throws` opErrors "Int" "Double" "Double"-      , testCase "a = Integer" $ op_b2 `throws` opErrors "Int" "Integer" "Integer"-      , testCase "a = Rational" $ op_b3 `throws` opErrors "Int" "GHC.Real.Ratio Integer" "Rational"-      ]-    , testGroup "b = Integer"-      [ testCase "a = Double" $ op_c1 `throws` opErrors "Integer" "Double" "Double"-      , testCase "a = Int" $ op_c2 `throws` opErrors "Integer" "Int" "Int"-      , testCase "a = Rational" $ op_c3 `throws` opErrors "Integer" "GHC.Real.Ratio Integer" "Rational"-      ]-    , testGroup "b = Rational"-      [ testCase "a = Double" $ op_d1 `throws` opErrors "GHC.Real.Ratio Integer" "Double" "Double"-      , testCase "a = Int" $ op_d2 `throws` opErrors "GHC.Real.Ratio Integer" "Int" "Int"-      , testCase "a = Integer" $ op_d3 `throws` opErrors "GHC.Real.Ratio Integer" "Integer" "Integer"-      ]-    ]-  , testGroup "showQuantity"-    [ testCase "myMass"         $ showQuantity myMass         @?= "65.0 kg"-    , testCase "gravityOnEarth" $ showQuantity gravityOnEarth @?= "9.808 m / s^2"-    , testCase "forceOnGround"  $ showQuantity forceOnGround  @?= "637.52 kg m / s^2"-    ]-  , testGroup "convert"-    [ testCase "10m in ft"     $ convert [u| 10m |]   @?= [u| 32.8 ft |]-    , testCase "5 km^2 in m^2" $ convert [u| 5km^2 |] @?= [u| 5000000 m m |]-    , testCase "ratio"         $ show (ratio [u| ft |] [u| m |]) @?= "[u| 3.28 ft / m |]"-    , testCase "100l in m^3"   $ convert [u| 100l |]   @?= [u| 0.1 m^3 |]-    , testCase "1l/m in m^2"   $ convert [u| 1l/m |]   @?= [u| 0.001 m^2 |]-    , testCase "1l/m in m^2"   $ convert [u| 1l/m |]   @?= [u| 0.001 m^2 |]-    , testCase "5l in ft^3"    $ convert [u| 5l   |]   @?= [u| 0.17643776 ft^3 |]-    , testCase "2000000l^2 in ft^3 m^3" $ convert [u| 2000000l^2 |] @?= [u| 70.575104 ft^3 m^3 |]-    , testCase "42 rad/s in s^-1" $ convert [u| 42 rad/s |] @?= [u| 42 s^-1 |]-    , testCase "2.4 l/h in m" $ convert [u| 2.4 l/ha |] @?= [u| 2.4e-7 m |]-    , testCase "1 m^4 in l m" $ convert [u| 1 m^4 |] @?= [u| 1000 l m |]-    ]-  , testGroup "errors"-    [ testCase "s/m ~ m/s"            $ mismatch1 `throws` mismatch1_errors-    , testCase "m + s"                $ mismatch2 `throws` mismatch2_errors-    , testCase "a ~ a  =>  a ~ kg"    $ given1 undefined `throws` given1_errors-    , testCase "a ~ b  =>  a ~ kg"    $ given2 undefined `throws` given2_errors-    , testCase "a^2 ~ b^3  =>  a ~ s" $ given3 undefined `throws` given3_errors-    ]-  , testGroup "read . show"-    [ testCase "3 m"     $ read (show [u| 3 m     |]) @?= [u| 3 m     |]-    , testCase "1.2 m/s" $ read (show [u| 1.2 m/s |]) @?= [u| 1.2 m/s |]-    , testCase "0"       $ read (show [u| 1       |]) @?= [u| 1       |]-    ]-  , testGroup "read normalisation"-    [ testCase "1 m/m"       $ read "[u| 1 m/m |]"       @?= [u| 1 |]-    , testCase "-0.3 m s^-1" $ read "[u| -0.3 m s^-1 |]" @?= [u| -0.3 m/s |]-    , testCase "42 s m s"    $ read "[u| 42 s m s |]"    @?= [u| 42 m s^2 |]-    ]-  ]----- | Assert that evaluation of the first argument (to WHNF) will throw--- an exception whose string representation contains one of the given--- lists of substrings.-throws :: a -> [[String]] -> Assertion-throws v xs =-    (evaluate v >> assertFailure "No exception!") `catch` \ (e :: SomeException) ->-        unless (any (all (`isInfixOf` show e)) xs) $ throw e
uom-plugin.cabal view
@@ -1,15 +1,10 @@--- This file has been generated from package.yaml by hpack version 0.28.2.------ see: https://github.com/sol/hpack------ hash: d110f2f74fb66155437b924f27283bd9b16991b4f2be45693731488d98f4938c+cabal-version: 1.12  name:           uom-plugin-version:        0.3.0.0-synopsis:       Units of measure as a GHC typechecker plugin-description:    The @uom-plugin@ library adds support for units of measure to GHC-                using the new experimental facility for typechecker plugins, which-                is available in GHC 7.10 and later.  See+version:        0.4.0.0+synopsis:       Units of measure as a GHC type-checker plugin+description:    The @uom-plugin@ library adds support for units of+                measure as a GHC type-checker plugin. See                 "Data.UnitsOfMeasure.Tutorial" for an introduction to the library. category:       Type System stability:      experimental@@ -17,16 +12,15 @@ bug-reports:    https://github.com/adamgundry/uom-plugin/issues author:         Adam Gundry <adam@well-typed.com> maintainer:     Adam Gundry <adam@well-typed.com>-copyright:      Copyright (c) 2014-2018, Adam Gundry+copyright:      Copyright (c) 2014-2022, Adam Gundry license:        BSD3 license-file:   LICENSE-tested-with:    GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2-+tested-with:    GHC == 9.0.2, GHC == 9.2.4, GHC == 9.4.2 build-type:     Simple-cabal-version:  >= 1.10 extra-source-files:-    changelog-    package.yaml+    CHANGELOG.md+    README.md+    LICENSE  source-repository head   type: git@@ -48,47 +42,83 @@       Data.UnitsOfMeasure.Plugin.NormalForm       Data.UnitsOfMeasure.Plugin.Unify       Data.UnitsOfMeasure.TH-      TcPluginExtras-      Paths_uom_plugin+      GhcApi+      GhcApi.Compare   hs-source-dirs:+      doc       src   ghc-options: -Wall -fno-warn-unticked-promoted-constructors   build-depends:-      base >=4.7 && <5-    , containers >=0.5 && <0.6+      base >=4.9.1.0 && <5+    , containers >=0.5 && <0.7     , deepseq >=1.3 && <1.5-    , ghc >=7.9 && <8.4-    , ghc-tcplugins-extra >=0.1 && <0.3-    , template-haskell >=2.9 && <2.13+    , ghc >=9.0.1 && <9.5+    , ghc-tcplugin-api >=0.8.3.0 && <0.9+    , template-haskell >=2.9 && <2.20     , units-parser >=0.1 && <0.2   default-language: Haskell2010 +test-suite doctest+  if impl(ghc >= 9.4)+      buildable: False+  type: exitcode-stdio-1.0+  main-is: DocTest.hs+  other-modules:+      Data.UnitsOfMeasure+      Data.UnitsOfMeasure.Convert+      Data.UnitsOfMeasure.Defs+      Data.UnitsOfMeasure.Internal+      Data.UnitsOfMeasure.Plugin+      Data.UnitsOfMeasure.Plugin.Convert+      Data.UnitsOfMeasure.Plugin.NormalForm+      Data.UnitsOfMeasure.Plugin.Unify+      Data.UnitsOfMeasure.Read+      Data.UnitsOfMeasure.Show+      Data.UnitsOfMeasure.Singleton+      Data.UnitsOfMeasure.TH+      GhcApi+      GhcApi.Compare+  hs-source-dirs:+      src+      test-suite-doctest+  ghc-options: -Wall -fno-warn-unticked-promoted-constructors -rtsopts -threaded -with-rtsopts=-N+  build-depends:+      QuickCheck+    , base >=4.9.1.0 && <5+    , containers >=0.5+    , deepseq >=1.3 && <1.5+    , doctest+    , ghc+    , ghc-tcplugin-api+    , template-haskell >=2.9+    , units-parser >=0.1+  default-language: Haskell2010+ test-suite hlint+  buildable: False   type: exitcode-stdio-1.0   main-is: HLint.hs-  other-modules:-      Paths_uom_plugin   hs-source-dirs:-      hlint-  ghc-options: -Wall -fno-warn-unticked-promoted-constructors -Wall -O0 -rtsopts -threaded -with-rtsopts=-N+      test-suite-hlint+  ghc-options: -Wall -fno-warn-unticked-promoted-constructors -rtsopts -threaded -with-rtsopts=-N   build-depends:-      base-    , hlint >=1.7 && <2.2+      base >=4.9.1.0 && <5+    , hlint >=2.0.11   default-language: Haskell2010  test-suite units   type: exitcode-stdio-1.0   main-is: Tests.hs   other-modules:+      Defs       ErrorTests-      Paths_uom_plugin+      Z   hs-source-dirs:-      tests-  other-extensions: TemplateHaskell-  ghc-options: -Wall -fno-warn-unticked-promoted-constructors -O0+      test-suite-units+  ghc-options: -Wall -fno-warn-unticked-promoted-constructors -rtsopts -threaded -with-rtsopts=-N   build-depends:-      base-    , tasty >=0.10 && <1.1-    , tasty-hunit >=0.9 && <0.10.1+      base >=4.9.1.0 && <5+    , tasty >=0.11.3+    , tasty-hunit >=0.9.2     , uom-plugin   default-language: Haskell2010