Binding a variable with a sequence of expressions
Hi Community! I’m trying to determine the best way to bind a sequence of Update expressions to the cps:[CalculationPeriod]:
generatePaymentCalculationPeriods : (Fetch f) => CalculationPeriodDates -> Optional ResetDates -> Optional PaymentDates -> f [PaymentCalculationPeriod]
generatePaymentCalculationPeriods cpds rds (fmap (checkPaymentDates cpds) -> pds) = do
-- Roll out calculation periods
cps <- generateResetPeriods cpds rds
-- Define payment calculation period
...
mapA (generateSinglePeriod payRelativeTo offset adj . replicate 1) cps
where
...
generateSinglePeriod payRelativeTo offset adj cps = do
let baseDate = case payRelativeTo of
PayRelativeToEnumCalculationPeriodStartDate ->
(get "adjustedStartDate" . (\cp -> cp.adjustedStartDate) . head) cps
PayRelativeToEnumCalculationPeriodEndDate ->
(get "adjustedEndDate" . (\cp -> cp.adjustedEndDate) . last) $ cps
otherwise -> throwNotSupportedError (show payRelativeTo)
...
return PaymentCalculationPeriod
{ id = None
, adjustedPaymentDate = Some aPayDate
, calculationPeriod = cps
, discountFactor = None
, fixedPaymentAmount = None
, forecastPaymentAmount = None
, presentValueAmount = None
, unadjustedPaymentDate = Some uPayDate
}
In the above function, cps generates Optional ResetDates to set the value for floatingRateDefinition.
My function below includes Optional OtherDates to also set otherValueDefinition:
generatePaymentCalculationPeriods : (Fetch f) => CalculationPeriodDates -> Optional ResetDates -> Optional OtherDates -> Optional PaymentDates -> f [PaymentCalculationPeriod]
generatePaymentCalculationPeriods cpds rds ods (fmap (checkPaymentDates cpds) -> pds) = do
-- Roll out calculation periods
cps <-
case (rds, ods) of
(Some r, None) -> generateResetPeriods cpds rds
(None, Some o) -> generateOtherPeriods cpds ods
(Some r, Some o) -> do
let r = generateResetPeriods cpds rds
let o = generateOtherPeriods cpds ods
-- sequence expression to return both r and o
return ...
(None, None) -> error "expecting calculation period"
The cps <- generate{Reset/Other}Periods functions have the same type signature, and their Update expressions are independent of each other, such that their respective floatingRateDefinition and otherValueDefinition will set.
Hi @Chris_Rivers, maybe you are looking for something like the following:
generatePaymentCalculationPeriods : (Fetch f) => CalculationPeriodDates -> Optional ResetDates -> Optional OtherDates -> Optional PaymentDates -> f [PaymentCalculationPeriod]
generatePaymentCalculationPeriods cpds rds ods (fmap (checkPaymentDates cpds) -> pds) = do
-- Roll out calculation periods
cps <-
case (rds, ods) of
(Some r, None) -> generateResetPeriods cpds rds
(None, Some o) -> generateOtherPeriods cpds ods
(Some r, Some o) -> do
r <- generateResetPeriods cpds rds
o <- generateOtherPeriods cpds ods
-- sequence expression to return both r and o
return (r ++ o)
(None, None) -> error "expecting calculation period"
The crucial part is that you first sequence the Updates with <- and then combine the results using ++ (assuming you are working with lists here).
You can also abstract over some of the repetition here to make it easier to extend with more optional values. Don’t take this as the best solution. What to abstract over and whether it’s worth abstracting over it at all heavily depends on your application:
handleOptional : Optional a -> (a -> f [b]) -> f [b]
handleOptional None _ = return []
handleOptional (Some x) f = f x
generatePaymentCalculationPeriods : (Fetch f) => CalculationPeriodDates -> Optional ResetDates -> Optional OtherDates -> Optional PaymentDates -> f [PaymentCalculationPeriod]
generatePaymentCalculationPeriods cpds rds ods (fmap (checkPaymentDates cpds) -> pds) = do
-- Roll out calculation periods
when (all isNone [rds, ods]) $
abort "Expecting calculation period"
results <- sequenceA
[ handleOptional rds (generateResetPeriods cpds)
, handleOptional ods (generateOtherPeriods ods)
]
let cps = concat results
…
(assuming you are working with lists here)
Your recommendation is helpful. On the abstraction comment, I had in mind of combining arrays based on a predicate for both rds and ods, albeit with more verbose language, but your suggestion is better.
After the sequence the Updates with <-, only the last Update takes effect, however. I see this because the Scenario sets the otherValueDefinition from o <- generate..., however, the error expects the floatingRateDefinition to set from the r <- generate....
Does this have something to do with this from above?: mapA (generateSinglePeriod payRelativeTo offset adj . replicate 1) cps
Could you show us the code you’re using now or at least the relevant parts? If you call sequenceA on a list of Updates, all of them will be applied not just the last one.
This class generates payment details:
generatePaymentCalculationPeriods : (Fetch f) => CalculationPeriodDates -> Optional ResetDates -> Optional OtherDates -> Optional PaymentDates -> f [PaymentCalculationPeriod]
generatePaymentCalculationPeriods cpds rds ods (fmap (checkPaymentDates cpds) -> pds) = do
-- Roll out calculation periods
cps <-
case (rds, ods) of
(Some r, None) -> generateResetPeriods cpds rds
(None, Some o) -> generateOtherPeriods cpds ads
(Some r, Some o) -> do
r <- generateResetPeriods cpds rds
o <- generateOtherPeriods cpds ods
return (r ++ o)
(None, None) -> error "expecting calculation period"
-- Define payment calculation period
let adj = optional noAdj (\x -> get "paymentDatesAdjustments" x.paymentDatesAdjustments) pds
let payRelativeTo = optional PayRelativeToEnumCalculationPeriodEndDate (\x -> get "payRelativeTo" x.payRelativeTo) pds
let offset = (\x -> x.paymentDaysOffset) =<< pds
mapA (generateSinglePeriod payRelativeTo offset adj . replicate 1) cps
where
noAdj = BusinessDayAdjustments
{ id = None
, businessCenters = None
, businessDayConvention = BusinessDayConventionEnumNONE
}
generateSinglePeriod :
(Fetch f)
=> PayRelativeToEnum
-> Optional Offset
-> BusinessDayAdjustments
-> [CalculationPeriod]
-> f PaymentCalculationPeriod
generateSinglePeriod _ _ _ [] = error "expecting at least one calculation period"
generateSinglePeriod payRelativeTo offset adj cps = do
let baseDate = case payRelativeTo of
PayRelativeToEnumCalculationPeriodStartDate ->
(get "adjustedStartDate" . (\cp -> cp.adjustedStartDate) . head) cps
PayRelativeToEnumCalculationPeriodEndDate ->
(get "adjustedEndDate" . (\cp -> cp.adjustedEndDate) . last) $ cps
otherwise -> throwNotSupportedError (show payRelativeTo)
uPayDate <- optional (pure baseDate) (\o -> applyOffset o adj.businessCenters baseDate) offset
aPayDate <- adjustDate adj uPayDate
return PaymentCalculationPeriod
{ id = None
, adjustedPaymentDate = Some aPayDate
, calculationPeriod = cps
, discountFactor = None
, fixedPaymentAmount = None
, forecastPaymentAmount = None
, presentValueAmount = None
, unadjustedPaymentDate = Some uPayDate
}
The PaymentCalculationPeriod details for the floatingRateDefinition:
populatePCP : [(Optional Text, ResetPrimitive)] -> [(Optional Text, EventPrimitive)] -> InterestRatePayout -> PaymentCalculationPeriod -> (PaymentCalculationPeriod, Lineage)
populatePCP existingResets existingEvents irp pcp =
let (resetReferences, eventReferences, cpsFull) = unzip3 $ map (setInterestRate . setDayCountFraction . setQuantity) pcp.calculationPeriod
lineage = emptyLineage
{ interestRatePayoutReference = [referenceWithEmptyMeta irp.primeKey]
, eventReference = map referenceWithEmptyMeta $ concat resetReferences
, eventReference = map referenceWithEmptyMeta $ concat eventReferences
}
pcpFull = pcp { calculationPeriod = cpsFull }
in (pcpFull, lineage)
where
...
setInterestRate : CalculationPeriod -> ([Text], [Text], CalculationPeriod)
setInterestRate cp
...
| Some floatingRate <- irp.rateSpecification.floatingRate
, Some otherValue <- irp.rateSpecification.otherValue
= let frDef = get "floatingRateDefinition" cp.floatingRateDefinition
frdNew = FR.calcFloatingRate existingResets floatingRate frDef
ovDef = get "otherValueDefinition" cp.otherValueDefinition
ovdNew = OV.calcOtherValue existingEvents otherValue ovDef
in (fst frdNew, fst ovdNew, cp { floatingRateDefinition = Some (snd frdNew)
; otherValueDefinition = Some (snd ovdNew) })
| otherwise = error "expecting exactly two 'rateSpecification'"So which of those updates is not getting applied?
I’ve made an update to the code example, the otherValue sets, however, the floatingRate from the setInterestRate does not:
Scenario execution failed on commit at Test.Event:118:28:
Aborted: expecting 'floatingRateDefinition' to be set
Stack trace:
- get (Org.Isda.Cdm.EventSpecificationModule.Impl.Utils:39:18)
- get (Org.Isda.Cdm.EventSpecificationModule.Impl.Utils:38:1)
- populatePCP (Org.Isda.Cdm.EventSpecificationModule.Impl.Contract.Payout.InterestRatePayout:166:13)
- populatePCP (Org.Isda.Cdm.EventSpecificationModule.Impl.Contract.Payout.InterestRatePayout:166:9)
- populatePCP (Org.Isda.Cdm.EventSpecificationModule.Impl.Contract.Payout.InterestRatePayout:131:72)
- $u002e (GHC.Base:53:11)
- map (GHC.Base:41:25)
- map (GHC.Base:41:15)
- populatePCP (Org.Isda.Cdm.EventSpecificationModule.Impl.Contract.Payout.InterestRatePayout:131:67)
- populatePCP (Org.Isda.Cdm.EventSpecificationModule.Impl.Contract.Payout.InterestRatePayout:131:58)
- populatePCP (Org.Isda.Cdm.EventSpecificationModule.Impl.Contract.Payout.InterestRatePayout:130:1)
- map (GHC.Base:41:25)
- map (GHC.Base:41:15)
- buildEvents (Org.Isda.Cdm.EventSpecificationModule.Impl.Contract.Payout.InterestRatePayout:61:7)
- $$c$u003e$u003e$u003d (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:49:34)
- $$c$u003e$u003e$u003d (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:48:23)
- $$cfmap (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:37:47)
- $$cfmap (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:37:24)
- $$c$u003c$u002a$u003e (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:42:47)
- $$c$u003c$u002a$u003e (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:42:23)
- $$cfmap (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:37:47)
- $$cfmap (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:37:24)
- $$c$u003e$u003e$u003d (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:48:46)
- $$c$u003e$u003e$u003d (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:48:23)
- $$cfmap (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:37:47)
- $$cfmap (Org.Isda.Cdm.EventSpecificationModule.Types.ReferenceData.Fetch:37:24)
- buildDerivedEvents (Org.Isda.Cdm.EventSpecificationModule.EventBuilder.Derived:37:41)
- buildDerivedEvents (Org.Isda.Cdm.EventSpecificationModule.EventBuilder.Derived:37:1)
- flip (DA.Internal.Prelude:369:1)
- $$cfmap (DA.Internal.LF:129:33)
- $$cfmap (DA.Internal.LF:129:22)Could you explain how things fit together in your example? The error expecting 'floatingRateDefinition' to be set doesn’t seem to be thrown by the code in your example which might hint at the issue being somewhere else. It also looks like you are not calling your populatePCP function.
The error seems to be thrown by this line. get is basically fromSomeNote.
In the code snippet you provide, it’s unclear to me how cp gets set. I imagine it comes directly from pcp.calculationPeriod, so the issue seems to be outside the function you are sharing (i.e. pcp is “wrong” to start with) or in the setDayCountFraction . setQuantity function, which may not be setting the expected field.
@cocreature and @Gary_Verhaegen the floatingRateDefinition (and, our novel, otherValueDefinition) are a result of the DerivedEventsWorkflow, which obtains all future events, in the form of a list, DerivedEvent(s). As a consequence of the DerivedEvent, the CreateDerivedEvent choice results in an EventInstance. Taking the EventInstance, the Lifecycling can be triggered by consuming a list of ContractInstance(s), the result of which is a new (or modified) ContractInstances and an EventNotification. Since this event requires a cash transfer, the payer will first instruct then allocate cash in order to Lifecycle the event.
Within the DerivedEventsWorkflow, novel, reset and cash events correspond to NovelDates, ResetDates and PaymentDates, which are then used to get and set the value of the floatingRateDefinition and (novel) otherValueDefinition.
We’ve had success with either using the Update expression for cps <- generateResetPeriods to set the floatingRateDefinition or cps <- generateOtherPeriods to set the otherValueDefinition, but not both. Essentially, when Update expressions are executed in a sequence, only the last expression sets its respective value which, in this instance, is the otherValueDefinition.
I think I see where my confusion was coming from:
generateResetPeriods has the following type
generateResetPeriods
: (Fetch f)
=> CalculationPeriodDates
-> Optional ResetDates
-> f [CalculationPeriod]
Note that there is no Update involved and Fetch does not have an instance for Update afaict.
I assume that generateOtherPeriods which you’ve added yourself has a similar type with only the second argument being different.
generateResetPeriods generates a list of CalculationPeriods and sets the floatingRangeDefinition field in each of them. I assume generateOtherPeriods sets otherValueDefinition which you’ve added as a field to CalculationPeriod.
My suggestion calls both idependently and then concatenates the results using ++. So you get back a list that first contains CalculationPeriods with floatingRangeDefinition set and then CalculationPeriods with otherValueDefinitionset but not both.
If you want to combine them in a way where both fields are set you need to pull apart the definition of generateResetPeriods. For the case where the argument is Some, generateResetPeriods does two things:
- First it generates the list of periods using
generateCalculationPeriods. - Afterwards it modifies the results using a combination of
generateResetPeriodDatesandsetResetDates. This is effectively amapover the result ofgenerateCalculationPeriodsso it does not change the number ofCalculationPeriods.
If you split out step 2 into a function (I’ll let you choose a better name
)
f : Fetch f => CalculationPeriod -> f [CalculationPeriod]
and do the same for your implementation of generateOtherPeriods (I assume it also first calls generateCalculationPeriods) and call the step 2 there g you can express the combined effects using
combinedPeriods cpds = do
cps <- generateCalculationPeriods cpds
map (g . f) cpds
Abstracting a bit away from your specific case, if you want to combine multiple updates, you have essentially two options: either the updates are independent, or the updates depend on each other. If they are independent, your code needs to look something like:
do
result1 <- updateFn1 some args
result2 <- updateFn2 other params
-- at this point you have both results as "pure" values with
-- no update anymore
let combined = combineFunction result1 result2
-- now you can actually use the combined result
the following, however, would not work and would result in the issue you are describing:
do
result <- updateFn1 some args
result <- updateFn2 other params
-- trying to use result here will look like only the second
-- effect was applied; the result of the first one is lost
-- because the result variable has been re-bound, shadowing
-- the result of updateFn1.
If the updates do depend on each other somehow, then you need to thread the result through:
do
result1 <- updateFn1 some args
result2 <- updateFn2 result1 other params -- note result1 is passed in
-- use result2 here, as it already represents a combined result
If you split out step 2 into a function
Am I?: (1) renaming the Some case of generateResetPeriods to f or (2) adding a new function, f, which includes generateResetPeriodDates and setResetDates
combinedPeriods cpds = do cps <- generateCalculationPeriods cpds map (g . f) cpds
Where does this gets added? Is this a nested function of f or a renamed Some case from the do block?
Thanks for this walk-through, Gary–very informative! In our application, we expect the floatingRateDefinition and otherValueDefinition to be set independently and are not dependent upon each other.
We’re working on @cocreature’s suggestion at the moment. Cheers!
Bear in mind that I know nothing about the domain nor the existing code in ex-cdm-swaps, so this may be completely off-base and should be taken with a boatload of salt.
It looks to me like, going back to your original block of code:
cps <-
case (rds, ods) of
(Some r, None) -> generateResetPeriods cpds rds
(None, Some o) -> generateOtherPeriods cpds ods
(Some r, Some o) -> do
let r = generateResetPeriods cpds rds
let o = generateOtherPeriods cpds ods
-- sequence expression to return both r and o
return ...
(None, None) -> error "expecting calculation period"
both generateResetPeriods and generateOtherPeriods are going to walk down the list of values in cpds and, for each element in there (possibly based on the values in rds or ods), generate a new element that has some properties added.
Simplifying a bit, let’s assume we have the following definitions:
data CPD = CPD { reset: Optional Int, other: Optional Int }
generateResetPeriods: [CPD] -> Optional Int -> [CPD]
generateResetPeriods cpds None = cpds
generateResetPeriods cpds (Some x) =
map (\cpd -> cpd with reset = Some x) cpds
generateOtherPeriods: [CPD] -> Optional Int -> [CPD]
generateOtherPeriods cpds None = cpds
generateOtherPeriods cpds (Some x) =
map (\cpd -> cpd with other = Some x) cpds
Obviously what the code is doing here is much more complicated and involves fetches, but if the issue is what I think it is, this should be enough to illustrate it.
This gives us the following situation:
cps <-
case (rds, ods) of
-- works as expected: all elements of cpds end up with
-- the reset field set to the given value
(Some _, None) -> generateResetPeriods cpds rds
-- works as expected: all elements of cpds end up with
-- the other field set to the given value
(None, Some _) -> generateOtherPeriods cpds ods
-- this doesn't work: applying both functions seems
-- to work well enough at first, but then, what can we
-- do with the results?
(Some _, Some _) -> do
let r = generateResetPeriods cpds rds
let o = generateOtherPeriods cpds ods
-- r and o cannot easily be combined: we have two
-- sets of results where we want one answer
return ...
(None, None) -> error "blow up"
So what to do? In this very simplified case, the input and output types are the same, so we could easily resolve the issue by changing the (Some _, Some _) case to:
let intermediateResult = generateResetPeriods cpds rds
let finalResult = generateOtherPeriods intermediateResult ods
return finalResult
But that doesn’t work in your case because the input and output types don’t match. Also, you have a Fetch instance there. First, we could get rid of the Fetch instance by turning the lets into <-. Second, we need a way to combine the results. Let’s update our example to match your case a bit better:
data Input = Input {}
data Output = Output { reset: Optional Int, other: Optional Int }
class (Action f) => Fetch f
generateResetPeriods: (Fetch f) => [Input] -> Optional Int -> f [Output]
generateResetPeriods cpds None =
return $ map (const $ Output None None) cpds
generateResetPeriods cpds (Some x) =
return $ map (const $ Output with other = None, reset = Some x) cpds
generateOtherPeriods: (Fetch f) => [Input] -> Optional Int -> f [Output]
generateOtherPeriods cpds None =
return $ map (const $ Output None None) cpds
generateOtherPeriods cpds (Some x) =
return $ map (const $ Output with other = Some x, reset = None) cpds
The (Some _, Some _) case would become:
(Some _, Some _) -> do
let r1 = generateResetPeriods cpds rds
let r2 = generateOtherPeriods cpds ods
-- r1 and r2 have the same type here: f [Output]
-- what to do about that `f`?
First, let’s take care of the Fetch issue: we would much rather deal with pure lists if possible. In this example, [Output], in your case, [CalculationPeriod]. As mentioned above, we can do that by using <- notation:
(Some _, Some _) -> do
r1 <- generateResetPeriods cpds rds
r2 <- generateOtherPeriods cpds ods
-- r1 and r2 have the same type here: [Output]
-- we still have two lists and we want only one
I can see two options here. The first one is what @cocreature was suggesting: traverse the original list only once. In the case of my simple example here, this would amount to rewriting the two generate functions to something like:
import DA.Action((>=>))
data Input = Input {}
data Output = Output { reset: Optional Int, other: Optional Int }
class (Action f) => Fetch f
commonStep: (Fetch f) => Input -> f Output
commonStep i = return $ Output with other = None, reset = None
-- note: swapped arguments for notational convenience later on
resetStep: (Fetch f) => Optional Int -> Output -> f Output
resetStep None o = return o
resetStep (Some x) o = return $ o with reset = Some x
otherStep : (Fetch f) => Optional Int -> Output -> f Output
otherStep None o = return o
otherStep (Some x) o = return $ o with other = Some x
-- By having decomposed things in this way, we can write:
f: (Fetch f) => [Input] -> Optional Int -> Optional Int -> f [Output]
f cpds rds ods = do
case (rds, ods) of
(Some _, None) -> mapA (commonStep >=> resetStep rds) cpds
(None, Some _) -> mapA (commonStep >=> otherStep ods) cpds
(Some _, Some _) ->
mapA (commonStep >=> resetStep rds >=> otherStep ods) cpds
(None, None) -> error "blow up"
This, however, means a lot of changes to the code as you need to extract a lot of logic from the guts of generateResetPeriods and generateOtherPeriods. Another option (which may or may not be applicable depending on some details of what these functions do that I don’t fully understand) is to combine the results of those functions after the fact, rather than combine their application. This can only be done if both functions do, as I believe, return lists of essentially the same things in the same order, just with some of them decorated with additional data.
In that case, the simplified example code becomes:
data Input = Input {}
data Output = Output { reset: Optional Int, other: Optional Int }
class (Action f) => Fetch f
generateResetPeriods: (Fetch f) => [Input] -> Optional Int -> f [Output]
generateResetPeriods cpds None =
return $ map (const $ Output None None) cpds
generateResetPeriods cpds (Some x) =
return $ map (const $ Output with other = None, reset = Some x) cpds
generateOtherPeriods: (Fetch f) => [Input] -> Optional Int -> f [Output]
generateOtherPeriods cpds None =
return $ map (const $ Output None None) cpds
generateOtherPeriods cpds (Some x) =
return $ map (const $ Output with other = Some x, reset = None) cpds
combine: Output -> Output -> Output
combine o1 o2 = Output with reset = o1.reset, other = o2.reset
f: (Fetch f) => [Input] -> Optional Int -> Optional Int -> f [Output]
f cpds rds ods = do
case (rds, ods) of
-- no change
(Some _, None) -> generateResetPeriods cpds rds
-- no change
(None, Some _) -> generateOtherPeriods cpds ods
(Some _, Some _) -> do
r1 <- generateResetPeriods cpds rds
r2 <- generateOtherPeriods cpds ods
-- the <- got rid of the fetches so we have two lists of results,
-- we just need to combine them by walking through both lists
-- at the same time
return $ zipWith combine r1 r2
(None, None) -> error "blow up"
Hope this helps.
Great summary @Gary_Verhaegen! You are completely correct that writing the combining function afterwards and using zipWith also works. However, I would still recommend splitting things up for a few reasons:
- I think the code is easier to understand if you separate the generation of the list from the modification afterwards.
- It’s more robust against refactoring. The
zipWithversion works as long as both generate lists of the same length. But if you modify one of them to return a longer or shorter list you will drop the additional elements from the longer list. By factoring it out, you make it much clearer that the generation should be shared and cannot end up accidentally changing it. - Writing the combining function isn’t quite as easy if you have extra fields in the type that aren’t modified by either of the functions (that seems to be the case here looking at
generateResetPeriods). You now need to make an arbitrary choice to either take the first or the second value of those fields or enforce that they are the same and error out if that’s not the case. However, I do agree that it’s probably still less work in the short term than the separation I’m proposing.
Going back to the concrete example, here’s how I would split up generateResetPeriods. For reference this is the original case (I’ve switched fom Optional ResetDates to ResetDates since we only call it with Some). To keep things simple, I’ve replaced the implementation of everything we’re not going to modify by ….
generateResetPeriods : Fetch f => CalculationPeriodDates -> ResetDates -> f [CalculationPeriod]
generateResetPeriods cpds (checkResetDates cpds -> rds) = do
-- Roll out calculation periods and unadjusted reset dates
cps <- generateCalculationPeriods cpds
let rpDatesV = map generateResetPeriodDates cps
-- Set reset and fixing dates
let adj = …
let fixingOffset = …
let resetRelativeTo =…
mapA (setResetDates resetRelativeTo fixingOffset adj) $ zip cps rpDatesV
where
-- multiple resets per period not supported yet
generateResetPeriodDates : CalculationPeriod -> [(Date, Date)]
generateResetPeriodDates cp = …
setResetDates :
(Fetch f)
=> ResetRelativeToEnum
-> RelativeDateOffset
-> BusinessDayAdjustments
-> (CalculationPeriod, [(Date, Date)])
-> f CalculationPeriod
setResetDates resetRelativeTo fixingOffset adj (cp, rpDates) = …
Now, let’s split out the modifications we do on each period generated by generateCalculationPeriods:
modifyCalculationPeriod : Fetch f => ResetDates -> CalculationPeriod -> f CalculationPeriod
modifyCalculationPeriod rds period = do
-- Set reset and fixing dates
let adj = error "unimplemented"
let fixingOffset = …
let resetRelativeTo = …
setResetDates resetRelativeTo fixingOffset adj (period, generateResetPeriodDates period)
where
-- multiple resets per period not supported yet
generateResetPeriodDates : CalculationPeriod -> [(Date, Date)]
generateResetPeriodDates cp = …
setResetDates :
(Fetch f)
=> ResetRelativeToEnum
-> RelativeDateOffset
-> BusinessDayAdjustments
-> (CalculationPeriod, [(Date, Date)])
-> f CalculationPeriod
setResetDates resetRelativeTo fixingOffset adj (cp, rpDates) = …
We can now recover generateResetPeriodDates by using this function with mapA.
generateResetPeriods : Fetch f => CalculationPeriodDates -> ResetDates -> f [CalculationPeriod]
generateResetPeriods cpds (checkResetDates cpds -> rds) = do
cps <- generateCalculationPeriods cpds
mapA (modifyCalculationPeriod rds) cps
Assuming you have done the same to generateOtherPeriods and have a modifyOtherPeriod function you can now get the combined function as follows:
generateCombinedPeriods : Fetch f => CalculationPeriodDates -> ResetDates -> OtherValue -> f [CalculationPeriod]
generateCombinedPeriods cpds (checkResetDates cpds -> rds) other = do
cps <- generateCalculationPeriods cpds
mapA combine cps
where combine period = do
period' <- modifyCalculationPeriod rds period
modifyOtherPeriod other period'
Thank you for laying this out, @cocreature! Initially, generatePaymentCalculationPeriods had Optional type for ResetDates and OtherDates.
The compiler complains in the InterestRatePayout class at:
pcps <- generatePaymentCalculationPeriods irp.calculationPeriodDates irp.resetDates irp.otherDates irp.paymentDates
The error:
/Applications/DEV/DAML/ex-cdm-swaps- copy/daml/Org/Isda/Cdm/EventSpecificationModule/Impl/Contract/Payout/InterestRatePayout.daml:43:11: error:
* Couldn't match type `Optional ResetDates' with `ResetDates'
arising from a functional dependency between:
constraint `DA.Internal.Record.HasField
"resetDates" InterestRatePayout ResetDates'
arising from a use of `DA.Internal.Record.getField'
instance `DA.Internal.Record.HasField
"resetDates" InterestRatePayout (Optional ResetDates)'
at <no location info>
* In the second argument of `generatePaymentCalculationPeriods', namely
`(DA.Internal.Record.getField @"resetDates" irp)'
In a stmt of a 'do' block:
pcps <- generatePaymentCalculationPeriods
(DA.Internal.Record.getField @"calculationPeriodDates" irp)
(DA.Internal.Record.getField @"resetDates" irp)
(DA.Internal.Record.getField @"otherDates" irp)
(DA.Internal.Record.getField @"paymentDates" irp)
In the expression:
do pcps <- generatePaymentCalculationPeriods
(DA.Internal.Record.getField @"calculationPeriodDates" irp)
(DA.Internal.Record.getField @"resetDates" irp)
(DA.Internal.Record.getField @"otherDates" irp)
(DA.Internal.Record.getField @"paymentDates" irp)
let firstPcp = getFirst "PaymentCalculationPeriod" pcps
let pastResetEvents = filter (\ e -> ...) pastEvents
let existingResets = getExistingResets firstPcp pastResetEvents irp
....compiler
There’s also a warning for the cps <-...:
/Applications/DEV/DAML/ex-cdm-swaps- copy/daml/Org/Isda/Cdm/EventSpecificationModule/Impl/Contract/Payout/InterestRatePayout.daml:46:7: warning:
Pattern match is redundant
In a case alternative: (r, o) -> ...Change your call-site from
cps <-
case (rds, ods) of
-- works as expected: all elements of cpds end up with
-- the reset field set to the given value
(Some _, None) -> generateResetPeriods cpds rds
to
cps <-
case (rds, ods) of
-- works as expected: all elements of cpds end up with
-- the reset field set to the given value
(Some rds', None) -> generateResetPeriods cpds rds'
or alternatively change generateResetPeriods to accept an Optional.
the combined function
Should the combined function be called by cps <-... in generatePaymentCalculationPeriods or it remain in the ResetDates class?
I’m getting the same error as before of floatingRateDefinition not set coming from here, existingResets, resetReferences and then floatingRateDefinition from the populatePCP function.
Could it be that the Fetch: State, Functor and Applicative need to include combined function (as both floatingRateDefinition and otherValueDefinition depend on different Reference Data)?
Hi Chris,
It’s highly unlikely you’ll need to modify the Fetch instances to achieve what you want here.
It seems your code has changed quite a bit since the last time you shared it. For example, there is no occurrence of (r, o) -> at all in the code you have shared so far. This makes helping you based on debug messages really hard.
Is there any chance you could put up your code somewhere we can access, say GitHub? Alternatively, could you share the parts that generate errors in their current form? It’s difficult to guess what’s going wrong in the new version of your code while looking at older versions.
Hi Gary,
Thanks for your note.
Currently, (r, o) -> is declared as (rds', ods') -> based on the example above.
@Gary_Verhaegen, you’ve been given access to Git. Feel free to also share with @cocreature (or others).