Make DA.List.Total return Optional instead (#11878)

* Make DA.List.Total return Optional instead.

Instead of using ActionFail or CanAbort, return an Optional. The library
user can then convert this into the appropriate error if they wish.

This PR also removes DA.Optional.Total (and the deprecated DA.Maybe.Total)
for having questionable utility.

changelog_begin

- [Daml Standard Library] DA.List.Total functions now return Optional
  instead of being polymorphic in the return type. DA.Optional.Total
  has been removed.

changelog_end

* update Examples.daml
This commit is contained in:
Sofia Faro 2021-11-25 14:33:48 +00:00 committed by GitHub
parent df373466dc
commit 0852c8f6fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 88 additions and 126 deletions

View File

@ -23,66 +23,86 @@ import DA.List hiding (foldBalanced1, head, tail, init, last, foldl1, foldr1,
import qualified DA.List
head : ActionFail m => [a] -> m a
head (x::_) = pure x
head [] = fail "head: empty list"
-- | Return the first element of a list. Return `None` if list is empty.
head : [a] -> Optional a
head = \case
(x :: _) -> Some x
[] -> None
tail : ActionFail m => [a] -> m [a]
tail (_::xs) = pure xs
tail [] = fail "tail: empty list"
-- | Return all but the first element of a list. Return `None` if list is empty.
tail : [a] -> Optional [a]
tail = \case
(_ :: xs) -> Some xs
[] -> None
last : ActionFail m => [a] -> m a
-- | Extract the last element of a list. Returns `None` if list is empty.
last : [a] -> Optional a
last = foldl1 (flip const)
init : ActionFail m => [a] -> m [a]
init [_] = pure []
init (x::xs) = do i <- init xs; pure (x :: i)
init [] = fail "init: empty list"
-- | Return all the elements of a list except the last one. Returns `None` if list is empty.
init : [a] -> Optional [a]
init = \case
[] -> None
[_] -> Some []
(x :: xs) -> do
xs' <- init xs
Some (x :: xs')
infixl 9 !!
(!!) : ActionFail m => [a] -> Int -> m a
_ !! i | i < 0 = fail "(!!): negative index"
[] !! _ = fail "(!!): index too large"
(x::_) !! 0 = pure x
-- | Return the nth element of a list. Return `None` if index is out of bounds.
(!!) : [a] -> Int -> Optional a
_ !! i | i < 0 = None
[] !! _ = None
(x::_) !! 0 = Some x
(_::xs) !! i = xs !! (i-1)
foldl1 : ActionFail m => (a -> a -> a) -> [a] -> m a
foldl1 f (x::xs) = pure (foldl f x xs)
foldl1 _ [] = fail "foldl1: empty list"
-- | Fold left starting with the head of the list.
-- For example, `foldl1 f [a,b,c] = f (f a b) c`.
-- Return `None` if list is empty.
foldl1 : (a -> a -> a) -> [a] -> Optional a
foldl1 f (x::xs) = Some (foldl f x xs)
foldl1 _ [] = None
foldr1 : ActionFail m => (a -> a -> a) -> [a] -> m a
foldr1 f [] = fail "foldr1: empty list"
-- | Fold right starting with the last element of the list.
-- For example, `foldr1 f [a,b,c] = f a (f b c)`
foldr1 : (a -> a -> a) -> [a] -> Optional a
foldr1 f [] = None
foldr1 f xs = foldl1 (flip f) (reverse xs)
foldBalanced1 : ActionFail m => (a -> a -> a) -> [a] -> m a
foldBalanced1 _ [] = fail "foldBalanced1: empty list"
foldBalanced1 _ [x] = pure x
foldBalanced1 f xs = foldBalanced1 f (combinePairs f xs)
-- | Fold a non-empty list in a balanced way. Balanced means that each
-- element has approximately the same depth in the operator
-- tree. Approximately the same depth means that the difference
-- between maximum and minimum depth is at most 1. The accumulation
-- operation must be associative and commutative in order to get the
-- same result as `foldl1` or `foldr1`.
--
-- Return `None` if list is empty.
foldBalanced1 : (a -> a -> a) -> [a] -> Optional a
foldBalanced1 f [] = None
foldBalanced1 f xs = Some (DA.List.foldBalanced1 f xs)
-- | `minimumBy f xs` returns the first element `x` of `xs` for which `f x y`
-- is either `LT` or `EQ` for all other `y` in `xs`. The result is
-- wrapped in a monadic context, with a failure if `xs` is empty.
minimumBy : (ActionFail m) => (a -> a -> Ordering) -> [a] -> m a
minimumBy _ [] = fail "minimumBy: empty list"
minimumBy f xs = pure $ DA.List.minimumBy f xs
-- | Return the least element of a list according to the given comparison function.
-- Return `None` if list is empty.
minimumBy : (a -> a -> Ordering) -> [a] -> Optional a
minimumBy _ [] = None
minimumBy f xs = Some (DA.List.minimumBy f xs)
-- | `maximumBy f xs` returns the first element `x` of `xs` for which `f x y`
-- is either `GT` or `EQ` for all other `y` in `xs`. The result is
-- wrapped in a monadic context, with a failure if `xs` is empty.
maximumBy : (ActionFail m) => (a -> a -> Ordering) -> [a] -> m a
maximumBy _ [] = fail "maximumBy: empty list"
maximumBy f xs = pure $ DA.List.maximumBy f xs
-- | Return the greatest element of a list according to the given comparison function.
-- Return `None` if list is empty.
maximumBy : (a -> a -> Ordering) -> [a] -> Optional a
maximumBy _ [] = None
maximumBy f xs = Some (DA.List.maximumBy f xs)
-- | `minimumOn f xs` returns the first element `x` of `xs` for which `f x`
-- is smaller than or equal to any other `f y` for `y` in `xs`. The result is
-- wrapped in a monadic context, with a failure if `xs` is empty.
minimumOn : (ActionFail m, Ord k) => (a -> k) -> [a] -> m a
minimumOn _ [] = fail "minimumOn: empty list"
minimumOn f xs = pure $ DA.List.minimumOn f xs
-- | Return the least element of a list when comparing by a key function.
-- For example `minimumOn (\(x,y) -> x + y) [(1,2), (2,0)] == Some (2,0)`.
-- Return `None` if list is empty.
minimumOn : (Ord k) => (a -> k) -> [a] -> Optional a
minimumOn _ [] = None
minimumOn f xs = Some (DA.List.minimumOn f xs)
-- | `maximumOn f xs` returns the first element `x` of `xs` for which `f x`
-- is greater than or equal to any other `f y` for `y` in `xs`. The result is
-- wrapped in a monadic context, with a failure if `xs` is empty.
maximumOn : (ActionFail m, Ord k) => (a -> k) -> [a] -> m a
maximumOn _ [] = fail "maximumOn: empty list"
maximumOn f xs = pure $ DA.List.maximumOn f xs
-- | Return the greatest element of a list when comparing by a key function.
-- For example `maximumOn (\(x,y) -> x + y) [(1,2), (2,0)] == Some (1,2)`.
-- Return `None` if list is empty.
maximumOn : (Ord k) => (a -> k) -> [a] -> Optional a
maximumOn _ [] = None
maximumOn f xs = Some (DA.List.maximumOn f xs)

View File

@ -1,20 +0,0 @@
-- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
-- | HIDE Hidden because deprecated.
module DA.Maybe.Total
( module DA.Maybe
, module DA.Maybe.Total
)
where
import DA.Maybe hiding (fromJust, fromJustNote)
import DA.Optional.Total
{-# DEPRECATED fromJust "Daml 1.2 compatibility helper, use 'fromSome' from 'DA.Optional.Total' instead of 'fromJust'" #-}
fromJust : ActionFail m => Optional a -> m a
fromJust = fromSome
{-# DEPRECATED fromJustNote "Daml 1.2 compatibility helper, use 'fromSomeNote' from 'DA.Optional.Total' instead of 'fromJustNote'" #-}
fromJustNote : ActionFail m => Text -> Optional a -> m a
fromJustNote = fromSomeNote

View File

@ -1,17 +0,0 @@
-- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module DA.Optional.Total
( module DA.Optional
, module DA.Optional.Total
)
where
import DA.Optional hiding (fromSome, fromSomeNote)
fromSome : ActionFail m => Optional a -> m a
fromSome = fromSomeNote "fromSome: None"
fromSomeNote : ActionFail m => Text -> Optional a -> m a
fromSomeNote _ (Some x) = pure x
fromSomeNote n None = fail n

View File

@ -37,12 +37,10 @@ import DA.List.BuiltinOrder
import DA.Logic
import DA.Map
import DA.Math
import DA.Maybe.Total
import DA.Maybe
import DA.Monoid
import DA.NonEmpty
import DA.Numeric
import DA.Optional.Total
import DA.Optional
import DA.Random
import DA.Record

View File

@ -40,6 +40,5 @@ import Optional()
-- import NextSet()
import List()
import Either()
import Optional_Total()
import Tuple()
import Self2()

View File

@ -6,46 +6,49 @@ module List_Total where
import DA.List.Total
import DA.Assert
intNil : [Int]
intNil = []
testHead = scenario do
Left "head: empty list" === head @(Either Text) @Int []
None === head intNil
Some 1 === head [1, 2, 3]
testTail = scenario do
Left "tail: empty list" === tail @(Either Text) @Int []
None === tail intNil
Some [2, 3] === tail [1, 2, 3]
testInit = scenario do
Left "init: empty list" === init @(Either Text) @Int []
None === init intNil
Some [1, 2] === init [1, 2, 3]
testDoubleBang = scenario do
Left "(!!): index too large" === [1, 2, 3] !! 4
None === [1, 2, 3] !! 4
Some 2 === [1, 2, 3] !! 1
testFoldl1 = scenario do
Left "foldl1: empty list" === foldl1 @(Either Text) @Int (-) []
None === foldl1 (-) intNil
Some (-4) === foldl1 (-) [1, 2, 3]
testFoldr1 = scenario do
Left "foldr1: empty list" === foldr1 @(Either Text) @Int (-) []
None === foldr1 (-) intNil
Some 2 === foldr1 (-) [1, 2, 3]
testFoldBalanced1 = scenario do
Left "foldBalanced1: empty list" === foldBalanced1 @(Either Text) @Int (+) []
None === foldBalanced1 (+) intNil
Some 6 === foldBalanced1 (+) [1, 2, 3]
testMinimumOn = scenario do
Left "minimumOn: empty list" === minimumOn @(Either Text) @Int @Int negate []
None === minimumOn negate intNil
Some 3 === minimumOn negate [1, 2, 3]
testMaximumOn = scenario do
Left "maximumOn: empty list" === maximumOn @(Either Text) @Int @Int negate []
None === maximumOn negate intNil
Some 1 === maximumOn negate [1, 2, 3]
testBy = scenario do
Left "minimumBy: empty list" === minimumBy @(Either Text) @Int compare []
Left "maximumBy: empty list" === maximumBy @(Either Text) @Int compare []
let cmp = (\x y -> compare (fst x) (fst y))
let ls = [(2, 3), (4, 1), (1, 4)]
Right (1, 4) === minimumBy @(Either Text) cmp ls
None === minimumBy @(Int,Int) cmp []
None === maximumBy @(Int,Int) cmp []
Some (1, 4) === minimumBy cmp ls
Some (4, 1) === maximumBy cmp ls

View File

@ -1,16 +1,15 @@
-- Copyright (c) 2020, Digital Asset (Switzerland) GmbH and/or its affiliates.
-- All rights reserved.
-- @WARN range=15:12-15:17; Maybe
-- @WARN range=18:3-18:8; maybe
-- @WARN range=18:20-18:27; Nothing
-- @WARN range=19:3-19:11; fromSome
-- @WARN range=19:13-19:17; Just
-- @WARN range=14:12-14:17; Maybe
-- @WARN range=17:3-17:8; maybe
-- @WARN range=17:20-17:27; Nothing
-- @WARN range=18:3-18:11; fromSome
-- @WARN range=18:13-18:17; Just
module MaybeCompat where
import DA.Assert
import DA.Maybe
import DA.Maybe.Total () -- we want to make sure there are not warnings in this module
type Foo = Maybe Int

View File

@ -1,20 +0,0 @@
-- Copyright (c) 2020, Digital Asset (Switzerland) GmbH and/or its affiliates.
-- All rights reserved.
module Optional_Total where
import DA.Optional.Total
import DA.Assert
testFromSome = scenario do
v <- fromSome (Some "abc")
v === "abc"
(Right 42 : Either Text Int) === fromSome (Some 42)
(Left "fromSome: None" : Either Text Int) === fromSome None
testFromSomeNote = scenario do
v <- fromSomeNote "error" (Some "abc")
v === "abc"
(Right 42 : Either Text Int) === fromSomeNote "problem" (Some 42)
(Left "problem" : Either Text Int) === fromSomeNote "problem" None