packages feed

linear-base 0.5.0 → 0.6.0

raw patch · 4 files changed

+78/−12 lines, 4 files

Files

CHANGELOG.md view
@@ -1,5 +1,13 @@ # Change Log +## [v0.6.0](https://github.com/tweag/linear-base/tree/v0.6.0) (2025-11-18)++[Full Changelog](https://github.com/tweag/linear-base/compare/v0.5.0...v0.6.0)++### Headline changes++- Make List.zipWith as lazy as expected [\#492](https://github.com/tweag/linear-base/pull/492) ([aspiwack](https://github.com/aspiwack))+ ## [v0.5.0](https://github.com/tweag/linear-base/tree/v0.5.0) (2025-04-07)  [Full Changelog](https://github.com/tweag/linear-base/compare/v0.4.0...v0.5.0)
linear-base.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               linear-base-version:            0.5.0+version:            0.6.0 license:            MIT license-file:       LICENSE copyright:          (c) Tweag Holding and affiliates
src/Data/List/Linear.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE LinearTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE NoImplicitPrelude #-}@@ -80,6 +80,7 @@     zip3,     zipWith,     zipWith',+    zipFold,     zipWith3,     unzip,     unzip3,@@ -354,18 +355,53 @@ zip3 = zipWith3 (,,)  zipWith :: (Consumable a, Consumable b) => (a %1 -> b %1 -> c) -> [a] %1 -> [b] %1 -> [c]-zipWith f xs ys =-  zipWith' f xs ys & \(ret, leftovers) ->-    leftovers `lseq` ret+zipWith f =+  zipFold (\a b cs -> f a b : cs) [] consume2 consume2+  where+    consume2 :: forall x y z. (Consumable x, Consumable y) => x %1 -> y %1 -> [z]+    consume2 x y = x `lseq` y `lseq` []  -- | Same as 'zipWith', but returns the leftovers instead of consuming them.+-- Because the leftovers are returned at toplevel, @zipWith'@ is pretty strict:+-- forcing the first cons cell of the returned list forces all the recursive+-- calls. zipWith' :: (a %1 -> b %1 -> c) -> [a] %1 -> [b] %1 -> ([c], Maybe (Either (NonEmpty a) (NonEmpty b)))-zipWith' _ [] [] = ([], Nothing)-zipWith' _ (a : as) [] = ([], Just (Left (a :| as)))-zipWith' _ [] (b : bs) = ([], Just (Right (b :| bs)))-zipWith' f (a : as) (b : bs) =-  case zipWith' f as bs of-    (cs, rest) -> (f a b : cs, rest)+zipWith' f =+  zipFold+    (\a b !(cs, rest) -> ((f a b : cs), rest))+    ([], Nothing)+    (\a as -> ([], Just (Left (a :| as))))+    (\b bs -> ([], Just (Right (b :| bs))))++-- | A function which combines zipping and 'foldr'. It's more general than all+-- the zip-family functions ('zip', 'zip'', 'zipWith', 'zipWith'').+--+-- If @k < n@, then+--+-- * @'zipFold' cons nil lefta leftb [a₁, a₂, …, aₙ] [b₁, b₂, …, bₖ] = cons a₁ b₁ (cons a₂ b₂ (… (cons aₖ bₖ (lefta aₖ₊₁ [aₖ₊₂, …, aₙ]))))@+-- * @'zipFold' cons nil lefta leftb [a₁, a₂, …, aₖ] [b₁, b₂, …, bₙ] = cons a₁ b₁ (cons a₂ b₂ (… (cons aₖ bₖ (leftb bₖ₊₁ [bₖ₊₂, …, bₙ]))))@+-- * @'zipFold' cons nil lefta leftb [a₁, a₂, …, aₖ] [b₁, b₂, …, bₖ] = cons a₁ b₁ (cons a₂ b₂ (… (cons aₖ bₖ nil)))@+zipFold ::+  forall r a b.+  -- | Combines elements at the same index+  (a %1 -> b %1 -> r %1 -> r) ->+  -- | Starting value if both lists have the same length+  r ->+  -- | Starting value if the first list is longer+  (a %1 -> [a] %1 -> r) ->+  -- | Starting value if the second list is longer+  (b %1 -> [b] %1 -> r) ->+  [a] %1 ->+  [b] %1 ->+  r+zipFold cons nil lefta leftb =+  go+  where+    go :: [a] %1 -> [b] %1 -> r+    go [] [] = nil+    go (a : as) [] = lefta a as+    go [] (b : bs) = leftb b bs+    go (a : as) (b : bs) = cons a b (go as bs)  zipWith3 :: forall a b c d. (Consumable a, Consumable b, Consumable c) => (a %1 -> b %1 -> c %1 -> d) -> [a] %1 -> [b] %1 -> [c] %1 -> [d] zipWith3 _ [] ys zs = (ys, zs) `lseq` []
test/Test/Data/List.hs view
@@ -4,6 +4,7 @@ module Test.Data.List (listTests) where  import qualified Data.List.Linear as List+import qualified Data.Num.Linear as Num import Hedgehog import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range@@ -17,7 +18,9 @@   testGroup     "List tests"     [ testPropertyNamed "take n ++ drop n = id" "take_drop" take_drop,-      testPropertyNamed "length . take n = const n" "take_length" take_length+      testPropertyNamed "length . take n = const n" "take_length" take_length,+      testPropertyNamed "zipWith is lazy" "zipWith_lazy" zipWith_lazy,+      testPropertyNamed "zipWith3 is lazy" "zipWith3_lazy" zipWith3_lazy     ]  take_drop :: Property@@ -41,3 +44,22 @@     False -> do       annotate "Prelude.length xs < n"       Prelude.length (List.take n xs) === Prelude.length xs++zipWith_lazy :: Property+zipWith_lazy = property $ do+  lgth <- forAll $ Gen.word (Range.linear 0 50)+  _ <- eval $ Prelude.head (xs lgth)+  Prelude.return ()+  where+    xs :: Word -> [Word]+    xs lgth = List.zipWith (Num.+) (0 : error "bottom") [0 .. lgth]++zipWith3_lazy :: Property+zipWith3_lazy = property $ do+  lgth1 <- forAll $ Gen.word (Range.linear 0 50)+  lgth2 <- forAll $ Gen.word (Range.linear 0 50)+  _ <- eval $ Prelude.head (xs lgth1 lgth2)+  Prelude.return ()+  where+    xs :: Word -> Word -> [Word]+    xs lgth1 lgth2 = List.zipWith3 (\x y z -> x Num.+ y Num.+ z) (0 : error "bottom") [0 .. lgth1] [0 .. lgth2]