How to use Either in Guard statement

Hi Everyone

I am new here and this is my first post. I am learning DAML and in the middle of one of the course videos.

I am trying to figure out how to chain together Functions returning Either Right or Left and if that is right I am trying to chain together conditional statements. I know how to do this in Rust for example but any help how to do this in DAML would be much appreciated. And how do I save into variables the result of the either statement if that the type is Right. Thank you

Maybe my thought process is not functional enough. Thank you for all your help!

Here is the code that I cannot seem to make to work. If you decide to help please keep it basic for me thank you :slight_smile:

– either
checkNumberOfSides: [Decimal] → Either Text [Decimal]
checkNumberOfSides sides =
if ((length sides) /= 3) then
Left “Error”
else Right sides

checkIfSideIsZero: [Decimal] → Int → Either Text [Decimal]
checkIfSideIsZero sides idx =
if (head sides == 0.0) then
Left “Error”
else Right (tail sides)

triangleArea: [Decimal] → Either Text [Decimal]
triangleArea sides =
if (checkNumberOfSides sides) then
if (checkIfSideIsZero 1) then
if (checkIfSideIsZero 2) then
if (checkIfSideIsZero 3) then
??

ideally I would like to do this

| …
| …
| …

So the primary issue here is that guards need to be Boolean expression, i.e. they need to return a Boolean value, not an Either a b.

You have two options: either you transform your functions into predicate, or you use Either a b as a monad. The shortest way to get your code to “work” as a monad would be something like:

module Main where

import Daml.Script
import DA.List (head, tail)
import DA.Math (sqrt)

checkNumberOfSides: [Decimal] -> Either Text [Decimal]
checkNumberOfSides sides =
  if ((length sides) /= 3) then
    Left "Error"
  else Right sides

checkIfSideIsZero: [Decimal] -> Int -> Either Text [Decimal]
checkIfSideIsZero sides idx =
  if (head sides == 0.0) then
    Left "Error"
  else Right (tail sides)

{-
-- This doesn't work, because guards must be Boolean expressions.
triangleAreaWrong: [Decimal] -> Either Text [Decimal]
triangleAreaWrong sides = case sides of
  _ | checkNumberOfSides sides -> Right sides
  _ | checkIfSideIsZero sides 1 -> Right sides
  _ | checkIfSideIsZero sides 2 -> Right sides
  _ | checkIfSideIsZero sides 3 -> Right sides
  _ -> Left "Error"
-}

triangleArea: [Decimal] -> Either Text Decimal
triangleArea sides = do
  [a, b, c] <- checkNumberOfSides sides
  _ <- checkIfSideIsZero sides 1
  _ <- checkIfSideIsZero sides 2
  _ <- checkIfSideIsZero sides 3
  let s = (sum sides) / 2.0
  return $ sqrt (s * (s -a) * (s - b) * (s -c))

setup : Script ()
setup = script do
  debug $ triangleArea [1.0, 1.0, 1.0]
  return ()

But that’s not very satisfying with all those _ <-. We can do better. First, here’s what the guard-based approach could look like:

module Main where

import Daml.Script
import DA.Math (sqrt)

hasThreeSides: [Decimal] -> Bool
hasThreeSides sides = length sides == 3

noZeroSide: [Decimal] -> Bool
noZeroSide sides = 0.0 `notElem` sides

triangleArea: [Decimal] -> Either Text Decimal
triangleArea sides = case sides of
  [a, b, c] | hasThreeSides sides && noZeroSide sides ->
    let s = (sum sides) / 2.0
    in return $ sqrt (s * (s -a) * (s - b) * (s -c))
  _ -> Left "Error"

setup : Script ()
setup = script do
  debug $ triangleArea [1.0, 1.0, 1.0]
  return ()

It’s not clear to me what using guards and Either a b adds in this case, but I think this illustrates nicely that this would not work as you want it to: you want to check that all the conditions match, whereas the guard syntax would offer one branch per condition.
If you want an example of how this could be constructed with Either a b, we could try something like this:

module Main where

import Daml.Script
import DA.List((!!))
import DA.Math (sqrt)

nonZero: Decimal -> Either Text Decimal
nonZero 0.0 = Left "zero element"
nonZero x = Right x

asTriangle: [Decimal] -> Either Text (Decimal, Decimal, Decimal)
asTriangle sides =
  if length sides /= 3 then
    Left $ "Incorrect number of sides: " <> show (length sides)
  else do
    a <- nonZero $ sides !! 0
    b <- nonZero $ sides !! 1
    c <- nonZero $ sides !! 2
    return (a, b, c)

triangleArea: [Decimal] -> Either Text Decimal
triangleArea sides = do
  (a, b, c) <- asTriangle sides
  let s = (sum sides) / 2.0
  return $ sqrt (s * (s -a) * (s - b) * (s -c))

hasThreeSides: [Decimal] -> Bool
hasThreeSides sides = length sides == 3

noZeroSide: [Decimal] -> Bool
noZeroSide sides = 0.0 `notElem` sides

setup : Script ()
setup = script do
  debug $ triangleArea [1.0, 1.0, 1.0]
  return ()

Hopefully this clarifies things a bit. Otherwise, please don’t hesitate to ask more questions.

2 Likes

Thank you for your valuable input Gary.

And that kicks off my journey in DAML. :slight_smile:

Brilliant. Thank you for the time and effort. Clears up a lot.