Can I introspect source code? Line numbers/modules?

As a splendid :grin: 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?

1 Like

Can you mock up what you are trying to do?

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.

1 Like

We already return location information on the API, I just reminded myself of what that looks like:

Interpretation error: Error: User abort: foo. Details: Last location: [ScriptExample:25], partial transaction: <empty transaction>.

The location ScriptExample:25 are the line number and module name of the abort statement that aborted the transaction.

1 Like

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.

1 Like

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.

1 Like

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.

1 Like

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.

2 Likes

I haven’t checked when this was added, but I notice we have a DA.Stack module now that can provide this type of debugging information.