Start adding Numeric to the standard library. (#2950)

* Numeric implementation

* Dealing with all sorts of numeric literals.

* Fix DA.Generics

* Reduce code duplication with IF_NUMERIC

* Simplify Prelude with IF_NUMERIC

* Fix daml-lf validation for MUL_NUMERIC and DIV_NUMERIC
This commit is contained in:
associahedron 2019-09-25 10:56:33 +01:00 committed by GitHub
parent 5eb76eef91
commit 46051e8d11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 296 additions and 112 deletions

View File

@ -8,6 +8,7 @@ module DA.Daml.LF.Ast.Numeric
, numeric
, numericScale
, numericMaxScale
, numericFromRational
, numericFromDecimal
) where
@ -48,6 +49,10 @@ numeric s m
| m > numericMaxMantissa = error "numeric error: mantissa too large"
| otherwise = Numeric $ Decimal (fromIntegral s) m
-- | Convert a rational number into a Numeric.
numericFromRational :: Rational -> Numeric
numericFromRational r = Numeric (fromRational r)
-- | Upper bound for numeric scale (inclusive).
numericMaxScale :: Natural
numericMaxScale = 37

View File

@ -95,7 +95,7 @@ import Control.Monad.Reader
import Control.Monad.State.Strict
import DA.Daml.LF.Ast as LF
import DA.Daml.LF.Ast.Type as LF
import DA.Daml.LF.Ast.Numeric (numericFromDecimal)
import DA.Daml.LF.Ast.Numeric
import Data.Data hiding (TyCon)
import Data.Foldable (foldlM)
import Data.Int
@ -211,8 +211,9 @@ convertInt64 x
| otherwise =
unsupported "Int literal out of bounds" (negate x)
convertRational :: Env -> Integer -> Integer -> ConvertM LF.Expr
convertRational env num denom
-- | Convert a rational number into a (legacy) Decimal literal.
convertRationalDecimal :: Env -> Integer -> Integer -> ConvertM LF.Expr
convertRationalDecimal env num denom
=
-- the denominator needs to be a divisor of 10^10.
-- num % denom * 10^10 needs to fit within a 128bit signed number.
@ -235,6 +236,67 @@ convertRational env num denom
upperBound128Bit = 10 ^ (38 :: Integer)
maxPrecision = 10 :: Integer
-- | Convert a rational number into a fixed scale Numeric literal. We check
-- that the scale is in bounds, and the number can be represented without
-- overflow or loss of precision.
convertRationalNumericMono :: Env -> Integer -> Integer -> Integer -> ConvertM LF.Expr
convertRationalNumericMono env scale num denom =
if | scale < 0 || scale > 37 ->
unsupported
("Tried to construct value of type Numeric " ++ show scale ++ ", but scale is out of bounds. Scale must be between 0 through 37, not " ++ show scale ++ ".")
scale
| abs (r * 10 ^ scale) >= upperBound128Bit ->
unsupported
("Rational is out of bounds: " ++
show ((fromInteger num / fromInteger denom) :: Double) ++
". The range of values representable by the Numeric " ++ show scale ++ " type is -10^" ++ show (38-scale) ++ " + 1 through 10^" ++ show (38-scale) ++ " - 1.")
(num, denom)
| (num * 10^scale) `mod` denom /= 0 ->
unsupported
("Rational is out of bounds: it cannot be represented without loss of precision. " ++
show ((fromInteger num / fromInteger denom) :: Double) ++
". Maximum precision for the Numeric " ++ show scale ++ " type is 10^-" ++ show scale ++ ".")
(num, denom)
| otherwise ->
pure $ EBuiltin $ BENumeric $
numeric (fromIntegral scale)
((num * 10^scale) `div` denom)
where
r = num % denom
upperBound128Bit = 10 ^ (38 :: Integer)
-- | Convert a rational number into a variable scale Numeric literal. We check that
-- the number can be represented at *some* Numeric scale without overflow or
-- loss of precision, and then we round and cast (with a multi-scale MUL_NUMERIC)
-- to the required scale. This means, for example, that a polymorphic literal like
-- "3.141592... : Numeric n" will be correctly rounded for all scales.
convertRationalNumericPoly :: Env -> LF.Type -> Integer -> Integer -> ConvertM LF.Expr
convertRationalNumericPoly env outputScale num denom =
if | max (abs num) (abs denom) >= upperBound128Bit
|| inputScale < 0 || inputScale > 37 ->
unsupported
("Numeric is out of bounds: " ++
show ((fromInteger num / fromInteger denom) :: Double) ++
". Numeric can only represent 38 digits of precision.")
(num, denom)
| otherwise ->
pure $
EBuiltin BEMulNumeric
`ETyApp` TNat inputScale
`ETyApp` TNat 0
`ETyApp` outputScale
`ETmApp` EBuiltin (BENumeric $ inputNumeric)
`ETmApp` EBuiltin (BENumeric $ numeric 0 1)
where
inputNumeric = numericFromRational (num % denom)
inputScale = numericScale inputNumeric
upperBound128Bit = 10 ^ (38 :: Integer)
convertModule :: LF.Version -> MS.Map UnitId T.Text -> NormalizedFilePath -> CoreModule -> Either FileDiagnostic LF.Module
convertModule lfVersion pkgMap file x = runConvertM (ConversionEnv file Nothing) $ do
definitions <- concatMapM (convertBind env) binds
@ -624,7 +686,15 @@ convertExpr env0 e = do
withTmArg env (varV2, record') args $ \x2 args ->
pure (ERecUpd (fromTCon record') (mkField $ fsToText name) x2 x1, args)
go env (VarIs "fromRational") (LExpr (VarIs ":%" `App` tyInteger `App` Lit (LitNumber _ top _) `App` Lit (LitNumber _ bot _)) : args)
= fmap (, args) $ convertRational env top bot
= fmap (, args) $ convertRationalDecimal env top bot
go env (VarIs "fromRational") (LType (isNumLitTy -> Just n) : LExpr (VarIs ":%" `App` tyInteger `App` Lit (LitNumber _ top _) `App` Lit (LitNumber _ bot _)) : args)
= fmap (, args) $ convertRationalNumericMono env n top bot
go env (VarIs "fromRational") (LType scaleTyCoRep : LExpr (VarIs ":%" `App` tyInteger `App` Lit (LitNumber _ top _) `App` Lit (LitNumber _ bot _)) : args)
= do
scaleType <- convertType env scaleTyCoRep
fmap (, args) $ convertRationalNumericPoly env scaleType top bot
go env (VarIs "negate") (tyInt : LExpr (VarIs "$fAdditiveInt") : LExpr (untick -> VarIs "fromInteger" `App` Lit (LitNumber _ x _)) : args)
= fmap (, args) $ convertInt64 (negate x)
go env (VarIs "fromInteger") (LExpr (Lit (LitNumber _ x _)) : args)
@ -1155,6 +1225,7 @@ convertTyCon env t
| Just m <- nameModule_maybe (getName t), m == gHC_TYPES =
case getOccText t of
"Text" -> pure TText
"Numeric" -> pure (TBuiltin BTNumeric)
"Decimal" ->
if envLfVersion env `supports` featureNumeric
then pure (TNumeric (TNat 10))
@ -1211,6 +1282,8 @@ convertType env t | Just t' <- getTyVar_maybe t
= TVar . fst <$> convTypeVar t'
convertType env t | Just s <- isStrLitTy t
= pure TUnit
convertType env t | Just n <- isNumLitTy t, n >= 0
= pure (TNat (fromIntegral n))
convertType env t | Just (a,b) <- splitAppTy_maybe t
= TApp <$> convertType env a <*> convertType env b
convertType env x
@ -1224,6 +1297,9 @@ convertKind x@(TypeCon t ts)
| t == runtimeRepTyCon, null ts = pure KStar
-- TODO (drsk): We want to check that the 'Meta' constructor really comes from GHC.Generics.
| getOccFS t == "Meta", null ts = pure KStar
| Just m <- nameModule_maybe (getName t)
, GHC.moduleName m == mkModuleName "GHC.Types"
, getOccFS t == "Nat", null ts = pure KNat
| t == funTyCon, [_,_,t1,t2] <- ts = KArrow <$> convertKind t1 <*> convertKind t2
convertKind (TyVarTy x) = convertKind $ tyVarKind x
convertKind x = unhandled "Kind" x

View File

@ -188,6 +188,37 @@ convertPrim _ "BEToText" (TNumeric10 :-> TText) =
convertPrim _ "BEDecimalFromText" (TText :-> TOptional TNumeric10) =
ETyApp (EBuiltin BENumericFromText) (TNat 10)
-- Numeric primitives. These are polymorphic in the scale.
convertPrim _ "BEAddNumeric" (TNumeric n1 :-> TNumeric n2 :-> TNumeric n3) | n1 == n2, n1 == n3 =
ETyApp (EBuiltin BEAddNumeric) n1
convertPrim _ "BESubNumeric" (TNumeric n1 :-> TNumeric n2 :-> TNumeric n3) | n1 == n2, n1 == n3 =
ETyApp (EBuiltin BESubNumeric) n1
convertPrim _ "BEMulNumeric" (TNumeric n1 :-> TNumeric n2 :-> TNumeric n3) =
EBuiltin BEMulNumeric `ETyApp` n1 `ETyApp` n2 `ETyApp` n3
convertPrim _ "BEDivNumeric" (TNumeric n1 :-> TNumeric n2 :-> TNumeric n3) =
EBuiltin BEDivNumeric `ETyApp` n1 `ETyApp` n2 `ETyApp` n3
convertPrim _ "BERoundNumeric" (TInt64 :-> TNumeric n1 :-> TNumeric n2) | n1 == n2 =
ETyApp (EBuiltin BERoundNumeric) n1
convertPrim _ "BEEqualNumeric" (TNumeric n1 :-> TNumeric n2 :-> TBool) | n1 == n2 =
ETyApp (EBuiltin BEEqualNumeric) n1
convertPrim _ "BELessNumeric" (TNumeric n1 :-> TNumeric n2 :-> TBool) | n1 == n2 =
ETyApp (EBuiltin BELessNumeric) n1
convertPrim _ "BELessEqNumeric" (TNumeric n1 :-> TNumeric n2 :-> TBool) | n1 == n2 =
ETyApp (EBuiltin BELessEqNumeric) n1
convertPrim _ "BEGreaterEqNumeric" (TNumeric n1 :-> TNumeric n2 :-> TBool) | n1 == n2 =
ETyApp (EBuiltin BEGreaterEqNumeric) n1
convertPrim _ "BEGreaterNumeric" (TNumeric n1 :-> TNumeric n2 :-> TBool) | n1 == n2 =
ETyApp (EBuiltin BEGreaterNumeric) n1
convertPrim _ "BEInt64ToNumeric" (TInt64 :-> TNumeric n) =
ETyApp (EBuiltin BEInt64ToNumeric) n
convertPrim _ "BENumericToInt64" (TNumeric n :-> TInt64) =
ETyApp (EBuiltin BENumericToInt64) n
convertPrim _ "BEToTextNumeric" (TNumeric n :-> TText) =
ETyApp (EBuiltin BEToTextNumeric) n
convertPrim _ "BENumericFromText" (TText :-> TOptional (TNumeric n)) =
ETyApp (EBuiltin BENumericFromText) n
convertPrim _ x ty = error $ "Unknown primitive " ++ show x ++ " at type " ++ renderPretty ty
-- | Some builtins are only supported in specific versions of DAML-LF.

View File

@ -6,7 +6,7 @@
{-# LANGUAGE DamlSyntax #-} -- [DA]
-- [DA] {-# LANGUAGE CPP #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE NoImplicitPrelude, MagicHash, StandaloneDeriving, BangPatterns,
KindSignatures, DataKinds, ConstraintKinds,
@ -195,8 +195,13 @@ instance Eq Bool where
instance Eq Int where
(==) = primitive @"BEEqual"
#ifdef DAML_NUMERIC
instance Eq (Numeric n) where
(==) = primitive @"BEEqualNumeric"
#else
instance Eq Decimal where
(==) = primitive @"BEEqual"
#endif
instance Eq Text where
(==) = primitive @"BEEqual"
@ -315,11 +320,19 @@ instance Ord Int where
(>=) = primitive @"BEGreaterEq"
(>) = primitive @"BEGreater"
#ifdef DAML_NUMERIC
instance Ord (Numeric n) where
(<) = primitive @"BELessNumeric"
(<=) = primitive @"BELessEqNumeric"
(>=) = primitive @"BEGreaterEqNumeric"
(>) = primitive @"BEGreaterNumeric"
#else
instance Ord Decimal where
(<) = primitive @"BELess"
(<=) = primitive @"BELessEq"
(>=) = primitive @"BEGreaterEq"
(>) = primitive @"BEGreater"
#endif
instance Ord Text where
(<) = primitive @"BELess"

View File

@ -2,6 +2,7 @@
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE CPP #-}
daml 1.2
-- | MOVE Prelude
@ -109,14 +110,20 @@ instance Signed Int where
signum x = if x == 0 then 0 else if x <= 0 then (-1) else 1
abs x = if x <= 0 then negate x else x
instance Additive Decimal where
(+) = primitive @"BEAddDecimal"
(-) = primitive @"BESubDecimal"
#ifdef DAML_NUMERIC
#define IF_NUMERIC(a,b) a
#else
#define IF_NUMERIC(a,b) b
#endif
instance Additive IF_NUMERIC((Numeric n), Decimal) where
(+) = primitive @IF_NUMERIC("BEAddNumeric", "BEAddDecimal")
(-) = primitive @IF_NUMERIC("BESubNumeric", "BESubDecimal")
negate x = 0.0 - x
aunit = 0.0
instance Multiplicative Decimal where
(*) = primitive @"BEMulDecimal"
instance Multiplicative IF_NUMERIC((Numeric n), Decimal) where
(*) = primitive @IF_NUMERIC("BEMulNumeric", "BEMulDecimal")
munit = 1.0
x ^ n
| n == 0 = 1.0
@ -125,10 +132,9 @@ instance Multiplicative Decimal where
| n % 2 == 0 = (x ^ (n / 2)) ^ 2
| otherwise = x * x ^ (n - 1)
instance Number Decimal where
-- no methods
instance Number IF_NUMERIC((Numeric n), Decimal)
instance Signed Decimal where
instance Signed IF_NUMERIC((Numeric n), Decimal) where
signum x = if x == 0.0 then 0.0 else if x <= 0.0 then (-1.0) else 1.0
abs x = if x <= 0.0 then negate x else x
@ -143,8 +149,8 @@ class (Multiplicative a) => Divisible a where
instance Divisible Int where
(/) = primitive @"BEDivInt64"
instance Divisible Decimal where
(/) = primitive @"BEDivDecimal"
instance Divisible IF_NUMERIC((Numeric n), Decimal) where
(/) = primitive @IF_NUMERIC("BEDivNumeric", "BEDivDecimal")
-- | Use the `Fractional` class for types that can be divided
-- and where the reciprocal is well defined. Instances
@ -159,7 +165,7 @@ class (Divisible a) => Fractional a where
recip : a -> a
recip x = munit / x
instance Fractional Decimal
instance Fractional IF_NUMERIC((Numeric n), Decimal)
-- | `x % y` calculates the remainder of `x` by `y`
(%) : Int -> Int -> Int

View File

@ -2,6 +2,7 @@
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE CPP #-}
-- GHC desugars Decimal literals to this type
daml 1.2
@ -15,6 +16,12 @@ data Ratio a = !a :% !a
type Rational = Ratio Integer
#ifdef DAML_NUMERIC
fromRational : Rational -> Numeric n
#else
fromRational : Rational -> Decimal
#endif
fromRational = magic @"fromRational"
{-# NOINLINE fromRational #-}

View File

@ -3,6 +3,7 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE CPP #-}
daml 1.2
-- | MOVE Prelude
@ -111,8 +112,14 @@ deriving instance (Show a, Show b) => Show (Either a b)
instance Show Int where
show = primitive @"BEToText"
#ifdef DAML_NUMERIC
instance Show (Numeric n) where
show = primitive @"BEToTextNumeric"
#else
instance Show Decimal where
show = primitive @"BEToText"
#endif
instance Show Text where
show x = "\"" ++ x ++ "\""

View File

@ -23,6 +23,11 @@ module GHC.Types (
Opaque,
ifThenElse,
primitive, magic,
#ifdef DAML_NUMERIC
Nat, Numeric,
#endif
) where
import GHC.Prim
@ -148,9 +153,26 @@ data Text =
-- | HIDE
Text Opaque
#ifdef DAML_NUMERIC
-- | (Kind) This is the kind of type-level naturals.
data Nat
-- | A type for fixed-point decimal numbers, with the scale
-- being passed as part of the type.
data Numeric (n:Nat) =
-- | HIDE
Numeric Opaque
type Decimal = Numeric 10
#else
-- | A type for fixed-point decimals: numbers of the form
-- `x / 10e10` where `x` is an integer with `|x| < 10e38`.
-- For example, `1.25`.
data Decimal =
-- | HIDE
Decimal Opaque
#endif

View File

@ -52,7 +52,7 @@ module DA.Generics (
) where
-- We use some base types
import GHC.Types
import GHC.Types hiding (Nat)
-- Needed for instances
import GHC.Classes ( Eq(..), Ord(..) )

View File

@ -2,6 +2,7 @@
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE CPP #-}
daml 1.2
-- | MOVE Prelude Our Prelude, extending WiredIn with things that don't need special treatment.
@ -23,11 +24,15 @@ import GHC.Real as GHC (fromRational)
import GHC.Show as GHC
import DA.Types as GHC (Either(..))
import GHC.Tuple()
import GHC.Types as GHC (Bool (..), Int, Ordering (..), Text, Decimal, ifThenElse, primitive, magic)
import GHC.Types as GHC (Bool (..), Int, Ordering (..), Text, Decimal, ifThenElse, primitive, magic
#ifdef DAML_NUMERIC
, Numeric
#endif
)
infixr 0 $
-- | Take a function from `a` to `b` and a value of type `a`, and apply the
-- function to the value of type `a`, returning a value of type `b`.
-- function to the value of type `a`, returning a value of type `b`.
-- This function has a very low precedence, which is why you might want to use
-- it instead of regular function application.
($) : (a -> b) -> a -> b
@ -85,7 +90,7 @@ ap : Applicative f => f (a -> b) -> f a -> f b
ap = (<*>)
-- This is a poor doc string but I don't know what the correct improvement would be.
-- | Inject a value into the monadic type. For example, for `Update` and a
-- | Inject a value into the monadic type. For example, for `Update` and a
-- value of type `a`, `return` would give you an `Update a`.
return : Applicative m => a -> m a
return = pure
@ -136,7 +141,7 @@ guard : ActionFail m => Bool -> m ()
guard False = fail "guard is false"
guard True = pure ()
-- | This function is a left fold, which you can use to inspect/analyse/consume lists.
-- | This function is a left fold, which you can use to inspect/analyse/consume lists.
-- `foldl f i xs` performs a left fold over the list `xs` using
-- the function `f`, using the starting value `i`.
--
@ -145,7 +150,7 @@ guard True = pure ()
-- ```
-- >>> foldl (+) 0 [1,2,3]
-- 6
--
--
-- >>> foldl (^) 10 [2,3]
-- 1000000
-- ```
@ -154,8 +159,8 @@ guard True = pure ()
foldl : (b -> a -> b) -> b -> [a] -> b
foldl = primitive @"BEFoldl"
-- | `find p xs` finds the first element of the list `xs` where the
-- predicate `p` is true. There might not be such an element, which
-- | `find p xs` finds the first element of the list `xs` where the
-- predicate `p` is true. There might not be such an element, which
-- is why this function returns an `Optional a`.
find : (a -> Bool) -> [a] -> Optional a
find p [] = None
@ -507,43 +512,55 @@ fst (x, _) = x
snd : (a, b) -> b
snd (_, x) = x
#ifdef DAML_NUMERIC
#define IF_NUMERIC(a,b) a
#else
#define IF_NUMERIC(a,b) b
#endif
-- | `truncate x` rounds `x` toward zero.
truncate : Decimal -> Int
truncate = primitive @"BEDecimalToInt64"
truncate : IF_NUMERIC(Numeric n, Decimal) -> Int
truncate = primitive @IF_NUMERIC("BENumericToInt64", "BEDecimalToInt64")
#ifdef DAML_NUMERIC
-- | Convert an `Int` to a `Numeric`.
intToNumeric : Int -> Numeric n
intToNumeric = primitive @"BEInt64ToNumeric"
#endif
-- | Convert an `Int` to a `Decimal`.
intToDecimal : Int -> Decimal
intToDecimal = primitive @"BEInt64ToDecimal"
intToDecimal = IF_NUMERIC(intToNumeric, primitive @"BEInt64ToDecimal")
-- | Bankers' Rounding: `roundBankers dp x` rounds `x` to `dp` decimal places, where a `.5` is rounded to the nearest even digit.
roundBankers : Int -> Decimal -> Decimal
roundBankers = primitive @"BERoundDecimal"
roundBankers : Int -> IF_NUMERIC(Numeric n -> Numeric n, Decimal -> Decimal)
roundBankers = primitive @IF_NUMERIC("BERoundNumeric", "BERoundDecimal")
-- | Commercial Rounding: `roundCommercial dp x` rounds `x` to `dp` decimal places, where a `.5` is rounded away from zero.
roundCommercial : Int -> Decimal -> Decimal
roundCommercial : Int -> IF_NUMERIC(Numeric n -> Numeric n, Decimal -> Decimal)
roundCommercial d x =
let t = intToDecimal (10 ^ d)
let t = IF_NUMERIC(intToNumeric, intToDecimal) (10 ^ d)
v = round (x * t)
w = intToDecimal v
w = IF_NUMERIC(intToNumeric, intToDecimal) v
in w / t
-- | Round a `Decimal` to the nearest integer, where a `.5` is rounded away from zero.
round : Decimal -> Int
round : IF_NUMERIC(Numeric n, Decimal) -> Int
round x = if x > 0.0
then truncate (x + 0.5)
else truncate (x - 0.5)
-- | Round a `Decimal` down to the nearest integer.
floor : Decimal -> Int
floor : IF_NUMERIC(Numeric n, Decimal) -> Int
floor x =
let i = truncate x
in if intToDecimal i <= x then i else i - 1
in if IF_NUMERIC(intToNumeric, intToDecimal) i <= x then i else i - 1
-- | Round a `Decimal` up to the nearest integer.
ceiling : Decimal -> Int
ceiling : IF_NUMERIC(Numeric n, Decimal) -> Int
ceiling x =
let i = truncate x
in if intToDecimal i < x then i + 1 else i
in if IF_NUMERIC(intToNumeric, intToDecimal) i < x then i + 1 else i
-- | Is the list empty? `null xs` is true if `xs` is the empty list.
null : [a] -> Bool

View File

@ -22,7 +22,7 @@ sumMono z [] = z
test = scenario do
sumInferred 0 [1, 2, 3] === 6
sumInferred 0.0 [1.0, 2.0, 3.0] === 6.0
sumInferred 0.0 [1.0, 2.0, 3.0] === (6.0 : Decimal)
sumPoly 0 [1, 2, 3] === 6
sumPoly 0.0 [1.0, 2.0, 3.0] === 6.0
sumPoly 0.0 [1.0, 2.0, 3.0] === (6.0 : Decimal)
sumMono 0 [1, 2, 3] === 6

View File

@ -197,7 +197,7 @@ testIsInfixOf = scenario do
testMapAccumL = scenario do
(6, [0,1,3]) === mapAccumL (\a x -> (a+x, a)) 0 [1, 2, 3]
(7.1, [1,3,7]) === mapAccumL (\a x -> (a + x, floor (a + x))) 0.0 [1.2, 2.5, 3.4]
(7.1, [1,3,7]) === mapAccumL (\a x -> (a + x, floor (a + x))) 0.0 ([1.2, 2.5, 3.4] : [Decimal])
(42, []) === mapAccumL (\a x -> (a*x, a)) 42 []
testIntersperse = scenario do
@ -295,12 +295,12 @@ testInit = scenario do
submitMustFail p $ pure $ init []
testFoldl1 = scenario do
1.25 === foldl1 (/) [10.0, 2.0, 4.0]
1.25 === foldl1 (/) ([10.0, 2.0, 4.0] : [Decimal])
"abc" === foldl1 (<>) ["a", "b", "c"]
-4 === foldl1 (-) [1, 2, 3]
testFoldr1 = scenario do
3.0 === foldr1 (/) [9.0, 150.0, 50.0]
3.0 === foldr1 (/) ([9.0, 150.0, 50.0] : [Decimal])
"abc" === foldr1 (<>) ["a", "b", "c"]
2 === foldr1 (-) [1, 2, 3]
@ -334,14 +334,14 @@ testFindIndex = scenario do
testSum = scenario do
assert $ sum [1, 2, 3] == 6
assert $ sum [] == 0
assert $ sum [] == 0.0
assert $ sum [40.0, 2.0] == 42.0
assert $ sum [] == (0.0 : Decimal)
assert $ sum [40.0, 2.0] == (42.0 : Decimal)
testProduct = scenario do
assert $ product [1, 2, 3] == 6
assert $ product [] == 1
assert $ product [] == 1.0
assert $ product [21.0, 2.0] == 42.0
assert $ product [] == (1.0 : Decimal)
assert $ product [21.0, 2.0] == (42.0 : Decimal)
testDelete = scenario do
delete "a" ["b","a","n","a","n","a"] === ["b","n","a","n","a"]

View File

@ -8,5 +8,5 @@ main = scenario do
alice <- getParty "alice"
submit alice do
assert $ [1] == [1]
assert $ [1.0] /= [2.0]
assert $ [1.0] /= ([2.0] : [Decimal])
assert $ [""] /= []

View File

@ -14,7 +14,7 @@ testEmpty = scenario do
testSize = scenario do
0 === size (fromList ([] : [(Int, Decimal)]))
3 === size (fromList [(1, 2.0), (2, 9.0), (3, 2.2)])
3 === size (fromList ([(1, 2.0), (2, 9.0), (3, 2.2)] : [(Int, Decimal)]))
testToList = scenario do
[(1, "c"), (2, "a"), (5, "b")] === toList (fromList [(2, "a"), (5, "b"), (1, "c")])
@ -29,13 +29,13 @@ testFromListWith = scenario do
fromListWith (<>) [] === (M.empty : Map Int Text)
testMember = scenario do
False === member "a" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
True === member "" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
False === member "a" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
True === member "" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
False === member 2 (fromList [])
testLookup = scenario do
None === M.lookup "a" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
Some 1.0 === M.lookup "" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
None === M.lookup "a" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
Some 1.0 === M.lookup "" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
None === M.lookup 2 (fromList ([] : [(Int, Text)]))
testNull = scenario do

View File

@ -48,7 +48,7 @@ testOptionalToList = scenario do
testFromOptional = scenario do
1 === fromOptional 0 (Some 1)
2.3 === fromOptional 2.3 None
(2.3 : Decimal) === fromOptional 2.3 None
testIsSome = scenario do
True === isSome (Some "a")

View File

@ -30,15 +30,15 @@ testRemainder = scenario do
testId = scenario do
1 === identity 1
"abc" === identity "abc"
[1.0, 2.0, 3.0] === identity [1.0, 2.0, 3.0]
[1.0, 2.0, 3.0] === identity ([1.0, 2.0, 3.0] : [Decimal])
testFoldl = scenario do
2.5 === foldl (/) 150.0 [5.0, 4.0, 3.0]
2.5 === foldl (/) 150.0 ([5.0, 4.0, 3.0] : [Decimal])
"abc" === foldl (<>) "" ["a", "b", "c"]
-6 === foldl (-) 0 [1, 2, 3]
testFoldr = scenario do
6.0 === foldr (/) 3.0 [36.0, 300.0, 150.0]
6.0 === foldr (/) 3.0 ([36.0, 300.0, 150.0] : [Decimal])
"abc" === foldr (<>) "" ["a", "b", "c"]
[1, 2, 3] === foldr (::) [] [1, 2, 3]
2 === foldr (-) 0 [1, 2, 3]
@ -86,12 +86,12 @@ testAnd = scenario do
True === and [True, True]
testElem = scenario do
True === elem 1.0 [1.0, 2.0, 3.0]
True === elem 1.0 ([1.0, 2.0, 3.0] : [Decimal])
False === elem 4 [1, 2, 3]
False === elem "" []
testNotElem = scenario do
False === notElem 1.0 [1.0, 2.0, 3.0]
False === notElem 1.0 ([1.0, 2.0, 3.0] : [Decimal])
True === notElem 4 [1, 2, 3]
True === notElem "" []
@ -124,8 +124,8 @@ testOptional = scenario do
5 === optional 5 T.length (Some "12345")
testEither = scenario do
11 === either (+1) floor (Left 10)
11 === either (+1) floor (Right 11.5)
11 === either (+1) (floor : Decimal -> Int) (Left 10)
11 === either (+1) (floor : Decimal -> Int) (Right 11.5)
testAppend = scenario do
"abc123" === "abc" <> "123"
@ -168,9 +168,9 @@ testSequence = scenario do
Right [1, 2] === sequence [Right 1, Right 2 : Either Text Int]
testRbind = scenario do
Some 1 === (Some . floor =<< Some 1.9)
None === (Some . floor =<< None)
(None : Optional Int) === (const None =<< Some 1.9)
Some 1 === (Some . floor =<< Some (1.9 : Decimal))
None === (Some . (floor : Decimal -> Int) =<< None)
(None : Optional Int) === (const None =<< Some (1.9 : Decimal))
testConcatMap = scenario do
["a", "b", "c", "d"] === concatMap T.explode ["a", "bcd"]
@ -239,20 +239,20 @@ testZip3 = scenario do
[] === zip3 ([] : [Int]) ([] : [Text]) ([] : [Decimal])
[] === zip3 [1, 2, 3] ([] : [Text]) ([] : [Decimal])
[] === zip3 ([] : [Int]) ["A", "B", "C"] ([] : [Decimal])
[] === zip3 ([] : [Int]) ([] : [Text]) [1.0, 2.0]
[(1, "A", 2.0), (2, "B", 1.0)] === zip3 [1, 2, 3] ["A", "B"] [2.0, 1.0, 3.0]
[(1, "A", 2.0), (2, "B", 1.0)] === zip3 [1, 2] ["A", "B", "C", "D"] [2.0, 1.0, 3.0]
[(1, "A", 2.0), (2, "B", 1.0), (0, "C", 3.0)] === zip3 [1, 2, 0, 5] ["A", "B", "C", "D"] [2.0, 1.0, 3.0]
[] === zip3 ([] : [Int]) ([] : [Text]) ([1.0, 2.0] : [Decimal])
[(1, "A", 2.0), (2, "B", 1.0)] === zip3 [1, 2, 3] ["A", "B"] ([2.0, 1.0, 3.0] : [Decimal])
[(1, "A", 2.0), (2, "B", 1.0)] === zip3 [1, 2] ["A", "B", "C", "D"] ([2.0, 1.0, 3.0] : [Decimal])
[(1, "A", 2.0), (2, "B", 1.0), (0, "C", 3.0)] === zip3 [1, 2, 0, 5] ["A", "B", "C", "D"] ([2.0, 1.0, 3.0] : [Decimal])
testZipWith = scenario do
[(1, "A"), (2, "B")] === zipWith (,) [1, 2] ["A", "B", "C", "D"]
[(1, "A"), (2, "B"), (0, "C")] === zipWith (,) [1, 2, 0, 4, 9] ["A", "B", "C"]
[11, 25, 33] === zipWith ((+) . floor) [1.9, 5.4, 3.2] [10, 20, 30, 40]
[11, 25, 33] === zipWith ((+) . floor) ([1.9, 5.4, 3.2] : [Decimal]) [10, 20, 30, 40]
testZipWith3 = scenario do
[(1, "A", 2.2), (2, "B", 1.1)] === zipWith3 (,,) [1, 2] ["A", "B", "C", "D"] [2.2, 1.1, 3.3]
[(1, "A", 2.2), (2, "B", 1.1), (0, "C", 3.3)] === zipWith3 (,,) [1, 2, 0, 4, 9] ["A", "B", "C"] [2.2, 1.1, 3.3]
[11, 21, 31] === zipWith3 (\x y z -> floor x + y - T.length z) [1.9, 5.4, 3.2] [10, 20, 30, 40] ["", "....", ".."]
[(1, "A", 2.2), (2, "B", 1.1)] === zipWith3 (,,) [1, 2] ["A", "B", "C", "D"] ([2.2, 1.1, 3.3] : [Decimal])
[(1, "A", 2.2), (2, "B", 1.1), (0, "C", 3.3)] === zipWith3 (,,) [1, 2, 0, 4, 9] ["A", "B", "C"] ([2.2, 1.1, 3.3] : [Decimal])
[11, 21, 31] === zipWith3 (\x y z -> floor x + y - T.length z) ([1.9, 5.4, 3.2] : [Decimal]) [10, 20, 30, 40] ["", "....", ".."]
testUnzip = scenario do
([], []) === unzip ([] : [(Int, Text)])
@ -260,7 +260,7 @@ testUnzip = scenario do
testUnzip3 = scenario do
([], [], []) === unzip3 ([] : [(Int, Text, Decimal)])
([1, 2, 3], ["A", "B", "C"], [2.0, 1.0, 9.0]) === unzip3 [(1, "A", 2.0), (2, "B", 1.0), (3, "C", 9.0)]
([1, 2, 3], ["A", "B", "C"], [2.0, 1.0, 9.0]) === unzip3 [(1, "A", 2.0), (2, "B", 1.0), (3, "C", (9.0 : Decimal))]
testFst = scenario do
1 === fst (1, "A")
@ -285,32 +285,32 @@ testIntToDecimal = scenario do
assert $ intToDecimal 0 == 0.0
testTruncate = scenario do
assert $ truncate 14.9 == 14
assert $ truncate 15.0 == 15
assert $ truncate (-9.3) == (-9)
assert $ truncate 0.0 == 0
assert $ truncate (14.9 : Decimal) == 14
assert $ truncate (15.0 : Decimal) == 15
assert $ truncate ((-9.3) : Decimal) == (-9)
assert $ truncate (0.0 : Decimal) == 0
testCeiling = scenario do
assert $ ceiling 14.9 == 15
assert $ ceiling 15.0 == 15
assert $ ceiling (-9.3) == (-9)
assert $ ceiling 0.0 == 0
assert $ ceiling (14.9 : Decimal) == 15
assert $ ceiling (15.0 : Decimal) == 15
assert $ ceiling ((-9.3) : Decimal) == (-9)
assert $ ceiling (0.0 : Decimal) == 0
testFloor = scenario do
assert $ floor 14.9 == 14
assert $ floor 15.0 == 15
assert $ floor (-9.3) == (-10)
assert $ floor 0.0 == 0
assert $ floor (14.9 : Decimal) == 14
assert $ floor (15.0 : Decimal) == 15
assert $ floor ((-9.3) : Decimal) == (-10)
assert $ floor (0.0 : Decimal) == 0
testRound = scenario do
assert $ roundCommercial 0 10.5 == 11.0
assert $ roundCommercial 2 22.105 == 22.110
assert $ roundBankers 0 10.5 == 10.0
assert $ roundBankers 2 22.105 == 22.100
assert $ roundCommercial 0 (-10.5) == -11.0
assert $ roundCommercial 2 (-22.105) == -22.110
assert $ roundBankers 0 (-10.5) == -10.0
assert $ roundBankers 2 (-22.105) == -22.100
assert $ roundCommercial 0 10.5 == (11.0 : Decimal)
assert $ roundCommercial 2 22.105 == (22.110 : Decimal)
assert $ roundBankers 0 10.5 == (10.0 : Decimal)
assert $ roundBankers 2 22.105 == (22.100 : Decimal)
assert $ roundCommercial 0 (-10.5) == (-11.0 : Decimal)
assert $ roundCommercial 2 (-22.105) == (-22.110 : Decimal)
assert $ roundBankers 0 (-10.5) == (-10.0 : Decimal)
assert $ roundBankers 2 (-22.105) == (-22.100 : Decimal)
testNth = scenario do
let l = [1, 5, 10]
@ -318,12 +318,12 @@ testNth = scenario do
assert $ v == 5
testDiv = scenario do
assert $ 10.0 / 2.0 == 5.0
assert $ 13.2 / 5.0 == 2.64
assert $ 10.0 / 2.0 == (5.0 : Decimal)
assert $ 13.2 / 5.0 == (2.64 : Decimal)
assert $ 0.5 == recip 2.0
1.0 / 3.0 === 0.3333333333
1.0 / 3.0 * 3.0 === 0.9999999999
assert $ 0.5 == recip (2.0 : Decimal)
1.0 / 3.0 === (0.3333333333 : Decimal)
1.0 / 3.0 * 3.0 === (0.9999999999 : Decimal)
10 / 2 === 5
10 / 3 === 3

View File

@ -9,4 +9,4 @@ daml 1.2
module RationalLowerBoundError where
-- -10^38 / 10^10
a = -10000000000000000000000000000.0000000000
a = (-10000000000000000000000000000.0000000000 : Decimal)

View File

@ -8,4 +8,4 @@ daml 1.2
module RationalLowerBoundMax where
-- -10^38 + 1 / 10^10
a = -9999999999999999999999999999.9999999999
a = (-9999999999999999999999999999.9999999999 : Decimal)

View File

@ -7,4 +7,4 @@ daml 1.2
module RationalPrecisionMax where
a = 0.0000000005
a = (0.0000000005 : Decimal)

View File

@ -8,4 +8,4 @@ daml 1.2
module RationalPrecisionUpperBoundError where
a = 0.00000000005
a = (0.00000000005 : Decimal)

View File

@ -9,4 +9,4 @@ daml 1.2
module RationalUpperBound where
-- 10^38 / 10^10
a = 10000000000000000000000000000.0000000000
a = (10000000000000000000000000000.0000000000 : Decimal)

View File

@ -30,8 +30,8 @@ main = scenario do
show (Field1 Bar (Some Blue) : Fields Int) === "Field1 {foo = bar, color = Some Blue}"
show (Some (Some Red)) === "Some (Some Red)"
show 1 === "1"
show 1.0 === "1.0"
show 1.1 === "1.1"
show (1.0 : Decimal) === "1.0"
show (1.1 : Decimal) === "1.1"
show "test" === "\"test\""
show True === "True"
show False === "False"

View File

@ -14,7 +14,7 @@ testEmpty = scenario do
testSize = scenario do
0 === size (fromList ([] : [(Text, Decimal)]))
3 === size (fromList [("1", 2.0), ("2", 9.0), ("3", 2.2)])
3 === size (fromList ([("1", 2.0), ("2", 9.0), ("3", 2.2)] : [(Text, Decimal)]))
testToList = scenario do
[("1", "c"), ("2", "a"), ("5", "b")] === toList (fromList [("2", "a"), ("5", "b"), ("1", "c")])
@ -29,13 +29,13 @@ testFromListWith = scenario do
fromListWith (++) [] === (empty : TextMap [Int])
testMember = scenario do
False === member "a" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
True === member "" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
False === member "a" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
True === member "" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
False === member "2" (fromList [])
testLookup = scenario do
None === TM.lookup "a" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
Some 1.0 === TM.lookup "" (fromList [("", 1.0), ("b", 2.0), ("c", 3.0)])
None === TM.lookup "a" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
Some 1.0 === TM.lookup "" (fromList ([("", 1.0), ("b", 2.0), ("c", 3.0)] : [(Text, Decimal)]))
None === TM.lookup "2" (fromList ([] : [(Text, Text)]))
testNull = scenario do

View File

@ -12,8 +12,8 @@ testFirst = scenario do
(3, "abc") === first length ([1,2,3], "abc")
testSecond = scenario do
(5.0, 12) === second (*4) (5.0, 3)
(5.0, 3) === second length (5.0, [1,2,3])
(5.0, 12) === second (*4) ((5.0 : Decimal), 3)
(5.0, 3) === second length ((5.0 : Decimal), [1,2,3])
testBoth = scenario do
(3, 1) === both length ([1,2,3], [1])

View File

@ -42,7 +42,7 @@ private[validation] object Typing {
protected[validation] lazy val typeOfBuiltinFunction = {
val alpha = TVar(Name.assertFromString("$alpha$"))
val beta = TVar(Name.assertFromString("$beta$"))
val gamma = TVar(Name.assertFromString("$beta$"))
val gamma = TVar(Name.assertFromString("$gamma$"))
def tBinop(typ: Type): Type = typ ->: typ ->: typ
val tNumBinop = TForall(alpha.name -> KNat, tBinop(TNumeric(alpha)))
val tMultiNumBinop =