Question about loops and actions

G-d willing

Hello,
Assuming I have the following data structures:

data ComplexText = ComplexText with
  textValues = [TextValue]

data TextValue = OneText with text1 : Text
    | TwoTexts with text1 : Text; text2 : Text
    | ThreeTexts with text1 : Text; text2 : Text; text3: Text

I would like to have a function with the following signature:

setComplexText : Text -> ComplexText -> ComplexText
setComplexText value textValues = 

the function basically receives a text argument and returns the same ComplexText that was sent on the second argument with all text1 fields set to the first text argument.
Can someone please advise me what is the best way to do it? I want to do it with mapA_ or forA_ but I am not sure how to do it.

Let’s take this piece by piece. Put an _what (underscore what) after the last =, like so

setComplexText value textValues = _what

-- Found hole: _what : ComplexText

And you’ll see that :arrow_up: error, meaning the expression at _what must have type ComplexText. (what can be anything, the only important thing is to start with a _. In this example, _what is the hole, and ComplexText is the goal type.

Ok, there’s only one way to make a ComplexText. Moreover, we’ll have to pull out the textValues from the argument. So let’s do both of those:

setComplexText value (ComplexText {textValues})=
  ComplexText with textValues = _tv

-- Found hole: _tv : [TextValue]

The function you’re looking for is map. Let’s use that:

  ComplexText with textValues = map _f textValues

-- Found hole: _f : TextValue -> TextValue

Well, we could write \tv -> tv and that would type, but you want to change the TextValues. So let’s just elaborate the lambda:

  ComplexText with textValues = map (\tv -> _otv) textValues

-- Found hole: _otv : TextValue
-- • Relevant bindings include
--    tv : TextValue
--      (bound at .../Main.daml:74:39)
--    textValues : [TextValue]
--      (bound at ...:73:36)
--    value : Text

You’ll note that when you introduce a variable, the “hole” error includes information about the type of that variable, which is really useful when you’re drilling down into complex structures, and not something that top-level variable declarations can really help you with.

You really want to have a different result based on which case tv : TextValue is in, so you can split it out into its cases:

  ComplexText with textValues = map (\tv -> case tv of  
                                              OneText {..} -> _one
                                              TwoTexts {..} -> _two
                                              ThreeTexts {..} -> _three) textValues

Each of _one, _two, _three is a separate error, each with different bindings; for example, the _two error tells you that text1 and text2 are available.

There are more language features that can shrink this code once you have this basic form working, but I would leave those for another post :slight_smile:

However, I would definitely suggest structuring TextValue differently. Since text1 occurs in every case, it would be better to have a record type with two fields: text1, and a second field of a variant type that contains nothing, text2, or text2 and text3.

Another alternative might be to use the “algebraic” form: your type is exactly as expressive as (Text, Optional (Text, Optional Text)), but the latter form has the benefit that various functions already exist for it. There’s a tradeoff here regarding the flexibility of your domain-specific TextValue form, but it is a tradeoff worth evaluating rather than dismissing.

1 Like

Thank you @Stephen. Since I will be doing it in several places I would like to use it in a function and not with a lambda. How can I write & use a function instead of it?

I tried with no success:

updateText : TextValue -> Text -> TextValue
updateText tv value =
  case tv of
      OneText{..} -> tv with text1 = value
      TwoTexts{..} -> tv with text1 = value
      ThreeTexts{..} -> tv with text1 = value

And then call it like this:

ComplexText with textValues = map (updateText tv "hello") textValues

But that of course does not compile. Can you please help me understand my mistake in here?

The problem here is that there is no tv variable when you write (updateText tv "hello"). What you want there instead of tv is “each element of textValues”. There are a couple ways to achieve that.

  1. You can wrap your updateText call in a lambda:
    map (\tv -> updateText tv "hello") textValues
    
  2. Use a “section”. A section is a function created by using an operator but not giving it all of its arguments; for example, (3*) is a section created by giving * its first argument but not its second, and is a function that takes one (numeric) argument and multiplies it by 3. Similarly, (+2) is a section created by giving + its second argument, but not the first one. More interestingly perhaps, (/2) is a function that will divide its argument by 2. How is this applicable here? In Daml, any two-argument function can be turned into an operator by surrounding its name (at use time) in backticks. This means that you can get your code snippet to work with:
    map (`updateText` "hello") textValues
    
    where the section is now ready to receive the first argument to updateText.
  3. Finally, you could change the order of arguments of updateText so it receives the Text first. If updateText has type Text -> TextValue -> TextValue, then (updateText "hello") has type TextValue -> TextValue and you can just write
    map (updateText "hello") textValues
    
    making use of automatic currying.
  4. If you don’t want to change the definition of udpateText, but still want to have the arguments reversed for this one use, you can use the function flip. As the name might suggest, it takes a function and flips its arguments. In our case, this means you could write
    map (flip updateText "hello") textValues
    
1 Like

Thank you so much @Gary_Verhaegen for your answer.
It helped me so much. I learned new things thanks to you.

@Gary_Verhaegen I have no way to express my thanks for your detailed answer.
Can I ask you about the second solution regarding the use of the “section”? Why we don’t write the first argument? Is there a link I can learn more about this?

Sections are a feature we’re inheriting directly from Haskell. I don’t think it’s documented in our own documentation, but you can take a look at this page, for example, for a more in-depth explanation. In this case, we’d use the section approach, usually meant for mathematical operator, together with the special backtick syntax that can turn any two-arg function into an operator, which is explained here.