cooked-validators
Safe HaskellSafe-Inferred
LanguageHaskell2010

Cooked.Tweak.Modify

Description

This module exposes involved tweaks to selectively and systematically modify elements of the same nature within a TxSkel

Synopsis

Modification parameters

data Branching Source #

How to combine the branches generated when several foci are eligible for modification in the same skeleton. See modifyTweak.

Constructors

OneBranchForAllFoci

Modify all eligible foci together, yielding a single modified skeleton.

OneBranchPerFoci

Modify exactly one eligible focus per branch.

OneBranchPerSubset

Modify every non-empty subset of the eligible foci, yielding one branch per subset (the power set, minus the empty set).

Manual (forall is. [is] -> [[is]])

Create a new branch for all the subsets computed by the given function applied on the focused indexes.

data ModifyTweakParams k k' is is' f a b c where Source #

The set of parameters piloting the modification tweak

Constructors

ModifyTweakParams 

Fields

  • :: { branching :: Branching

    The branching policy to apply when several foci are targeted

  •    , outerOptic :: Optic' k is TxSkel a

    A type-preserving optic traversing the TxSkel and pinpointing a first layer of elements. Being type-preserving, it only chooses where to act.

  •    , innerOptic :: Optic k' is' a a b c

    A second, type-changing AffineTraversal reaching from within each selected element to the inner focus that is actually modified. It carries the b -> c type change that the outer optic cannot, and its affine-ness (0 or 1 focus) acts as an additional selection layer.

  •    , modification :: b -> f c

    The modifying function, which can possibly fail through f, further selecting elements to modify.

  •    , selection :: Int -> Bool

    A selection function based on the indexes of the selected foci. This is the last layer of selection, if all the others are insufficient.

  •    } -> ModifyTweakParams k k' is is' f a b c
     

modifyTweakParamsAllIndexes :: Branching -> Optic' k is TxSkel a -> Optic k' is' a a b c -> (b -> f c) -> ModifyTweakParams k k' is is' f a b c Source #

A standard ModifyTweakParams without the index filtering

modifyTweakParamsNoTypeChange :: Branching -> Optic' k is TxSkel a -> (a -> f a) -> ModifyTweakParams k An_Iso is NoIx f a a a Source #

A standard ModifyTweakParams without any index filtering or type changing inner optic

modifyTweakParamsOneBranchForAllFoci :: Optic' k is TxSkel a -> (a -> f a) -> ModifyTweakParams k An_Iso is NoIx f a a a Source #

A standard ModifyTweakParams without any index filtering or type changing inner optic, modifying all foci in the same transaction.

modifyTweakParamsOneBranchPerFoci :: Optic' k is TxSkel a -> (a -> f a) -> ModifyTweakParams k An_Iso is NoIx f a a a Source #

A standard ModifyTweakParams without any index filtering or type changing inner optic, branching on each focus.

modifyTweakParamsOneBranchPerSubset :: Optic' k is TxSkel a -> (a -> f a) -> ModifyTweakParams k An_Iso is NoIx f a a a Source #

A standard ModifyTweakParams without any index filtering or type changing inner optic, branching on each subset of foci.

Modifying tweaks

modifyTweak Source #

Arguments

:: (Ord is, Is k A_Traversal, Members '[Tweak, NonDet] effs) 
=> ([is] -> [[is]])

Function that explains which subsets of targeted indexes will be simultaneously subject to being transformed. If you want to transform all foci in a single transaction (assuming the transformation itself does not branch), use (: []). On the other end of the spectrum, if you want each focus to be transformed separately in their own transaction, use fmap (: []). Everything in between is of course possible.

-> Optic' k (WithIx is) TxSkel x

Optic targeting the various foci which should be subject to being transformed. This optic can be built manually, but can also be enlarged using convenience functions such as elementsOf.

-> (is -> x -> Sem effs (x, l))

Function that describes how the foci and their indexes can be transformed within the structure. Bear in mind that effs contains NonDet so this transformation can already branch.

-> Sem effs [l]

Returns the list of all foci modified in the transaction, as they were before the modification was applied, represented by their label. In most cases, the label will be the element itself, but other use cases are allowed as the label is arbitrary data.

When constructing a tweak from an optic and a modification of foci, there are in principle two options for optics with many foci: (a) apply the modification to all foci and return one modified transaction (b) generate a number of transactions that contain different combinations of modified and un-modified foci.

This function is the most general building block for both strategies: its first argument selects, per transaction, which foci are modified together, so it can realise strategy (a), strategy (b), or anything in between. The overMods... helpers defined below specialise it to common cases. The meaning of each argument and of the return value is documented on the parameters themselves below.

Shared setup for the examples

Assume the optic has three foci, which we denote by a, b, c :: x, with indices 1, 2, 3 :: Integer respectively.

Example 1: modify every focus in a single transaction

Choosing (: []) for the [is] -> [[is]] argument yields the single grouping [[1, 2, 3]], so all foci are modified together. Assuming the modification does not itself branch (changes returns exactly one result per focus), this produces exactly one modified transaction, in which a, b, and c are all modified. This is the grouping used by the OneBranchForAllFoci Branching of modifyTweak.

Example 2: one modification per transaction

Now additionally assume that changes, of type is -> x -> Sem effs (x, l), branches into 2, 3, and 5 results on a, b, and c respectively; call those a1, a2 and b1, b2, b3 and c1, c2, c3, c4, c5. Choosing map (: []) for the [is] -> [[is]] argument tries every modification on a separate transaction, since

map (: []) [1, 2, 3] = [[1], [2], [3]]  .

Thus there will be 2 + 3 + 5 = 10 modified transactions: for each element of

[a1, a2, b1, b2, b3, c1, c2, c3, c4, c5]

you get one modified transaction that includes that value in place of the original focus. This is the grouping used by the OneBranchPerFoci Branching of modifyTweak.

Example 3: all combinations of modifications

In the same setting, if you want to combine all possible modifications of one focus with all possible modifications of the other foci, choose tail . subsequences for the [is] -> [[is]] argument. This is the grouping used by the OneBranchPerSubset Branching of modifyTweak. We have

tail (subsequences [1, 2, 3])
  == [ [1], [2], [3],
       [1, 2], [1, 3], [2, 3],
       [1, 2, 3]
     ]

This corresponds to the following 71 modified transactions, represented by the list of modified foci they contain:

[ -- one modified focus (the 10 cases from Example 2)
  [a1],
  [a2],
  ...
  [c4],
  [c5],

  -- two modifications of different foci (2*3 + 2*5 + 3*5 = 31 cases)
  [a1, b1],
  [a1, b2],
  ...
  [b3, c4],
  [b3, c5],

  -- three modified foci, one from each focus (2*3*5 = 30 cases)
  [a1, b1, c1],
  [a1, b1, c2],
  ...
  [a1, b3, c4],
  [a1, b3, c5]
]

So you see that tweaks constructed like this can branch quite wildly. Use with caution!

Note that if changes branches to no result for a targeted focus (one whose index occurs in a grouping), that entire grouping branch is dropped, since there is no possible value to put in place of that focus.

modifyTweakFromParams Source #

Arguments

:: (Members '[Tweak, NonDet] effs, Is k A_Traversal, Is k' An_AffineTraversal, Foldable f, Alternative f) 
=> ModifyTweakParams k k' is is' f a b c

The parameters piloting the modifications

-> Sem effs [b]

Returns the list of inner foci (as they were before modification) that were modified.

The most convenient and expressive way to build a focusing-and-modifying Tweak. It targets foci in a TxSkel through two optics and applies a (possibly type-changing, possibly failing) modification to them, branching over the eligible foci according to the requested Branching strategy.

Why two optics?

The underlying engine (modifyTweak) can only apply type-preserving modifications: each modified focus is written back into the TxSkel in place, so the skeleton's overall shape is fixed and the outer optic must be an Optic' ... TxSkel a. That optic can therefore only decide where in the skeleton to act; it cannot express a change of type.

The modification we actually want to perform is finer-grained and type-changing: within each selected element a, an inner part of type b is replaced by a value of type c. This type change cannot ride on the outer, type-preserving optic, which is exactly why a second optic is needed. opticIn localises and carries the type change inside each selected element, and traverseOf folds it back into a type-preserving operation a -> f a, so that the outer traversal stays type-preserving while the inner focus still changes from b to c.

The second optic serves a second purpose: being an AffineTraversal (zero or one focus), it doubles as an extra selection layer. We guard on matching, so an element is only eligible when its inner focus actually exists there. Together with the failure allowed by f in change and the index predicate select, this gives several independent layers of selection: outer optic, inner-optic existence, modification success, and index.

Helper to build optics

selectP :: (a -> Bool) -> Prism' a a Source #

A modification that can fail is sometimes best expressed by explicitly stating which property the foci should satisfy to be eligible for a modification that cannot fail. selectP provides a prism to make such a selection. The intended use case is overTweak (optic % selectP prop) mod where optic gives the candidate foci, prop is the predicate to be satisfied by the foci, and mod is the modification to be applied to the selected foci.

Note that selectP is not a lawful prism: its build side is id, so reviewing a value that does not satisfy prop breaks the preview o (review o b) ≡ Just b law. We nevertheless keep it as a Prism' (rather than the lawful but read-only filtered, which is an AffineFold) because we need the write capability: composing a traversal with a prism stays a traversal, whereas composing it with an AffineFold collapses to a read-only fold. This is safe in the intended overTweak (optic % selectP prop) mod pattern, where the unlawful build side is never exercised: we only ever reach foci that already satisfy prop.