Skip to content

Commit 7eca85d

Browse files
authored
Add iminimum(By)Of and imaximum(By)Of (#548)
1 parent fdf750d commit 7eca85d

4 files changed

Lines changed: 168 additions & 9 deletions

File tree

optics-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Add support for converting getters and folds to and from their van Laarhoven
44
representations: `GetterVL`, `getterVL` and `toGetterVL` in `Optics.Getter`,
55
and `FoldVL`, `foldVL` and `toFoldVL` in `Optics.Fold`.
6+
* Add `iminimumOf`, `imaximumOf`, `iminimumByOf` and `imaximumByOf` to
7+
`Optics.IxFold`.
68
* **Breaking changes**:
79
- Rename the `traverse_`-like `Fold` constructor `foldVL` to `mkFold`. The new
810
`foldVL` now builds a `Fold` from its van Laarhoven representation `FoldVL`.

optics-core/src/Optics/Fold.hs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -571,9 +571,9 @@ lengthOf o = foldlOf' o (\ n _ -> 1 + n) 0
571571
-- 'maximum' ≡ 'Data.Maybe.fromMaybe' ('error' \"empty\") '.' 'maximumOf' 'folded'
572572
-- @
573573
--
574-
-- In the interest of efficiency, This operation has semantics more strict than
575-
-- strictly necessary. @\\o -> 'Data.Semigroup.getMax' . 'foldMapOf' o 'Data.Semigroup.Max'@ has lazier
576-
-- semantics but could leak memory.
574+
-- In the interest of efficiency, this operation has semantics more strict than
575+
-- strictly necessary. @\\o -> 'Data.Semigroup.getMax' . 'foldMapOf' o
576+
-- 'Data.Semigroup.Max'@ has lazier semantics but could leak memory.
577577
maximumOf :: (Is k A_Fold, Ord a) => Optic' k is s a -> s -> Maybe a
578578
maximumOf o = foldlOf' o mf Nothing where
579579
mf Nothing y = Just $! y
@@ -598,9 +598,9 @@ maximumOf o = foldlOf' o mf Nothing where
598598
-- 'minimum' ≡ 'Data.Maybe.fromMaybe' ('error' \"empty\") '.' 'minimumOf' 'folded'
599599
-- @
600600
--
601-
-- In the interest of efficiency, This operation has semantics more strict than
602-
-- strictly necessary. @\\o -> 'Data.Semigroup.getMin' . 'foldMapOf' o 'Data.Semigroup.Min'@ has lazier
603-
-- semantics but could leak memory.
601+
-- In the interest of efficiency, this operation has semantics more strict than
602+
-- strictly necessary. @\\o -> 'Data.Semigroup.getMin' . 'foldMapOf' o
603+
-- 'Data.Semigroup.Min'@ has lazier semantics but could leak memory.
604604
minimumOf :: (Is k A_Fold, Ord a) => Optic' k is s a -> s -> Maybe a
605605
minimumOf o = foldlOf' o mf Nothing where
606606
mf Nothing y = Just $! y
@@ -613,7 +613,7 @@ minimumOf o = foldlOf' o mf Nothing where
613613
-- >>> maximumByOf folded (compare `on` length) ["mustard","relish","ham"]
614614
-- Just "mustard"
615615
--
616-
-- In the interest of efficiency, This operation has semantics more strict than
616+
-- In the interest of efficiency, this operation has semantics more strict than
617617
-- strictly necessary.
618618
--
619619
-- @
@@ -629,14 +629,14 @@ maximumByOf o = \cmp ->
629629
-- | Obtain the minimum element (if any) targeted by a 'Fold' according to a
630630
-- user supplied 'Ordering'.
631631
--
632-
-- In the interest of efficiency, This operation has semantics more strict than
632+
-- In the interest of efficiency, this operation has semantics more strict than
633633
-- strictly necessary.
634634
--
635635
-- >>> minimumByOf folded (compare `on` length) ["mustard","relish","ham"]
636636
-- Just "ham"
637637
--
638638
-- @
639-
-- 'minimumBy' cmp ≡ 'Data.Maybe.fromMaybe' ('error' \"empty\") '.' 'minimumByOf' 'folded' cmp
639+
-- 'Data.Foldable.minimumBy' cmp ≡ 'Data.Maybe.fromMaybe' ('error' \"empty\") '.' 'minimumByOf' 'folded' cmp
640640
-- @
641641
minimumByOf :: Is k A_Fold => Optic' k is s a -> (a -> a -> Ordering) -> s -> Maybe a
642642
minimumByOf o = \cmp ->

optics-core/src/Optics/IxFold.hs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ module Optics.IxFold
3636
, inoneOf
3737
, ifindOf
3838
, ifindMOf
39+
, imaximumOf
40+
, iminimumOf
41+
, imaximumByOf
42+
, iminimumByOf
3943

4044
-- * Combinators
4145
, ipre
@@ -367,5 +371,156 @@ ifindMOf o = \f -> ifoldrOf o
367371
(pure Nothing)
368372
{-# INLINE ifindMOf #-}
369373

374+
-- | Obtain the maximum element (if any) targeted by an 'IxFold' along with its
375+
-- index.
376+
--
377+
-- In case of multiple maximum elements the last one is returned:
378+
--
379+
-- >>> imaximumOf ifolded "gazorpazorp"
380+
-- Just (7,'z')
381+
--
382+
-- so that the following property holds:
383+
--
384+
-- @
385+
-- 'fmap' 'snd' '.' 'imaximumOf' o ≡ 'maximumOf' o
386+
-- @
387+
--
388+
-- >>> let xs = "gazorpazorp"
389+
-- >>> fmap snd (imaximumOf ifolded xs) == maximumOf ifolded xs
390+
-- True
391+
--
392+
-- It holds even if the 'Ord' instance of the element type doesn't describe a
393+
-- total order, e.g. because of @NaN@ in floating point types:
394+
--
395+
-- >>> let ys = [2, 0/0, 1] :: [Double]
396+
-- >>> fmap snd (imaximumOf ifolded ys) == maximumOf ifolded ys
397+
-- True
398+
--
399+
-- Note: 'imaximumOf' on a valid 'Optics.IxLens.IxLens' or
400+
-- 'Optics.IxGetter.IxGetter' will always return 'Just' a value.
401+
--
402+
-- In the interest of efficiency, this operation has semantics more strict than
403+
-- strictly necessary.
404+
--
405+
-- @since 0.5
406+
imaximumOf
407+
:: (Is k A_Fold, is `HasSingleIndex` i, Ord a)
408+
=> Optic' k is s a -> s -> Maybe (i, a)
409+
imaximumOf o = ifoldlOf' o mf Nothing where
410+
-- The guard mirrors max, i.e. max x y = if x <= y then y else x.
411+
mf i acc a = case acc of
412+
Just (_, a') | not (a' <= a) -> acc
413+
_ -> Just (i, a)
414+
{-# INLINE imaximumOf #-}
415+
416+
-- | Obtain the minimum element (if any) targeted by an 'IxFold' along with its
417+
-- index.
418+
--
419+
-- In case of multiple minimum elements the first one is returned:
420+
--
421+
-- >>> iminimumOf ifolded "calamari"
422+
-- Just (1,'a')
423+
--
424+
-- so that the following property holds:
425+
--
426+
-- @
427+
-- 'fmap' 'snd' '.' 'iminimumOf' o ≡ 'minimumOf' o
428+
-- @
429+
--
430+
-- >>> let xs = "calamari"
431+
-- >>> fmap snd (iminimumOf ifolded xs) == minimumOf ifolded xs
432+
-- True
433+
--
434+
-- It holds even if the 'Ord' instance of the element type doesn't describe a
435+
-- total order, e.g. because of @NaN@ in floating point types:
436+
--
437+
-- >>> let ys = [2, 0/0, 1] :: [Double]
438+
-- >>> fmap snd (iminimumOf ifolded ys) == minimumOf ifolded ys
439+
-- True
440+
--
441+
-- Note: 'iminimumOf' on a valid 'Optics.IxLens.IxLens' or
442+
-- 'Optics.IxGetter.IxGetter' will always return 'Just' a value.
443+
--
444+
-- In the interest of efficiency, this operation has semantics more strict than
445+
-- strictly necessary.
446+
--
447+
-- @since 0.5
448+
iminimumOf
449+
:: (Is k A_Fold, is `HasSingleIndex` i, Ord a)
450+
=> Optic' k is s a -> s -> Maybe (i, a)
451+
iminimumOf o = ifoldlOf' o mf Nothing where
452+
-- The guard mirrors min, i.e. min x y = if x <= y then x else y.
453+
mf i acc a = case acc of
454+
Just (_, a') | a' <= a -> acc
455+
_ -> Just (i, a)
456+
{-# INLINE iminimumOf #-}
457+
458+
-- | Obtain the maximum element (if any) targeted by an 'IxFold' according to a
459+
-- user supplied 'Ordering' along with its index.
460+
--
461+
-- In case of multiple maximum elements the last one is returned:
462+
--
463+
-- >>> imaximumByOf ifolded (compare `on` length) ["hey","you","ham"]
464+
-- Just (2,"ham")
465+
--
466+
-- so that the following property holds:
467+
--
468+
-- @
469+
-- 'fmap' 'snd' '.' 'imaximumByOf' o cmp ≡ 'maximumByOf' o cmp
470+
-- @
471+
--
472+
-- >>> let xs = ["hey","you","ham"]
473+
-- >>> let cmp = compare `on` length
474+
-- >>> fmap snd (imaximumByOf ifolded cmp xs) == maximumByOf ifolded cmp xs
475+
-- True
476+
--
477+
-- In the interest of efficiency, this operation has semantics more strict than
478+
-- strictly necessary.
479+
--
480+
-- @since 0.5
481+
imaximumByOf
482+
:: (Is k A_Fold, is `HasSingleIndex` i)
483+
=> Optic' k is s a -> (a -> a -> Ordering) -> s -> Maybe (i, a)
484+
imaximumByOf o = \cmp ->
485+
let mf i acc a = case acc of
486+
Just (_, a') | cmp a' a == GT -> acc
487+
_ -> Just (i, a)
488+
in ifoldlOf' o mf Nothing
489+
{-# INLINE imaximumByOf #-}
490+
491+
-- | Obtain the minimum element (if any) targeted by an 'IxFold' according to a
492+
-- user supplied 'Ordering' along with its index.
493+
--
494+
-- In case of multiple minimum elements the first one is returned:
495+
--
496+
-- >>> iminimumByOf ifolded (compare `on` length) ["hey","you","ham"]
497+
-- Just (0,"hey")
498+
--
499+
-- so that the following property holds:
500+
--
501+
-- @
502+
-- 'fmap' 'snd' '.' 'iminimumByOf' o cmp ≡ 'minimumByOf' o cmp
503+
-- @
504+
--
505+
-- >>> let xs = ["hey","you","ham"]
506+
-- >>> let cmp = compare `on` length
507+
-- >>> fmap snd (iminimumByOf ifolded cmp xs) == minimumByOf ifolded cmp xs
508+
-- True
509+
--
510+
-- In the interest of efficiency, this operation has semantics more strict than
511+
-- strictly necessary.
512+
--
513+
-- @since 0.5
514+
iminimumByOf
515+
:: (Is k A_Fold, is `HasSingleIndex` i)
516+
=> Optic' k is s a -> (a -> a -> Ordering) -> s -> Maybe (i, a)
517+
iminimumByOf o = \cmp ->
518+
let mf i acc a = case acc of
519+
Just (_, a') | cmp a' a /= GT -> acc
520+
_ -> Just (i, a)
521+
in ifoldlOf' o mf Nothing
522+
{-# INLINE iminimumByOf #-}
523+
370524
-- $setup
371525
-- >>> import Optics.Core
526+
-- >>> import Data.Function (on)

optics/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Add support for converting getters and folds to and from their van Laarhoven
44
representations: `GetterVL`, `getterVL` and `toGetterVL` in `Optics.Getter`,
55
and `FoldVL`, `foldVL` and `toFoldVL` in `Optics.Fold`.
6+
* Add `iminimumOf`, `imaximumOf`, `iminimumByOf` and `imaximumByOf` to
7+
`Optics.IxFold`.
68
* **Breaking changes**:
79
- Rename the `traverse_`-like `Fold` constructor `foldVL` to `mkFold`. The new
810
`foldVL` now builds a `Fold` from its van Laarhoven representation `FoldVL`.

0 commit comments

Comments
 (0)