I've just discovered what the `@<template_name>` notation in functions like `fetchByKey` et al means

So far I thought it was part of the key parameter, so that we know what template the key is part of (I’m not a Haskell pro - yet…).

It turns out that the @<template_name> notation belongs to the function, and it’s called visible type application in Haskell.

Here is a description: Visible Type Application in GHC 8 (Kwang’s Haskell Blog).

Kwang illustrates its use by the following example, compare:

λ> id "a"
"a"
λ> id (3 :: Int)
3

With using visible type annotation it becomes simpler:

λ> :set -XTypeApplications
λ> id @String "a"
"a"
λ> id @Int 3
3

For fetchByKey, thinking that the @<template_name> part belongs to the key argument, doesn’t cause a syntax error, because we don’t have parameters other than the key.

But for queryContractKey, which also has a party parameter, we have to keep in mind that the @<template_name> part must be written right after the function name, and this is the right syntax:

maybeDealer <- queryContractKey @Username [originator,operator] (operator, "DealerUser")
6 Likes

Another subtle thing which took me a while to understand is that the type applications need to appear in the order corresponding to the type parameters in the function. And this is tied in with the forall statement you may have sometimes seen.

For example, a full function signature is written like:

fmap : forall f a b . (a -> b) -> f a  -> f b

writing fmap @Set would mean that f = Set, because it appears first in the forall statement. However, often you’ll see signatures with the forall omitted, e.g.:

fmap : (a -> b) -> f a -> f b

in which case the compiler will infer the order of variables from the signature. In this case, I think it would mean that writing fmap @Set would fail (someone correct me), because it would try to match Set : * -> * with a : *, the first type parameter, rather than f : * -> *, which appears third in that signature.

And that’s how I discovered what forall is for :grin:

2 Likes

Here’s an actual example of what I’m talking about above:

1 Like

Thanks, that’s interesting, I saw forall many times but so far I didn’t dig deeper to understand what it’s for.

1 Like

Let me introduce a little wrinkle. I added this to the trigger Daml library a while ago:

query : forall a m. (Template a, ActionTriggerAny m) => m [(ContractId a, a)]
query = implQuery

class ActionTriggerAny m where
  implQuery : forall a. Template a => m [(ContractId a, a)]

We are indeed supporting the @<template_name> application that @gyorgybalazsi started off with. Why would I not define query within class ActionTriggerAny here?

1 Like

I’m not sure what you’re getting at, tbh?

You couldn’t define query at least in the same way as it is in your example, i.e.

class ActionTriggerAny m where
  query : forall a m . (Template a, ActionTriggerAny m) => m [(ContractId a, a)]
                  --   ^ m is defined twice!

because the m parameter is declared as a class-level (for lack of a better term) variable. So you would be trying to declare m twice. But this could be worked around by just dropping the m type constraint, as you’ve done?

Or is it somehow related to the inference of m? So that it’s lost and can’t be “type applied” when it’s declared at the class level? i.e. I couldn’t write implQuery @MyTemplate @Script for example? Just guessing here, I really don’t know :confused: :laughing:

1 Like

For class methods, you can’t explicitly quantify over the variables bound in the class head and those variables always come before the ones you quantify over in the method. So in this example, m would be before a which is not what you want for type applications.

1 Like

The same visible type application is used with the query function in Triggers as well:

  subscribers : [(ContractId Subscriber, Subscriber)] <- query @Subscriber
  originals : [(ContractId Original, Original)] <- query @Original
  copies : [(ContractId Copy, Copy)] <- query @Copy

See: Daml Triggers - Off-Ledger Automation in Daml — Daml SDK 1.10.0 documentation

And for the query function for triggers:

https://docs.daml.com/triggers/api/Daml-Trigger.html#function-daml-trigger-query-2759

1 Like