Utilizing Either As Choice Return Type

Hey folks,

I want to use the Either module to return one of two potential contract types in a template choice. I’m having issues figuring out the correct syntax to use.

Code sample and a screenshot of the error are below:

template ContractCreation
with
    owner: Party
where
    signatory owner

    controller owner can
        nonconsuming CreateContract: Either (ContractId Contract1) (ContractId Contract2)
            with
               creatorType: Text
            do  if (creatorType == "1") then do create Contract1 with owner
                else if (creatorType == "2") then do create Contract2 with owner
                else abort "Error"

template Contract1 
    with
        owner: Party
    where 
        signatory owner

template Contract2
    with
        owner: Party
    where 
        signatory owner

4 Likes

DAML doesn’t have any concept of implicit conversions. In your example, create gives you back a Update (ContractId Contract2) but you need an Update (Either (ContractId Contract1) (Contract2)) which is why you see the error.

To go from a to Either a b you have to use the Left constructor and similarly the Right constructor to go from b to Either a b.

Putting this to use in your example, you get something like the following:

module Main where
template ContractCreation
  with
    owner: Party
  where
    signatory owner

    controller owner can
        nonconsuming CreateContract: Either (ContractId Contract1) (ContractId Contract2)
            with
               creatorType: Text
            do  if (creatorType == "1") then do 
                  cid <- create Contract1 with owner
                  pure (Left cid)
                else if (creatorType == "2") then do 
                  cid <- create Contract2 with owner
                  pure (Right cid)
                else abort "Error"

template Contract1 
    with
        owner: Party
    where 
        signatory owner

template Contract2
    with
        owner: Party
    where 
        signatory owner

You could also use either of the following two in place of the do:

fmap Left (create Contract1 with owner)
Left <$> create Contract1 with owner

which one you prefer is mostly a matter of taste. The do version can often be easier to figure out as you get started but over time the more concise version using fmap or the infix version <$> often becomes appealing.

3 Likes

Adding a bit of context to make the last two lines less magical. First off, <$> is the same function as fmap, but defined as an infix operator. This means fmap a b is exactly the same as a <$> b. In the following I’ll talk only about fmap, but obviously this applies just as well to <$>.

fmap is a function defined on the Functor type class. As an early, informal, and incomplete explanation, you can think of a functor as some sort of container, and fmap function box as applying a function function to whatever is currently in the container box, returning a new box containing the result. The details of how that works depend on the type of the container. Here are a few concrete examples:

$ daml repl
daml> succ 1
2
daml> succ 4
5
daml> fmap succ [1]
[2]
daml> fmap succ [1, 2, 3, 4]
[2,3,4,5]
daml> fmap succ (Some 4)
Some 5
daml> fmap succ (None : Optional Int)
None
daml> 

As you can see, using fmap f on a List will return a new List of the same length where each element is the result of applying f to the corresponding element in the original list. Using fmap f on an Optional results in a new Optional that is None (empty) if the original was None, and a Some with the value of applying the f function if there was originally a value.

These could be defined as:

fmap : (a -> b) -> [a] -> [b]
fmap f [] = []
fmap f (x::xs) = f x :: fmap f xs

and

fmap : (a -> b) -> Optional a -> Optional b
fmap f None = None
fmap f (Some x) = Some (f x)

(These are not quite the correct syntax as fmap is a typeclass method, not a function. See this page for an example of how to implement typeclass instances.)

Stretching the analogy a bit, you can also think of Update as a container of sorts: it’s a magic box that will contain a result once the code has been run on the ledger. Despite the analogy not holding out too well here (it was informal and incomplete to begin with), Update is also an instance of Functor, and you can similarly think of fmap as somehow sending the function inside the update to be applied there. How would you go about doing that? Well, first you would have to create a context in which you can run some code (do), then in that context you can extract the value from the existing update (v <- update), and then you can apply the function and wrap the result in a new Update context (pure (f v)). So in this case the implementation would look something like:

fmap : (a -> b) -> Update a -> Update b
fmap f u = do
  v <- u
  pure (f v)

which is pretty much exactly the code in the branches of your choice, where Left (and Right for the other branch) is the function you want to apply.

So the final code using fmap would be:

module Main where
template ContractCreation
  with
    owner: Party
  where
    signatory owner

    controller owner can
        nonconsuming CreateContract: Either (ContractId Contract1) (ContractId Contract2)
            with
               creatorType: Text
            do if (creatorType == "1") then fmap Left (create Contract1 with owner)
               else if (creatorType == "2") then fmap Right (create Contract2 with owner)
               else abort "Error"

template Contract1 
    with
        owner: Party
    where 
        signatory owner

template Contract2
    with
        owner: Party
    where 
        signatory owner

As a final note, when I have a three-way (or more) branch, I tend to prefer pattern matching to if-then-else. This is very much a personal preference and there’s nothing wrong with if-then-else, but in case you’re curious here is what the body of the choice would look like using pattern matching (i.e. a case ... of expression):

            do case creatorType of
                 "1" -> fmap Left (create Contract1 with owner)
                 "2" -> fmap Right (create Contract2 with owner)
                 _ ->  abort "Error"
8 Likes

Thank you @cocreature. Marked as the answer, I appreciate the clarity on the syntax.

@Gary_Verhaegen, awesome awesome response and explanation. Really appreciated that extra level of explanation.

4 Likes