Skip to content
Discussions/Outreach/I've just discovered what the `@<template_name>` notation in functions like `fetchByKey` et al meansForum ↗

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

Outreach8 posts1,162 views14 likesLast activity Mar 2021
GY
gyorgybalazsiOP
Mar 2021

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")
LU
Luciano
Mar 2021

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:

LU
Luciano
Mar 2021

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

Hi Luciano, that’s a very interesting problem. The solution mostly involves sprinkling a few more types across your code. Despite that, you will still need to enable the AllowAmbiguousTypes language extension because the type parameter f only appears within the type class constraints. First of all, you need to tell the compiler that you want to access the x field in updateInner's setField and getField: updateInner : forall x f. (Functor f, HasField x Outer (f Inner)) => (Inner -> Inner) -> Ou…
GY
gyorgybalazsi
Mar 2021

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

ST
Stephen
Mar 2021

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?

LU
Luciano
Mar 2021

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:

CO
cocreature
Mar 2021

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.

GY
gyorgybalazsi
Mar 2021

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

← Back to Discussions