There have been previous questions on the forum around smart constructors and the use of newtype’s to have fine grain control over types without the extra boxing burden. One way to verify that one’s on ledger data complies is wrap the check for validity into the precondition ensure clause of a Template
.
That is easy enough with a custom type class, ex
newtype Price = Price { p : Decimal }
deriving (Eq, Ord, Show)
class Ensurable a where
valid : a -> Bool
instance Ensurable Price where
valid Price { p } = p >= 0.0
and then
template Good
with
seller : Party
asset : Text
price : Price
where
signatory seller
ensure valid price
Three related questions:
- It seems that one cannot reuse the
HasEnsure
type class for this purpose as:
instance HasEnsure Price where
ensure Price { p } = p >= 0.0
leads to
$ daml build
Compiling training to a DAR.
File: daml/Ensureable.daml
Hidden: no
Range: 1:1-2:1
Source: Core to DAML-LF
Severity: DsError
Message:
Failure to process DAML program, this feature is not currently supported.
Missing required instances in template definition. with (:).
[T, y, p, e, C, o, n, N, a, m, e, , {, u, n, T, y, p, e, C, o, n,
N, a, m, e, , =, , [, ", P, r, i, c, e, ", ], }]
ERROR: Creation of DAR file failed.
Is this intentional? I could imagine that if we were to reuse it for non Template, data that it could be confusing as the default semantics would not execute the ensure
check on price
(assuming it has a HasEnsure
instance) unless specifically asked.
-
On the flip side, if the check was automatic it could extend the usefulness of the type class. And the broad notion of a precondition check would not need to be separated into different type classes. This automatic behavior could be particularly useful as one would not have to nest all of your data in
Ensurable
, just the “leaf” element where one defines custom types. But at the surprise of performing computation that is not explicitly defined in a template. -
Are there other considerations to adopting these precondition checks to data types? I can see that it could hinder performance (or at least lead to surprising behavior), as that check gets called on every creation. For example, a consuming choice that updates another field but does not modify a
Price
in this example would lead to another check of the Price’s validity on the creation of the choice’s output. Furthermore, I assume the nestingEnsurable
’s would incur a cost that has to be paid at construction. For example, if I were to have atype Inventory = TextMap Price
, no way around walking the map?