From 9a624f197fb187f066b5807f00b908a3e86eba31 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Sun, 3 May 2026 10:24:08 +0530 Subject: [PATCH 01/10] Fix code for the internal-dev flag to work --- src/Streamly/Internal/Data/SmallArray.hs | 2 +- src/Streamly/Internal/FileSystem/FD.hs | 18 +++++------ src/Streamly/Internal/System/IOVec.hs | 35 ++++++++------------- src/Streamly/Internal/System/IOVec/Type.hsc | 14 +++++++++ 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/Streamly/Internal/Data/SmallArray.hs b/src/Streamly/Internal/Data/SmallArray.hs index c351860594..ddb5d11fe6 100644 --- a/src/Streamly/Internal/Data/SmallArray.hs +++ b/src/Streamly/Internal/Data/SmallArray.hs @@ -41,7 +41,7 @@ module Streamly.Internal.Data.SmallArray ) where -import Prelude hiding (foldr, length, read) +import Prelude hiding (foldl', foldr, length, read) import Control.DeepSeq (NFData(..)) import Control.Monad (when) import Control.Monad.IO.Class (MonadIO, liftIO) diff --git a/src/Streamly/Internal/FileSystem/FD.hs b/src/Streamly/Internal/FileSystem/FD.hs index 1da8c004dc..21970a0844 100644 --- a/src/Streamly/Internal/FileSystem/FD.hs +++ b/src/Streamly/Internal/FileSystem/FD.hs @@ -125,21 +125,19 @@ import System.IO (IOMode) import Prelude hiding (read) import qualified GHC.IO.FD as FD -import qualified GHC.IO.Device as RawIO +import qualified GHC.IO.Device as GHCDevice import Streamly.Data.Array (Array, Unbox) import Streamly.Data.Stream (Stream) -import Streamly.Internal.Data.Array (byteLength, unsafeFreeze, unsafePinnedAsPtr) +import Streamly.Internal.Data.Array (unsafeFreeze, unsafePinnedAsPtr) import Streamly.Internal.System.IO (defaultChunkSize) #if !defined(mingw32_HOST_OS) -{- import Streamly.Internal.Data.Stream.IsStream.Type (toStreamD) import Streamly.Internal.System.IOVec (groupIOVecsOf) -import qualified Streamly.Internal.FileSystem.FDIO as RawIO hiding (write) +import qualified Streamly.Internal.FileSystem.FDIO as RawIO import qualified Streamly.Internal.System.IOVec.Type as RawIO --} #endif -- import Streamly.Data.Fold (Fold) -- import Streamly.String (encodeUtf8, decodeUtf8, foldLines) @@ -147,7 +145,7 @@ import qualified Streamly.Internal.System.IOVec.Type as RawIO import qualified Streamly.Data.Array as A import qualified Streamly.Data.Fold as FL import qualified Streamly.Internal.Data.MutArray as MArray - (MutArray(..), unsafePinnedAsPtr, pinnedNewBytes) + (MutArray(..), unsafePinnedAsPtr, pinnedNewBytes, unsafeCreateWithPtr') import qualified Streamly.Internal.Data.Array.Stream as AS import qualified Streamly.Internal.Data.Stream as S import qualified Streamly.Internal.Data.Stream as D @@ -222,7 +220,7 @@ readArrayUpto size (Handle fd) = do MArray.unsafeCreateWithPtr' size $ \p -> -- n <- hGetBufSome h p size #if MIN_VERSION_base(4,15,0) - RawIO.read fd p 0 size + GHCDevice.read fd p 0 size #else RawIO.read fd p size #endif @@ -241,10 +239,10 @@ readArrayUpto size (Handle fd) = do writeArray :: Unbox a => Handle -> Array a -> IO () writeArray _ arr | A.length arr == 0 = return () writeArray (Handle fd) arr = - unsafePinnedAsPtr arr $ \p -> + unsafePinnedAsPtr arr $ \p aLen -> -- RawIO.writeAll fd (castPtr p) aLen #if MIN_VERSION_base(4,15,0) - RawIO.write fd (castPtr p) 0 aLen + GHCDevice.write fd (castPtr p) 0 aLen #else RawIO.write fd (castPtr p) aLen #endif @@ -254,9 +252,9 @@ writeArray (Handle fd) arr = let iov' = iov {arrEnd = arrBound iov} A.writeIndex iov' 0 (RawIO.IOVec (castPtr p) (fromIntegral aLen)) RawIO.writevAll fd (unsafeForeignPtrToPtr (aStart iov')) 1 - -} where aLen = byteLength arr + -} #if !defined(mingw32_HOST_OS) {- diff --git a/src/Streamly/Internal/System/IOVec.hs b/src/Streamly/Internal/System/IOVec.hs index e0a7e1cd55..4d94881a99 100644 --- a/src/Streamly/Internal/System/IOVec.hs +++ b/src/Streamly/Internal/System/IOVec.hs @@ -15,10 +15,8 @@ module Streamly.Internal.System.IOVec , c_writev , c_safe_writev #if !defined(mingw32_HOST_OS) -{- , groupIOVecsOf , groupIOVecsOfMut --} #endif ) where @@ -26,18 +24,15 @@ where #include "inline.hs" #if !defined(mingw32_HOST_OS) -{- import Control.Monad (when) import Control.Monad.IO.Class (MonadIO(..)) import Foreign.Ptr (castPtr) -import Streamly.Internal.Data.MutArray (length) +import Streamly.Internal.Data.MutArray (length, MutArray(..)) import Streamly.Internal.Data.SVar.Type (adaptState) -import Streamly.Internal.Data.MutArray (Array(..)) import qualified Streamly.Internal.Data.Array as Array -import qualified Streamly.Internal.Data.MutArray.Type as MArray +import qualified Streamly.Internal.Data.MutArray as MArray import qualified Streamly.Internal.Data.Stream as D --} #endif import Streamly.Internal.System.IOVec.Type @@ -45,7 +40,6 @@ import Streamly.Internal.System.IOVec.Type import Prelude hiding (length) #if !defined(mingw32_HOST_OS) -{- data GatherState s arr = GatherInitial s | GatherBuffering s arr Int @@ -59,7 +53,7 @@ data GatherState s arr -- @since 0.7.0 {-# INLINE_NORMAL groupIOVecsOfMut #-} groupIOVecsOfMut :: MonadIO m - => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec) + => Int -> Int -> D.Stream m (MutArray a) -> D.Stream m (MutArray IOVec) groupIOVecsOfMut n maxIOVLen (D.Stream step state) = D.Stream step' (GatherInitial state) @@ -78,11 +72,10 @@ groupIOVecsOfMut n maxIOVLen (D.Stream step state) = r <- step (adaptState gst) st case r of D.Yield arr s -> do - let p = arrStart arr - len = MArray.byteLength arr - iov <- liftIO $ MArray.newArray maxIOVLen - iov' <- liftIO $ MArray.snocUnsafe iov (IOVec (castPtr p) - (fromIntegral len)) + let len = MArray.byteLength arr + iov <- liftIO $ MArray.new maxIOVLen + iov' <- MArray.unsafeAsPtr arr $ \p _ -> + MArray.snocUnsafe iov (IOVec (castPtr p) (fromIntegral len)) if len >= n then return $ D.Skip (GatherYielding iov' (GatherInitial s)) else return $ D.Skip (GatherBuffering s iov' len) @@ -93,19 +86,18 @@ groupIOVecsOfMut n maxIOVLen (D.Stream step state) = r <- step (adaptState gst) st case r of D.Yield arr s -> do - let p = arrStart arr - alen = MArray.byteLength arr + let alen = MArray.byteLength arr len' = len + alen if len' > n || length iov >= maxIOVLen then do - iov' <- liftIO $ MArray.newArray maxIOVLen - iov'' <- liftIO $ MArray.snocUnsafe iov' (IOVec (castPtr p) - (fromIntegral alen)) + iov' <- liftIO $ MArray.new maxIOVLen + iov'' <- MArray.unsafeAsPtr arr $ \p _ -> + MArray.snocUnsafe iov' (IOVec (castPtr p) (fromIntegral alen)) return $ D.Skip (GatherYielding iov (GatherBuffering s iov'' alen)) else do - iov' <- liftIO $ MArray.snocUnsafe iov (IOVec (castPtr p) - (fromIntegral alen)) + iov' <- MArray.unsafeAsPtr arr $ \p _ -> + MArray.snocUnsafe iov (IOVec (castPtr p) (fromIntegral alen)) return $ D.Skip (GatherBuffering s iov' len') D.Skip s -> return $ D.Skip (GatherBuffering s iov len) D.Stop -> return $ D.Skip (GatherYielding iov GatherFinish) @@ -128,5 +120,4 @@ groupIOVecsOf n maxIOVLen str = D.map Array.unsafeFreeze $ groupIOVecsOfMut n maxIOVLen $ D.map Array.unsafeThaw str --} #endif diff --git a/src/Streamly/Internal/System/IOVec/Type.hsc b/src/Streamly/Internal/System/IOVec/Type.hsc index 6955c5b553..c15e90df32 100644 --- a/src/Streamly/Internal/System/IOVec/Type.hsc +++ b/src/Streamly/Internal/System/IOVec/Type.hsc @@ -31,6 +31,7 @@ import Foreign.Ptr (Ptr) import System.Posix.Types (CSsize(..)) #if !defined(mingw32_HOST_OS) import Foreign.Storable (Storable(..)) +import Streamly.Internal.Data.MutByteArray (Unbox(..)) #endif ------------------------------------------------------------------------------- @@ -62,6 +63,19 @@ instance Storable IOVec where len :: #{type size_t} = iovLen vec #{poke struct iovec, iov_base} ptr base #{poke struct iovec, iov_len} ptr len + +instance Unbox IOVec where + {-# INLINE peekAt #-} + peekAt i arr = do + base <- peekAt i arr + len <- peekAt (i + #{offset struct iovec, iov_len}) arr + return (IOVec base len) + {-# INLINE pokeAt #-} + pokeAt i arr (IOVec base len) = do + pokeAt i arr base + pokeAt (i + #{offset struct iovec, iov_len}) arr len + {-# INLINE sizeOf #-} + sizeOf _ = #{size struct iovec} #endif -- capi calling convention does not work without -fobject-code option with GHCi From 1ef03ce2929502a3769c8317de107b13ef7488cc Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Tue, 5 May 2026 10:56:01 +0530 Subject: [PATCH 02/10] Cleanup internal-dev build flag, use a cabal.project file --- .packcheck.ignore | 1 + benchmark/streamly-benchmarks.cabal | 13 +--- cabal.project.dev | 44 +++++++++++++ core/src/Streamly/Internal/Data/Array.hs | 6 +- core/src/Streamly/Internal/Data/Array/Type.hs | 48 +++++--------- .../Streamly/Internal/Data/MutArray/Type.hs | 65 +++---------------- .../Streamly/Internal/Data/Serialize/Type.hs | 6 +- core/streamly-core.cabal | 9 +-- streamly.cabal | 14 +--- 9 files changed, 78 insertions(+), 128 deletions(-) create mode 100644 cabal.project.dev diff --git a/.packcheck.ignore b/.packcheck.ignore index 66b8033047..f2b44a794b 100644 --- a/.packcheck.ignore +++ b/.packcheck.ignore @@ -31,6 +31,7 @@ cabal.project.markdown-doctest cabal.project.streamly cabal.project.Werror cabal.project.Werror-nocode +cabal.project.dev core/ examples/README.md flake.lock diff --git a/benchmark/streamly-benchmarks.cabal b/benchmark/streamly-benchmarks.cabal index 34815a1629..17738465f8 100644 --- a/benchmark/streamly-benchmarks.cabal +++ b/benchmark/streamly-benchmarks.cabal @@ -110,9 +110,6 @@ common compile-options -- if flag(use-streamly-core) -- cpp-options: -DUSE_STREAMLY_CORE - if flag(dev) - cpp-options: -DDEVBUILD - if flag(include-strict-utf8) cpp-options: -DINCLUDE_STRICT_UTF8 @@ -146,10 +143,6 @@ common compile-options if flag(has-llvm) ghc-options: -fllvm - if flag(dev) - ghc-options: -Wmissed-specialisations - -Wall-missed-specialisations - common optimization-options if flag(opt) ghc-options: -O2 @@ -158,7 +151,7 @@ common optimization-options -fmax-worker-args=16 -- For this to be effective it must come after the -O2 option - if flag(dev) || flag(debug) + if flag(debug) ghc-options: -fno-ignore-asserts if flag(fusion-plugin) && !impl(ghcjs) && !impl(ghc < 8.6) @@ -205,10 +198,6 @@ common bench-depends build-depends: template-haskell >= 2.14 && < 2.25 , inspection-testing >= 0.4 && < 0.7 - -- Array uses a Storable constraint in dev build making several inspection - -- tests fail - if flag(dev) && flag(inspection) - build-depends: inspection-and-dev-flags-cannot-be-used-together ------------------------------------------------------------------------------- -- Library diff --git a/cabal.project.dev b/cabal.project.dev new file mode 100644 index 0000000000..e5d5ebfab2 --- /dev/null +++ b/cabal.project.dev @@ -0,0 +1,44 @@ +packages: streamly.cabal + , core/streamly-core.cabal + , test/streamly-tests.cabal + , benchmark/streamly-benchmarks.cabal + , bench-test-lib/bench-test-lib.cabal + +-- For compiling standalone programs +-- write-ghc-environment-files: always + +-- For debugging heap overflow +-- jobs: 1 + +package streamly-core + flags: +internal-dev + ghc-options: -DDEVBUILD -DDEBUG + -Wmissed-specialisations + -Wall-missed-specialisations + -fno-ignore-asserts + +package streamly + flags: +internal-dev + ghc-options: -DDEVBUILD -DDEBUG + -Wmissed-specialisations + -Wall-missed-specialisations + -fno-ignore-asserts + +package streamly-tests + flags: +dev + ghc-options: -DDEVBUILD -DDEBUG + -Wmissed-specialisations + -Wall-missed-specialisations + -fno-ignore-asserts + +package streamly-benchmarks + flags: +dev + ghc-options: -DDEVBUILD -DDEBUG + -Wmissed-specialisations + -Wall-missed-specialisations + -fno-ignore-asserts + +source-repository-package + type: git + location: https://github.com/composewell/fusion-plugin.git + tag: 15c0ad50a235a35e85d03cced9ad0f3938565e5a diff --git a/core/src/Streamly/Internal/Data/Array.hs b/core/src/Streamly/Internal/Data/Array.hs index 74a7d6849b..44ae6d8f3b 100644 --- a/core/src/Streamly/Internal/Data/Array.hs +++ b/core/src/Streamly/Internal/Data/Array.hs @@ -403,11 +403,7 @@ streamTransform f arr = -- -- /Pre-release/ -- -unsafeCast, castUnsafe :: -#ifdef DEVBUILD - Unbox b => -#endif - Array a -> Array b +unsafeCast, castUnsafe :: Array a -> Array b unsafeCast (Array contents start end) = Array contents start end RENAME(castUnsafe,unsafeCast) diff --git a/core/src/Streamly/Internal/Data/Array/Type.hs b/core/src/Streamly/Internal/Data/Array/Type.hs index ff65a94d0b..99080b4348 100644 --- a/core/src/Streamly/Internal/Data/Array/Type.hs +++ b/core/src/Streamly/Internal/Data/Array/Type.hs @@ -18,7 +18,6 @@ module Streamly.Internal.Data.Array.Type ( -- ** Type - -- $arrayNotes Array (..) -- ** Conversion @@ -284,16 +283,15 @@ import Streamly.Internal.System.IO (unsafeInlineIO, defaultChunkSize) -- Array Data Type ------------------------------------------------------------------------------- --- $arrayNotes --- --- We can use an 'Unbox' constraint in the Array type and the constraint can --- be automatically provided to a function that pattern matches on the Array --- type. However, it has huge performance cost, so we do not use it. --- Investigate a GHC improvement possiblity. --- data Array a = #ifdef DEVBUILD - Unbox a => + -- We can use an 'Unbox' constraint in the Array type and the constraint + -- can be automatically provided to a function that pattern matches on the + -- Array type. This can make the Foldable instance possible. However, it + -- has huge performance cost, so we do not use it. Investigate a GHC + -- improvement possiblity. + -- + -- Unbox a => #endif -- All offsets are in terms of bytes from the start of arrContents Array @@ -513,12 +511,7 @@ isPinned = MA.isPinned . unsafeThaw -- would make a copy on every splice operation, instead use the -- 'fromChunksK' operation to combine n immutable arrays. {-# INLINE splice #-} -splice :: (MonadIO m -#ifdef DEVBUILD - , Unbox a -#endif - ) - => Array a -> Array a -> m (Array a) +splice :: MonadIO m => Array a -> Array a -> m (Array a) splice arr1 arr2 = unsafeFreeze <$> MA.spliceCopy (unsafeThaw arr1) (unsafeThaw arr2) @@ -1558,11 +1551,15 @@ instance (Unbox a, Ord a) => Ord (Array a) where min x y = if x <= y then x else y #ifdef DEVBUILD --- Definitions using the Unboxed constraint from the Array type. These are to --- make the Foldable instance possible though it is much slower (7x slower). +-- Definitions using the Unbox constraint from the Array type. If we enable the +-- Unbox constraint in the Array type definition we can remove the constraint +-- from here. These are to make the Foldable instance (see below) possible +-- though it is much slower (7x slower). Need to check if the situation has +-- changed in newer GHCs. -- {-# INLINE_NORMAL _toStreamD_ #-} -_toStreamD_ :: forall m a. MonadIO m => Int -> Array a -> D.Stream m a +_toStreamD_ :: forall m a. (Unbox a, MonadIO m) => + Int -> Array a -> D.Stream m a _toStreamD_ size Array{..} = D.Stream step arrStart where @@ -1574,13 +1571,12 @@ _toStreamD_ size Array{..} = D.Stream step arrStart return $ D.Yield x (p + size) {- -XXX Why isn't Unboxed implicit? This does not compile unless I use the Unboxed -contraint. {-# INLINE_NORMAL _foldr #-} _foldr :: forall a b. (a -> b -> b) -> b -> Array a -> b _foldr f z arr = let !n = SIZE_OF(a) in unsafePerformIO $ D.foldr f z $ toStreamD_ n arr + -- | Note that the 'Foldable' instance is 7x slower than the direct -- operations. instance Foldable Array where @@ -1602,19 +1598,11 @@ instance Foldable Array where instance Unbox a => Semigroup (Array a) where arr1 <> arr2 = unsafePerformIO $ splice arr1 arr2 -empty :: -#ifdef DEVBUILD - Unbox a => -#endif - Array a +empty :: Array a empty = Array Unboxed.empty 0 0 {-# DEPRECATED nil "Please use empty instead." #-} -nil :: -#ifdef DEVBUILD - Unbox a => -#endif - Array a +nil :: Array a nil = empty instance Unbox a => Monoid (Array a) where diff --git a/core/src/Streamly/Internal/Data/MutArray/Type.hs b/core/src/Streamly/Internal/Data/MutArray/Type.hs index de3bd61023..fb58bf31bc 100644 --- a/core/src/Streamly/Internal/Data/MutArray/Type.hs +++ b/core/src/Streamly/Internal/Data/MutArray/Type.hs @@ -631,7 +631,7 @@ bytesToElemCount _ n = n `div` SIZE_OF(a) -- data MutArray a = #ifdef DEVBUILD - Unbox a => + -- Unbox a => #endif -- The array is a range into arrContents. arrContents may be a superset of -- the slice represented by the array. All offsets are in bytes. @@ -828,27 +828,15 @@ emptyWithAligned alloc alignSize count = liftIO $ do -- will mostly import the Array module qualified this should be fine. -- | Create an empty array. -empty :: -#ifdef DEVBUILD - Unbox a => -#endif - MutArray a +empty :: MutArray a empty = MutArray Unboxed.empty 0 0 0 {-# DEPRECATED nil "Please use empty instead." #-} -nil :: -#ifdef DEVBUILD - Unbox a => -#endif - MutArray a +nil :: MutArray a nil = empty {-# INLINE newBytesAs #-} -newBytesAs :: MonadIO m => -#ifdef DEVBUILD - Unbox a => -#endif - PinnedState -> Int -> m (MutArray a) +newBytesAs :: MonadIO m => PinnedState -> Int -> m (MutArray a) newBytesAs ps bytes = do contents <- liftIO $ Unboxed.newAs ps bytes return $ MutArray @@ -867,11 +855,7 @@ newBytesAs ps bytes = do -- /Pre-release/ {-# INLINE pinnedNewBytes #-} {-# DEPRECATED pinnedNewBytes "Please use emptyOf' to create a Word8 array and cast it accordingly." #-} -pinnedNewBytes :: MonadIO m => -#ifdef DEVBUILD - Unbox a => -#endif - Int -> m (MutArray a) +pinnedNewBytes :: MonadIO m => Int -> m (MutArray a) pinnedNewBytes = newBytesAs Pinned -- | Like 'emptyWithAligned' but using an allocator is a pinned memory allocator and @@ -2184,11 +2168,7 @@ data ArrayUnsafe a = ArrayUnsafe toArrayUnsafe :: MutArray a -> ArrayUnsafe a toArrayUnsafe (MutArray contents start end _) = ArrayUnsafe contents start end -fromArrayUnsafe :: -#ifdef DEVBUILD - Unbox a => -#endif - ArrayUnsafe a -> MutArray a +fromArrayUnsafe :: ArrayUnsafe a -> MutArray a fromArrayUnsafe (ArrayUnsafe contents start end) = MutArray contents start end end @@ -3217,13 +3197,7 @@ fromListRev xs = fromListRevN (Prelude.length xs) xs -- it again. We can use SIMD read/write as well. {-# INLINE cloneAs #-} -cloneAs :: - ( MonadIO m -#ifdef DEVBUILD - , Unbox a -#endif - ) - => PinnedState -> MutArray a -> m (MutArray a) +cloneAs :: MonadIO m => PinnedState -> MutArray a -> m (MutArray a) cloneAs ps src = do let startSrc = arrStart src @@ -3240,24 +3214,12 @@ cloneAs ps src = -- The new "MutArray" is unpinned in nature. Use "clone'" to clone the -- MutArray in pinned memory. {-# INLINE clone #-} -clone :: - ( MonadIO m -#ifdef DEVBUILD - , Unbox a -#endif - ) - => MutArray a -> m (MutArray a) +clone :: MonadIO m => MutArray a -> m (MutArray a) clone = cloneAs Unpinned -- Similar to "clone" but uses pinned memory. {-# INLINE clone' #-} -pinnedClone, clone' :: - ( MonadIO m -#ifdef DEVBUILD - , Unbox a -#endif - ) - => MutArray a -> m (MutArray a) +pinnedClone, clone' :: MonadIO m => MutArray a -> m (MutArray a) clone' = cloneAs Pinned RENAME_PRIME(pinnedClone,clone) @@ -3271,9 +3233,6 @@ RENAME_PRIME(pinnedClone,clone) -- Note: If you freeze and splice it will create a new array. {-# INLINE spliceCopy #-} spliceCopy :: forall m a. MonadIO m => -#ifdef DEVBUILD - Unbox a => -#endif MutArray a -> MutArray a -> m (MutArray a) spliceCopy arr1 arr2 = do let start1 = arrStart arr1 @@ -3615,11 +3574,7 @@ RENAME(splitAt,breakAt) -- -- /Pre-release/ -- -castUnsafe, unsafeCast :: -#ifdef DEVBUILD - Unbox b => -#endif - MutArray a -> MutArray b +castUnsafe, unsafeCast :: MutArray a -> MutArray b unsafeCast (MutArray contents start end bound) = MutArray contents start end bound diff --git a/core/src/Streamly/Internal/Data/Serialize/Type.hs b/core/src/Streamly/Internal/Data/Serialize/Type.hs index dedf21b2e1..aa979ccc4a 100644 --- a/core/src/Streamly/Internal/Data/Serialize/Type.hs +++ b/core/src/Streamly/Internal/Data/Serialize/Type.hs @@ -275,11 +275,7 @@ instance forall a. Serialize a => Serialize [a] where pokeList (acc + 1) o1 xs pokeList 0 off1 val -instance -#ifdef DEVBUILD - Unbox a => -#endif - Serialize (Array a) where +instance Serialize (Array a) where {-# INLINE addSizeTo #-} addSizeTo i (Array {..}) = i + (arrEnd - arrStart) + 8 diff --git a/core/streamly-core.cabal b/core/streamly-core.cabal index 347bbcf344..d7498c1f98 100644 --- a/core/streamly-core.cabal +++ b/core/streamly-core.cabal @@ -156,9 +156,6 @@ common compile-options if flag(force-lstat-readdir) cpp-options: -DFORCE_LSTAT_READDIR - if flag(internal-dev) - cpp-options: -DDEVBUILD - if flag(use-unfolds) cpp-options: -DUSE_UNFOLDS_EVERYWHERE @@ -196,10 +193,6 @@ common compile-options if flag(has-llvm) ghc-options: -fllvm - if flag(internal-dev) - ghc-options: -Wmissed-specialisations - -Wall-missed-specialisations - if flag(limit-build-mem) ghc-options: -j1 +RTS -M500M -RTS @@ -282,7 +275,7 @@ common optimization-options -fmax-worker-args=16 -- For this to be effective it must come after the -O2 option - if flag(internal-dev) || flag(debug) || !flag(opt) + if flag(debug) || !flag(opt) cpp-options: -DDEBUG ghc-options: -fno-ignore-asserts diff --git a/streamly.cabal b/streamly.cabal index 25c8f178f5..a7ab285174 100644 --- a/streamly.cabal +++ b/streamly.cabal @@ -248,9 +248,6 @@ common compile-options if os(windows) cpp-options: -DCABAL_OS_WINDOWS - if flag(internal-dev) - cpp-options: -DDEVBUILD - if flag(inspection) cpp-options: -DINSPECTION @@ -281,10 +278,6 @@ common compile-options if flag(has-llvm) ghc-options: -fllvm - if flag(internal-dev) - ghc-options: -Wmissed-specialisations - -Wall-missed-specialisations - if flag(limit-build-mem) ghc-options: +RTS -M600M -RTS @@ -364,7 +357,7 @@ common optimization-options -fmax-worker-args=16 -- For this to be effective it must come after the -O2 option - if flag(internal-dev) || flag(debug) || !flag(opt) + if flag(debug) || !flag(opt) ghc-options: -fno-ignore-asserts cpp-options: -DDEBUG @@ -589,8 +582,3 @@ library if flag(inspection) build-depends: inspection-testing >= 0.4 && < 0.7 - - -- Array uses a Storable constraint in dev build making several inspection - -- tests fail - if flag(internal-dev) && flag(inspection) - build-depends: inspection-and-dev-flags-cannot-be-used-together From f10ede5ef0dd73f1107d8b0dd055148e413dde51 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Tue, 5 May 2026 11:35:12 +0530 Subject: [PATCH 03/10] Fix build with DEVBUILD flag, import foldr1 --- test/lib/Streamly/Test/Prelude/Common.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/lib/Streamly/Test/Prelude/Common.hs b/test/lib/Streamly/Test/Prelude/Common.hs index fd7474d1ff..d82f03ef2a 100644 --- a/test/lib/Streamly/Test/Prelude/Common.hs +++ b/test/lib/Streamly/Test/Prelude/Common.hs @@ -115,6 +115,9 @@ import Data.List , findIndices , foldl' , foldl1' +#ifdef DEVBUILD + , foldr1 +#endif , insert , intersperse , isPrefixOf From aa20233bb4f03dd8039b18ab725a4f041805a61e Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 6 May 2026 01:17:01 +0530 Subject: [PATCH 04/10] Fix SmallArray code (under internal-dev flag) --- src/Streamly/Data/SmallArray.hs | 2 +- src/Streamly/Internal/Data/SmallArray.hs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Streamly/Data/SmallArray.hs b/src/Streamly/Data/SmallArray.hs index 43c2236c71..26775c2f9e 100644 --- a/src/Streamly/Data/SmallArray.hs +++ b/src/Streamly/Data/SmallArray.hs @@ -14,7 +14,7 @@ module Streamly.Data.SmallArray , A.fromListN , A.fromStreamN - , A.writeN + , A.createOf -- * Elimination , A.read diff --git a/src/Streamly/Internal/Data/SmallArray.hs b/src/Streamly/Internal/Data/SmallArray.hs index ddb5d11fe6..8452f9bde7 100644 --- a/src/Streamly/Internal/Data/SmallArray.hs +++ b/src/Streamly/Internal/Data/SmallArray.hs @@ -23,7 +23,7 @@ module Streamly.Internal.Data.SmallArray , length - , writeN + , createOf , toStreamD , toStreamDRev @@ -99,14 +99,14 @@ foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr foldr :: (a -> b -> b) -> b -> SmallArray a -> b foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr --- | @writeN n@ folds a maximum of @n@ elements from the input stream to an +-- | @createOf n@ folds a maximum of @n@ elements from the input stream to an -- 'SmallArray'. -- -- Since we are folding to a 'SmallArray' @n@ should be <= 128, for larger number -- of elements use an 'Array' from either "Streamly.Data.Array.Generic" or "Streamly.Data.Array.Foreign". -{-# INLINE_NORMAL writeN #-} -writeN :: MonadIO m => Int -> Fold m a (SmallArray a) -writeN len = FL.Fold step initial extract extract +{-# INLINE_NORMAL createOf #-} +createOf :: MonadIO m => Int -> Fold m a (SmallArray a) +createOf len = FL.Fold step initial extract extract where From 32ba212949b76311be9997a9094bc2023e0be15e Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 6 May 2026 01:17:34 +0530 Subject: [PATCH 05/10] Fix/hide some failing tests under the "dev" flag --- test/Streamly/Test/Data/Stream/Rate.hs | 6 ++++++ test/Streamly/Test/Data/Stream/Time.hs | 2 ++ test/Streamly/Test/Prelude/Rate.hs | 6 ++++++ test/Streamly/Test/Prelude/Serial.hs | 2 ++ test/Streamly/Test/Unicode/Char.hs | 6 ++---- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/test/Streamly/Test/Data/Stream/Rate.hs b/test/Streamly/Test/Data/Stream/Rate.hs index aef68b7785..be97abee42 100644 --- a/test/Streamly/Test/Data/Stream/Rate.hs +++ b/test/Streamly/Test/Data/Stream/Rate.hs @@ -211,7 +211,9 @@ main = hspec $ do -- 10 0.1 second. let rates = [0.1, 1, 10, 100, 1000, 10000 #ifndef __GHCJS__ +#ifdef INCLUDE_FLAKY_TESTS , 100000, 1000000 +#endif #endif ] in describe "async no consumer delay no producer delay" $ @@ -241,7 +243,9 @@ main = hspec $ do let rates = [0.1, 1, 10, 100, 1000, 10000 #ifndef __GHCJS__ +#ifdef INCLUDE_FLAKY_TESTS , 100000, 1000000 +#endif #endif ] in describe "interleaved, no consumer delay no producer delay" $ @@ -257,7 +261,9 @@ main = hspec $ do let rates = [0.1, 1, 10, 100, 1000, 10000 #ifndef __GHCJS__ +#ifdef INCLUDE_FLAKY_TESTS , 100000, 1000000 +#endif #endif ] in describe "ordered, no consumer delay no producer delay" $ diff --git a/test/Streamly/Test/Data/Stream/Time.hs b/test/Streamly/Test/Data/Stream/Time.hs index ca4abe6ef5..41a88cffab 100644 --- a/test/Streamly/Test/Data/Stream/Time.hs +++ b/test/Streamly/Test/Data/Stream/Time.hs @@ -89,7 +89,9 @@ main = hspec #ifdef DEVBUILD describe "Filtering" $ do it "takeInterval" (testTakeInterval `shouldReturn` True) +#ifdef INCLUDE_FLAKY_TESTS it "dropInterval" (testDropInterval `shouldReturn` True) +#endif #endif it "dummy" (return () `shouldReturn` ()) diff --git a/test/Streamly/Test/Prelude/Rate.hs b/test/Streamly/Test/Prelude/Rate.hs index 11c348a4dc..aee3d25e58 100644 --- a/test/Streamly/Test/Prelude/Rate.hs +++ b/test/Streamly/Test/Prelude/Rate.hs @@ -172,7 +172,9 @@ main = hspec $ do -- 10 0.1 second. let rates = [1, 10, 100, 1000, 10000 #ifndef __GHCJS__ +#ifdef INCLUDE_FLAKY_TESTS , 100000, 1000000 +#endif #endif ] in describe "asyncly no consumer delay no producer delay" $ @@ -202,7 +204,9 @@ main = hspec $ do let rates = [1, 10, 100, 1000, 10000 #ifndef __GHCJS__ +#ifdef INCLUDE_FLAKY_TESTS , 100000, 1000000 +#endif #endif ] in describe "fromWAsync, no consumer delay no producer delay" $ @@ -218,7 +222,9 @@ main = hspec $ do let rates = [1, 10, 100, 1000, 10000 #ifndef __GHCJS__ +#ifdef INCLUDE_FLAKY_TESTS , 100000, 1000000 +#endif #endif ] in describe "aheadly, no consumer delay no producer delay" $ diff --git a/test/Streamly/Test/Prelude/Serial.hs b/test/Streamly/Test/Prelude/Serial.hs index 89b731f759..945759dfb8 100644 --- a/test/Streamly/Test/Prelude/Serial.hs +++ b/test/Streamly/Test/Prelude/Serial.hs @@ -677,7 +677,9 @@ main = hspec #ifdef DEVBUILD describe "Filtering" $ do it "takeInterval" (testTakeInterval `shouldReturn` True) +#ifdef INCLUDE_FLAKY_TESTS it "dropInterval" (testDropInterval `shouldReturn` True) +#endif #endif -- Just some basic sanity tests for now diff --git a/test/Streamly/Test/Unicode/Char.hs b/test/Streamly/Test/Unicode/Char.hs index c71f68e276..69c4bdd643 100644 --- a/test/Streamly/Test/Unicode/Char.hs +++ b/test/Streamly/Test/Unicode/Char.hs @@ -72,11 +72,9 @@ checkNFKD :: (Text, Text, Text, Text, Text) -> IO Bool checkNFKD (c1, c2, c3, c4, c5) = checkOp "toNFKD" NFKD $ map (c5,) [c1, c2, c3, c4, c5] -splitOn predicate f = S.foldMany1 (FL.takeEndBy_ predicate f) - checkAllTestCases :: Int -> String -> IO () checkAllTestCases lineno line = do - cs <- toList $ splitOn (== ';') FL.toList $ S.fromList line + cs <- toList $ S.splitSepBy_ (== ';') FL.toList $ S.fromList line case cs of c1 : c2 : c3 : c4 : c5 : _ -> do let cps = map cpToText [c1, c2, c3, c4, c5] @@ -123,7 +121,7 @@ testNormalize file = do checkAll [] = return () filesDir :: String -filesDir = "test/Streamly/Test/Unicode" +filesDir = "Streamly/Test/Unicode" main :: IO () main = do From 83e57ab2babadc52425d9f26eb1601072c6bec97 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 6 May 2026 01:27:11 +0530 Subject: [PATCH 06/10] Add a CI for dev flag --- .github/workflows/haskell.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/haskell.yml b/.github/workflows/haskell.yml index 73fa43208a..c40c28b5f6 100644 --- a/.github/workflows/haskell.yml +++ b/.github/workflows/haskell.yml @@ -192,13 +192,14 @@ jobs: subdir: core ignore_error: false - - name: debug-no-opt + # This includes the debug flag as well + - name: dev-no-opt runner: macos-latest command: cabal ghc_version: 9.14.1 # WARNING! cannot use # comments inside pack_options. pack_options: >- - CABAL_PROJECT=cabal.project + CABAL_PROJECT=cabal.project.dev CABAL_BUILD_OPTIONS="--flag debug --flag -opt" ignore_error: false From 5cba41a3b7a568a2145d48314d70f4170ced37d2 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 6 May 2026 03:13:54 +0530 Subject: [PATCH 07/10] Remove nano-bench --- benchmark/NanoBenchmarks.hs | 127 ---------------------------- benchmark/streamly-benchmarks.cabal | 7 -- streamly.cabal | 1 - 3 files changed, 135 deletions(-) delete mode 100644 benchmark/NanoBenchmarks.hs diff --git a/benchmark/NanoBenchmarks.hs b/benchmark/NanoBenchmarks.hs deleted file mode 100644 index 81c142659b..0000000000 --- a/benchmark/NanoBenchmarks.hs +++ /dev/null @@ -1,127 +0,0 @@ -{-# OPTIONS_GHC -Wno-deprecations #-} - -------------------------------------------------------------------------------- --- Investigate specific benchmarks more closely in isolation, possibly looking --- at GHC generated code for optimizing specific problematic cases. -------------------------------------------------------------------------------- - -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE ScopedTypeVariables #-} - -import Data.Char (ord) -import Streamly.Internal.Data.Stream (Stream) -import System.IO (hSeek, SeekMode(..), openFile, IOMode(..)) - -import qualified Streamly.Data.Array as A -import qualified Streamly.Internal.FileSystem.Handle as IFH -import qualified Streamly.Data.Fold as FL -import qualified Streamly.Internal.Data.Stream as S -import qualified Streamly.Internal.Data.Stream.IsStream as Internal - -import Test.Tasty.Bench -import System.Random - -maxValue :: Int -maxValue = 100000 - -drain :: Monad m => S.Stream m a -> m () -drain = S.fold FL.drain - -{-# INLINE sourceUnfoldrM #-} -sourceUnfoldrM :: Monad m => Stream m Int -sourceUnfoldrM = S.unfoldrM step 0 - where - step cnt = - if cnt > maxValue - then return Nothing - else return (Just (cnt, cnt + 1)) - -{-# INLINE sourceUnfoldrMN #-} -sourceUnfoldrMN :: Monad m => Int -> Stream m Int -sourceUnfoldrMN n = S.unfoldrM step n - where - step cnt = - if cnt > n - then return Nothing - else return (Just (cnt, cnt + 1)) - -{-# INLINE sourceUnfoldr #-} -sourceUnfoldr :: Monad m => Int -> Stream m Int -sourceUnfoldr n = S.unfoldr step n - where - step cnt = - if cnt > n + maxValue - then Nothing - else Just (cnt, cnt + 1) - -------------------------------------------------------------------------------- --- take-drop composition -------------------------------------------------------------------------------- - -takeAllDropOne :: Monad m => Stream m Int -> Stream m Int -takeAllDropOne = S.drop 1 . S.take maxValue - --- Requires -fspec-constr-recursive=5 for better fused code --- The number depends on how many times we compose it - -{-# INLINE takeDrop #-} -takeDrop :: Monad m => Stream m Int -> m () -takeDrop = drain . - takeAllDropOne . takeAllDropOne . takeAllDropOne . takeAllDropOne - -------------------------------------------------------------------------------- --- dropWhileFalse composition -------------------------------------------------------------------------------- - -dropWhileFalse :: Monad m => Stream m Int -> Stream m Int -dropWhileFalse = S.dropWhile (> maxValue) - --- Requires -fspec-constr-recursive=5 for better fused code --- The number depends on how many times we compose it - -{-# INLINE dropWhileFalseX4 #-} -dropWhileFalseX4 :: Monad m => Stream m Int -> m () -dropWhileFalseX4 = drain - . dropWhileFalse . dropWhileFalse . dropWhileFalse . dropWhileFalse - -------------------------------------------------------------------------------- --- iteration -------------------------------------------------------------------------------- - -{-# INLINE iterateSource #-} -iterateSource - :: Monad m - => (Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int -iterateSource g i n = f i (sourceUnfoldrMN n) - where - f (0 :: Int) m = g m - f x m = g (f (x - 1) m) - --- Keep only the benchmark that is to be investiagted and comment out the rest. --- We keep all of them enabled by default for testing the build. -main :: IO () -main = do - defaultMain [bench "unfoldr" $ nfIO $ - randomRIO (1,1) >>= \n -> drain (sourceUnfoldr n)] - defaultMain [bench "take-drop" $ nfIO $ takeDrop sourceUnfoldrM] - defaultMain [bench "dropWhileFalseX4" $ - nfIO $ dropWhileFalseX4 sourceUnfoldrM] - defaultMain [bench "iterate-mapM" $ - nfIO $ drain $ iterateSource (S.mapM return) 100000 10] - - inText <- openFile "benchmark/text-processing/gutenberg-500.txt" ReadMode - defaultMain [mkBenchText "splitOn abc...xyz" inText $ do - S.fold FL.length - (Internal.splitOnSeq - (A.fromList - $ map (fromIntegral . ord) "abcdefghijklmnopqrstuvwxyz" - ) - FL.drain - $ IFH.read inText - ) - >>= print - ] - where - - mkBenchText name h action = - env (hSeek h AbsoluteSeek 0) (\_ -> bench name $ nfIO action) diff --git a/benchmark/streamly-benchmarks.cabal b/benchmark/streamly-benchmarks.cabal index 17738465f8..f096085952 100644 --- a/benchmark/streamly-benchmarks.cabal +++ b/benchmark/streamly-benchmarks.cabal @@ -649,13 +649,6 @@ benchmark Unicode.Parser hs-source-dirs: Streamly/Benchmark/Unicode main-is: Parser.hs -executable nano-bench - import: bench-options - hs-source-dirs: . - main-is: NanoBenchmarks.hs - if !flag(dev) - buildable: False - benchmark Unicode.Stream import: bench-options type: exitcode-stdio-1.0 diff --git a/streamly.cabal b/streamly.cabal index a7ab285174..6941af9fd0 100644 --- a/streamly.cabal +++ b/streamly.cabal @@ -67,7 +67,6 @@ build-type: Configure extra-source-files: bench-test-lib/bench-test-lib.cabal bench-test-lib/src/BenchTestLib/DirIO.hs - benchmark/*.hs benchmark/Streamly/Benchmark/Data/*.hs benchmark/Streamly/Benchmark/Data/Array.hs benchmark/Streamly/Benchmark/Data/Array/Common.hs From 6fbbba85ba4b5679b677bd547f606337393e8c2b Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 6 May 2026 04:13:43 +0530 Subject: [PATCH 08/10] Disable the Rate tests in CI --- test/streamly-tests.cabal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/streamly-tests.cabal b/test/streamly-tests.cabal index 23b1f3e8ca..bbc8eba86c 100644 --- a/test/streamly-tests.cabal +++ b/test/streamly-tests.cabal @@ -411,7 +411,7 @@ test-suite Data.Stream.Rate buildable: True else buildable: False - if flag(use-streamly-core) + if flag(use-streamly-core) || !flag(include-flaky-tests) buildable: False -- XXX Rename to MutByteArray.Unbox @@ -628,7 +628,7 @@ test-suite Prelude.Rate buildable: True else buildable: False - if flag(use-streamly-core) + if flag(use-streamly-core) || !flag(include-flaky-tests) buildable: False test-suite Prelude.Serial From 4963bca222891df7cebda4273d098ad040549ebd Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 6 May 2026 01:43:06 +0530 Subject: [PATCH 09/10] Cleanup and fix the circle-ci config file install gcc and bintuils separately. they are not found by default anymore. --- .circleci/config.yml | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a5718b5526..d022a12526 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ env: &env # ------------------------------------------------------------------------ # GHC_OPTIONS: "-Werror" # For updating see: https://downloads.haskell.org/~ghcup/ - GHCUP_VERSION: 0.1.20.0 + GHCUP_VERSION: latest CABAL_REINIT_CONFIG: "y" LC_ALL: "C.UTF-8" @@ -44,7 +44,7 @@ env: &env # ------------------------------------------------------------------------ # Where to find the required tools # ------------------------------------------------------------------------ - PATH: /sbin:/usr/sbin:/bin:/usr/bin + PATH: /sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin # ------------------------------------------------------------------------ # Location of packcheck.sh (the shell script invoked to perform CI tests ). @@ -57,7 +57,7 @@ env: &env # If you have not committed packcheck.sh in your repo at PACKCHECK # then it is automatically pulled from this URL. PACKCHECK_GITHUB_URL: "https://raw.githubusercontent.com/composewell/packcheck" - PACKCHECK_GITHUB_COMMIT: "dd6862df527f317fd4987afa523fba3f4fde7e19" + PACKCHECK_GITHUB_COMMIT: "157310b35907e93b9c5c69e6bdcad9ab6296260e" executors: amd64-executor: @@ -91,11 +91,7 @@ preinstall: &preinstall apt-get install -y zlib1g-dev # For ghcup to install ghc - if test -n "$GHCUP_VERSION" - then - apt-get install -y gcc - apt-get install -y g++ - fi + apt-get install -y build-essential # libgmp required by ghc for linking apt-get install -y libgmp-dev @@ -146,24 +142,7 @@ save: &save #----------------------------------------------------------------------------- jobs: - cabal-ghc-8_6_5: - <<: *env - executor: amd64-executor - steps: - - checkout - - *restore - - *preinstall - - run: - environment: - GHCVER: "8.6.5" - CABALVER: "3.6.2.0" - CABAL_PROJECT: "cabal.project" - DISABLE_SDIST_BUILD: "yes" - CABAL_BUILD_OPTIONS: "--flag debug --flag -opt" - command: | - bash -c "$PACKCHECK cabal" - - *save - cabal-ghc-9_8_1-docspec: + cabal-ghc-9_14_1-docspec: <<: *env executor: amd64-executor steps: @@ -172,8 +151,7 @@ jobs: - *preinstall - run: environment: - GHCVER: "9.8.1" - CABALVER: "3.10.1.0" + GHCVER: "9.14.1" CABAL_PROJECT: "cabal.project.doctest" DISABLE_SDIST_BUILD: "y" DISABLE_TEST: "y" @@ -212,9 +190,7 @@ workflows: version: 2 build: jobs: - #- cabal-ghc-8_6_5: - # name: 8.6.5-debug-unoptimized - - cabal-ghc-9_8_1-docspec: - name: ghc-9.8.1-docspec + - cabal-ghc-9_14_1-docspec: + name: ghc-9.14.1-docspec - hlint-trailing-spaces: name: hlint and trailing spaces From 9cc22a83460c7f0925d2c1b703363a678df89c9f Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 6 May 2026 05:01:23 +0530 Subject: [PATCH 10/10] Fix docspec for GHC 9.14.1 --- src/Streamly/Internal/Data/Stream/MkType.hs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Streamly/Internal/Data/Stream/MkType.hs b/src/Streamly/Internal/Data/Stream/MkType.hs index a11df53ed0..8415d655c0 100644 --- a/src/Streamly/Internal/Data/Stream/MkType.hs +++ b/src/Streamly/Internal/Data/Stream/MkType.hs @@ -516,9 +516,11 @@ flattenDec (ma:mas) = do -- unZipStream (ZipStream strm) = strm -- deriving instance IsList (ZipStream Identity a) -- deriving instance a ~ --- GHC.Types.Char => IsString (ZipStream Identity a) --- deriving instance GHC.Classes.Eq a => Eq (ZipStream Identity a) --- deriving instance GHC.Classes.Ord a => Ord (ZipStream Identity a) +-- GHC.Internal.Types.Char => IsString (ZipStream Identity a) +-- deriving instance GHC.Internal.Classes.Eq a => Eq (ZipStream Identity +-- a) +-- deriving instance GHC.Internal.Classes.Ord a => Ord (ZipStream Identity +-- a) -- instance Show a => Show (ZipStream Identity a) -- where {{-# INLINE show #-}; show (ZipStream strm) = show strm} -- instance Read a => Read (ZipStream Identity a)