As a splendid functional programmer I write my error handling using an error monad, or even better an applicative.
When I eventually trigger a runtime failure, for example with an assertMsg clause, I’d like to be able to determine where the error occurred (which might not be the same as where assert is called)
So my question is whether there is a way to introspect a file/module’s name and line number in DAML code, so as to store them in a string for later display. The incentive being that as a developer I’d like to be able to quickly pinpoint where an error was raised in code.
If it’s not possible, is this something that could be added to the language? Or are there obvious problems with this approach?
Let’s say you do your error handling using Either Text:
instance CanAbort (Either Text) where
abort = Left
At which place are you hoping to get your hands on line numbers?
s : Scenario () = do
p <- getParty "p"
let e : Either Text () = do
abort "foo"
submit p do
assert (e == Right ())
return ()
Are you hoping to be able to introspect line number at line 4 (the abort) to be able to say abort ("foo at " <> stackTrace"), or are you hoping to be able to magically recover it at line 6 to say assertMsg (show e <> " at " <> stackTraceOf e) (e == Right())?
Either way, I’m not sure it’s a good idea to make this information introspectable within DAML. If your ask was merely to emit line number information / stack traces on the API or in the console logs, that sounds more harmless.
As @bernhard said, you currently cannot introspect the line number from DAML itself.
I don’t see a fundamental issue with exposing line information. A common solution for that is CPP which gives you a __LINE__ and __FILE__ macro. We don’t currently expose CPP to users but we do use it internally for compiling the standard library and it wouldn’t be that difficult to expose it (you can argue about whether exposing CPP is a good idea but that’s a separate issue).
The other option is something like Haskell’s HasCallStack which is nicer since it’s a language construct rather than something that you bolt on top like CPP. I haven’t tried exposing it but all the hard work here should be in GHC so I don’t expect it to be too difficult.
Yes, maybe an example would make it easier to understand. Let’s take a classic example of form validation:
newType MyErr = MyErr Text --ideally would include line no. here
type Validated = Validation MyErr a
data TheForm = TheForm with
name : Text
birthday : Text
phone : Text
data ParsedForm = TheForm with
name : Text
birthday : Date
phone : Int
validateName : Text -> Validated Text
validateBirthday: Text -> Validated Date
validatePhone : Text -> Validated Int
validateForm : Scenario ()
validateForm = do
let a = ParsedForm
<$> validateName
<*> validateBirthday
<*> validatePhone
(run a : Either [MyError] ParsedForm) === Right (ParsedForm "Luciano" (Date 2020 08 11) 7841234)
Just for clarity, this will parse the three fields, emitting one or more validation errors.
In case one or more of those validation functions fail, I would like to have assert throw an error like:
Interpretation error: Error: User abort: [
MyModule:12 Name is too long.
MyModule: 14 Date must be of the format DD-MM-YY
].
Details: Last location: [ScriptExample:25], partial transaction: <empty transaction>.
So you see, in this case, the line numbers emitted in the fictitious error above are different (and in a different module) from ScriptExample:25.
This [Validation] is a pretty common pattern.
It also has the advantage of parsing all the fields in the form, and then emitting an error with all of them, rather than failing-fast on the first error.
Before we jump to exposing CPP as a language feature we have to support forever, can you clarify a little bit more why you want this? I can’t think of any point in my career where I would ever have wanted this, in any language.
In particular, why is the obvious alternative of having clear, unique error messages not good enough? In my experience, in most cases, with a good error message, I don’t need to look at the code at all, and in the rare cases where I do, grep works great.
I agree with you entirely. Unfortunately, in my case, we have little or not control over these - they’re spec’d by the client. To repeat that: I would prefer pushing back and getting this changed; unfortunately it’s not an option. However, the above feature would help, regardless of the circumstances.
Thanks for all your feedback. I agree that this isn’t probably the best approach for solving my problem - I should have error messages that are meaningful, as @Gary_Verhaegen pointed out. Not a particularly burning desire to have this feature, was more curiosity than anything.