Hi @cohen.avraham,
Not everything in Daml is a function: we also have values (1
, "hello"
, None
, etc.).
What @cocreature meant is that your code is essentially rewritten to (first snippet):
convertTypeByProduct : Optional SomeType -> ProductType -> Date -> Optional SomeOtherType
convertTypeByProduct someType productType today =
let mustHaveValue =
let valueType = doSomething someType in
if isNone valueType
then error $ "No Value Error | Product Type = " <> (show productType)
else Some SomeOtherType with
intValue = 1
in
case productType of
TypeOne -> mustHaveValue
TypeTwo -> mustHaveValue
TypeThree -> None
which may make the problem more apparent: when executing that function, the first thing that the Daml interpreter does is evaluating the value of the right hand side of the mustHaveValue = ...
assignment. If your argument happens to make doSomething
return None
, this evaluation will trigger an error (due to the error
function being called), before Daml even looks at the case productType of
line.
In the second case, we have essentially the same scenario, except that it roughly gets rewritten to:
convertTypeByProduct : Optional SomeType -> ProductType -> Date -> Optional SomeOtherType
convertTypeByProduct someType productType today =
let mustHaveValue = \value ->
let valueType = doSomething someType in
if isNone valueType
then error $ "No Value Error | Product Type = " <> (show productType) <> " | value = " <> (show value)
else Some SomeOtherType with
intValue = 1
in
case productType of
TypeOne -> mustHaveValue 1
TypeTwo -> mustHaveValue 2
TypeThree -> trace "Reached TypeThree" None
because the syntax f x = body
is a shorthand for f = \x -> body
. The Daml interpreter is still going to look at the mustHaveValue = ...
assignment first, but in this case, because the right hand side is a function (specifically, an anonymous function, also known as a lambda), its evaluation is deferred until it is applied to an argument. Now, in this case, the mustHaveValue
function will not be called if the productType
argument matches the TypeThree
constructor, and therefore, if TypeThree
was the only value that made doSomething
return None
, you no longer have an error.
So hopefully that clarifies the problem a little bit.
Now, what to do about it? The fundamental issue here is that you are calling error
, which is hard to use because it produces a side-effect. It is generally best to keep your Daml code as pure as possible: as you can see here, it is sometimes a bit difficult to reason about the order of evaluation of a Daml expression, and pure code does not care about order of evaluation (barring non-termination).
How to turn this code pure? One way to collect error conditions without calling error
explicitly is to use a âsuccess/errorâ wrapping type. In your case, youâre almost already doing that: doSomething
returns an Optional
, which in some contexts can be interpreted as success = Some
and error = None
. Further, convertTypeByProduct
is already defined to return an Optional
itself, so one option would be to simply carry on the Optional
value and have None
mean âthere was an errorâ:
convertTypeByProduct : Optional SomeType -> ProductType -> Date -> Optional SomeOtherType
convertTypeByProduct someType productType today =
let mustHaveValue = \value ->
let valueType = doSomething someType in
if isNone valueType
then None
else Some SomeOtherType with
intValue = 1
in
case productType of
TypeOne -> mustHaveValue 1
TypeTwo -> mustHaveValue 2
TypeThree -> trace "Reached TypeThree" None
This assumes the code that calls convertTypeByProduct
considers None
to be an error and handles it appropriately.
Note that if doSomething
already returns None
exclusively for TypeThree
values, using some syntactic sugar, I believe this can be rewritten as
convertTypeByProduct : Optional SomeType -> ProductType -> Date -> Optional SomeOtherType
convertTypeByProduct someType productType today = do
valueType <- doSomething someType
return $ SomeOtherType 1
but that may be a topic for another time.
One thing weâve lost with this approach is that we no longer have an error message, whereas we had one in the original approach. This is because Optional
does not have any argument to its second (None
) constructor. Thereâs another type we can use, though, which can have a similar interpretation: Either
. Specifically, Either Text SomeType
in this case, where you could have the calling code interpret Right SomeType
as a success and Left Text
as an error with the error message attached.
This would look something like:
convertTypeByProduct : Optional SomeType -> ProductType -> Date -> Either Text SomeOtherType
convertTypeByProduct someType productType today =
let mustHaveValue =
let valueType = doSomething someType in
if isNone valueType
then Left $ "No Value Error | Product Type = " <> (show productType)
else Right SomeOtherType with
intValue = 1
in
case productType of
TypeOne -> mustHaveValue
TypeTwo -> mustHaveValue
TypeThree -> Left "given TypeThree"
Note the changed return type for convertTypeByProduct
.
Finally, if you also rewrote doSomething
itself to return an Either
, you could do something like:
convertTypeByProduct : Optional SomeType -> ProductType -> Date -> Either Text SomeOtherType
convertTypeByProduct someType productType today =
let mustHaveValue = do
valueType <- doSomething someType
return $ SomeOtherType 1
in
case productType of
TypeOne -> mustHaveValue
TypeTwo -> mustHaveValue
TypeThree -> Left "given TypeThree"
assuming that doSomething
returns a Left Text
with the appropriate error message itself.