diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+# 0.4.4
+
+* Addition of `appendOuterM` and `concatOuterM`
+* Addition of `zoom`
+* Addition of `write_`, `modify_` and `swap_`
+
+# 0.4.3
+
+* Addition of `catMaybesS` and `tally`
+
 # 0.4.3
 
 * Addition of `applyStencil` and `Padding` with helper functions `noPadding` and `samePadding`.
diff --git a/massiv.cabal b/massiv.cabal
--- a/massiv.cabal
+++ b/massiv.cabal
@@ -1,5 +1,5 @@
 name:                massiv
-version:             0.4.3.0
+version:             0.4.4.0
 synopsis:            Massiv (Массив) is an Array Library.
 description:         Multi-dimensional Arrays with fusion, stencils and parallel computation.
 homepage:            https://github.com/lehins/massiv
@@ -16,7 +16,9 @@
 tested-with:          GHC == 8.4.3
                     , GHC == 8.4.4
                     , GHC == 8.6.3
+                    , GHC == 8.6.4
                     , GHC == 8.6.5
+                    , GHC == 8.8.1
 
 flag unsafe-checks
   description: Enable all the bounds checks for unsafe functions at the cost of
diff --git a/src/Data/Massiv/Array.hs b/src/Data/Massiv/Array.hs
--- a/src/Data/Massiv/Array.hs
+++ b/src/Data/Massiv/Array.hs
@@ -135,6 +135,7 @@
   -- * Algorithms
   -- ** Sorting
   , quicksort
+  , tally
   -- ** Iterations
   , iterateUntil
   -- * Conversion
@@ -163,7 +164,7 @@
 import Data.Massiv.Array.Ops.Fold
 import Data.Massiv.Array.Ops.Map
 import Data.Massiv.Array.Ops.Slice
-import Data.Massiv.Array.Ops.Sort (quicksort)
+import Data.Massiv.Array.Ops.Sort (quicksort, tally)
 import Data.Massiv.Array.Ops.Transform
 import Data.Massiv.Array.Stencil
 import Data.Massiv.Core
diff --git a/src/Data/Massiv/Array/Delayed/Push.hs b/src/Data/Massiv/Array/Delayed/Push.hs
--- a/src/Data/Massiv/Array/Delayed/Push.hs
+++ b/src/Data/Massiv/Array/Delayed/Push.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
@@ -24,6 +25,8 @@
   , makeLoadArray
   , unsafeMakeLoadArray
   , fromStrideLoad
+  , appendOuterM
+  , concatOuterM
   ) where
 
 import Control.Monad
@@ -117,6 +120,51 @@
     {-# INLINE load #-}
 {-# INLINE mappendDL #-}
 
+-- | Append two arrays together along the outer most dimension. Inner dimensions must
+-- agree, otherwise `SizeMismatchException`.
+--
+-- @since 0.4.4
+appendOuterM ::
+     forall ix e m. (Index ix, MonadThrow m)
+  => Array DL ix e
+  -> Array DL ix e
+  -> m (Array DL ix e)
+appendOuterM (DLArray c1 sz1 mDef1 load1) (DLArray c2 sz2 mDef2 load2) = do
+  let (!i1, !szl1) = unconsSz sz1
+      (!i2, !szl2) = unconsSz sz2
+  unless (szl1 == szl2) $ throwM $ SizeMismatchException sz1 sz2
+  pure $
+    DLArray {dlComp = c1 <> c2, dlSize = consSz (i1 + i2) szl1, dlDefault = Nothing, dlLoad = load}
+  where
+    !k1 = totalElem sz1
+    !k2 = totalElem sz2
+    load :: Monad n => Scheduler n () -> Int -> (Int -> e -> n ()) -> n ()
+    load scheduler !startAt dlWrite = do
+      scheduleWork_ scheduler $ do
+        S.traverse_ (\def1 -> loopM_ startAt (< k1) (+ 1) (`dlWrite` def1)) mDef1
+        load1 scheduler startAt dlWrite
+      scheduleWork_ scheduler $ do
+        let !startAt2 = startAt + k1
+        S.traverse_ (\def2 -> loopM_ startAt2 (< startAt2 + k2) (+ 1) (`dlWrite` def2)) mDef2
+        load2 scheduler startAt2 dlWrite
+    {-# INLINE load #-}
+{-# INLINE appendOuterM #-}
+
+-- | Concat arrays together along the most most dimension. Inner dimensions must agree
+-- for all arrays in the list, otherwise `SizeMismatchException`.
+--
+-- @since 0.4.4
+concatOuterM ::
+     forall ix e m. (Index ix, MonadThrow m)
+  => [Array DL ix e]
+  -> m (Array DL ix e)
+concatOuterM =
+  \case
+    [] -> pure empty
+    (x:xs) -> F.foldlM appendOuterM x xs
+{-# INLINE concatOuterM #-}
+
+
 -- | Describe how an array should be loaded into memory sequentially. For parallelizable
 -- version see `makeLoadArray`.
 --
@@ -203,7 +251,7 @@
 toLoadArray :: Load r ix e => Array r ix e -> Array DL ix e
 toLoadArray arr =
   DLArray (getComp arr) (size arr) Nothing $ \scheduler startAt dlWrite ->
-    loadArrayM scheduler arr (\ !i -> dlWrite (i + startAt))
+    loadArrayM scheduler arr (dlWrite . (+ startAt))
 {-# INLINE toLoadArray #-}
 
 -- | Convert an array that can be loaded with stride into `DL` representation.
diff --git a/src/Data/Massiv/Array/Delayed/Stream.hs b/src/Data/Massiv/Array/Delayed/Stream.hs
--- a/src/Data/Massiv/Array/Delayed/Stream.hs
+++ b/src/Data/Massiv/Array/Delayed/Stream.hs
@@ -23,6 +23,7 @@
   , filterM
   , mapMaybeS
   , mapMaybeM
+  , catMaybesS
   , unfoldr
   , unfoldrN
   ) where
@@ -300,12 +301,19 @@
 
 
 -- | Apply a function to each element of the array, while discarding `Nothing` and
--- keepingt he `Maybe` result.
+-- keeping the `Maybe` result.
 --
 -- @since 0.4.1
 mapMaybeS :: S.Stream r ix a => (a -> Maybe b) -> Array r ix a -> Array DS Ix1 b
 mapMaybeS f = DSArray . S.mapMaybe f . S.toStream
 {-# INLINE mapMaybeS #-}
+
+-- | Keep all `Maybe`s and discard the `Nothing`s.
+--
+-- @since 0.4.4
+catMaybesS :: S.Stream r ix (Maybe a) => Array r ix (Maybe a) -> Array DS Ix1 a
+catMaybesS = mapMaybeS id
+{-# INLINE catMaybesS #-}
 
 
 -- | Similar to `mapMaybeS`, but with the use of `Applicative`
diff --git a/src/Data/Massiv/Array/Delayed/Windowed.hs b/src/Data/Massiv/Array/Delayed/Windowed.hs
--- a/src/Data/Massiv/Array/Delayed/Windowed.hs
+++ b/src/Data/Massiv/Array/Delayed/Windowed.hs
@@ -27,7 +27,6 @@
   ) where
 
 import Control.Exception (Exception(..))
-import Control.Scheduler (trivialScheduler_)
 import Control.Monad (when)
 import Data.Massiv.Array.Delayed.Pull
 import Data.Massiv.Array.Manifest.Boxed
@@ -75,12 +74,7 @@
   {-# INLINE makeArray #-}
 
 
--- TODO: adjust in response to Window
--- instance Index ix => Extract DW ix e where
---   unsafeExtract sIx newSz = unsafeExtract sIx newSz . dwArray
---   {-# INLINE unsafeExtract #-}
 
-
 instance Functor (Array DW ix) where
   fmap f arr@DWArray{dwArray, dwWindow} =
     arr
@@ -364,7 +358,7 @@
   {-# INLINE size #-}
   getComp = dComp . dwArray
   {-# INLINE getComp #-}
-  loadArrayM scheduler = loadWithIxN (scheduleWork scheduler)
+  loadArrayM = loadWithIxN
   {-# INLINE loadArrayM #-}
 
 instance (Index (IxN n), StrideLoad DW (Ix (n - 1)) e) => StrideLoad DW (IxN n) e where
@@ -421,11 +415,11 @@
 
 loadWithIxN ::
      (Index ix, Monad m, Load DW (Lower ix) e)
-  => (m () -> m ())
+  => Scheduler m ()
   -> Array DW ix e
   -> (Int -> e -> m ())
   -> m ()
-loadWithIxN with arr uWrite = do
+loadWithIxN scheduler arr uWrite = do
   let DWArray darr window = arr
       DArray {dSize = sz, dIndex = indexBorder} = darr
       Window {windowStart, windowSize, windowIndex, windowUnrollIx2} = fromMaybe zeroWindow window
@@ -443,8 +437,8 @@
       mkLowerArray mw i =
         DWArray {dwArray = DArray Seq szL (indexBorder . consDim i), dwWindow = ($ i) <$> mw}
       loadLower mw !i =
-        with $
-        loadArrayM trivialScheduler_ (mkLowerArray mw i) (\k -> uWrite (k + pageElements * i))
+        scheduleWork_ scheduler $
+        loadArrayM scheduler (mkLowerArray mw i) (\k -> uWrite (k + pageElements * i))
       {-# NOINLINE loadLower #-}
   loopM_ 0 (< headDim windowStart) (+ 1) (loadLower Nothing)
   loopM_ t (< headDim windowEnd) (+ 1) (loadLower (Just mkLowerWindow))
diff --git a/src/Data/Massiv/Array/Mutable.hs b/src/Data/Massiv/Array/Mutable.hs
--- a/src/Data/Massiv/Array/Mutable.hs
+++ b/src/Data/Massiv/Array/Mutable.hs
@@ -20,13 +20,16 @@
   , readM
   , read'
   , write
+  , write_
   , writeM
   , write'
   , modify
+  , modify_
   , modifyM
   , modifyM_
   , modify'
   , swap
+  , swap_
   , swapM
   , swapM_
   , swap'
@@ -910,6 +913,16 @@
   else pure False
 {-# INLINE write #-}
 
+-- | /O(1)/ - Write an element into the cell of a mutable array. Same as `write` function
+-- in case of an out of bounds index it is noop, but unlike `write`, there is no
+-- information is returned about was the writing of element successful or not.  In other
+-- words, just like `writeM`, but doesn't throw an exception.
+--
+-- @since 0.4.4
+write_ :: (Mutable r ix e, PrimMonad m) => MArray (PrimState m) r ix e -> ix -> e -> m ()
+write_ marr ix = when (isSafeIndex (msize marr) ix) . unsafeWrite marr ix
+{-# INLINE write_ #-}
+
 -- | /O(1)/ - Same as `write`, but throws `IndexOutOfBoundsException` on an invalid index.
 --
 -- @since 0.4.0
@@ -946,6 +959,20 @@
     else return Nothing
 {-# INLINE modify #-}
 
+-- | /O(1)/ - Same as `modify`, except that neither the previous value, nor any
+-- information on whether the modification was successful are returned. In other words,
+-- just like `modifyM_`, but doesn't throw an exception.
+--
+-- @since 0.4.4
+modify_ ::
+     (Mutable r ix e, PrimMonad m)
+  => MArray (PrimState m) r ix e -- ^ Array to mutate.
+  -> (e -> m e) -- ^ Monadic action that modifies the element
+  -> ix -- ^ Index at which to perform modification.
+  -> m ()
+modify_ marr f ix = when (isSafeIndex (msize marr) ix) $ void $ unsafeModify marr f ix
+{-# INLINE modify_ #-}
+
 -- | /O(1)/ - Modify an element in the cell of a mutable array with a supplied
 -- action. Throws an `IndexOutOfBoundsException` exception for invalid index and returns
 -- the previous value otherwise.
@@ -997,18 +1024,29 @@
 {-# DEPRECATED modify' "In favor of more general `modifyM`" #-}
 
 
--- | /O(1)/ - Same as `swapM`, but instead of thropwing an exception returns `Nothing` when
+-- | /O(1)/ - Same as `swapM`, but instead of throwing an exception returns `Nothing` when
 -- either one of the indices is out of bounds and `Just` elements under those indices
 -- otherwise.
 --
 -- @since 0.1.0
 swap :: (Mutable r ix e, PrimMonad m) => MArray (PrimState m) r ix e -> ix -> ix -> m (Maybe (e, e))
 swap marr ix1 ix2 =
-  let sz = msize marr
+  let !sz = msize marr
    in if isSafeIndex sz ix1 && isSafeIndex sz ix2
         then Just <$> unsafeSwap marr ix1 ix2
         else pure Nothing
 {-# INLINE swap #-}
+
+
+-- | /O(1)/ - Same as `swap`, but instead of returning `Nothing` it does nothing. In other
+-- words, it is similar to `swapM_`, but does not throw any exceptions.
+--
+-- @since 0.4.4
+swap_ :: (Mutable r ix e, PrimMonad m) => MArray (PrimState m) r ix e -> ix -> ix -> m ()
+swap_ marr ix1 ix2 =
+  let !sz = msize marr
+   in when (isSafeIndex sz ix1 && isSafeIndex sz ix2) $ void $ unsafeSwap marr ix1 ix2
+{-# INLINE swap_ #-}
 
 -- | /O(1)/ - Swap two elements in a mutable array under the supplied indices. Throws an
 -- `IndexOutOfBoundsException` when either one of the indices is out of bounds and
diff --git a/src/Data/Massiv/Array/Numeric/Integral.hs b/src/Data/Massiv/Array/Numeric/Integral.hs
--- a/src/Data/Massiv/Array/Numeric/Integral.hs
+++ b/src/Data/Massiv/Array/Numeric/Integral.hs
@@ -30,7 +30,6 @@
   , fromFunction
   -- ** Sampled at the midpoint
   , fromFunctionMidpoint
-  -- * Helper functions
   ) where
 
 import           Data.Coerce
diff --git a/src/Data/Massiv/Array/Ops/Construct.hs b/src/Data/Massiv/Array/Ops/Construct.hs
--- a/src/Data/Massiv/Array/Ops/Construct.hs
+++ b/src/Data/Massiv/Array/Ops/Construct.hs
@@ -258,7 +258,7 @@
 -- | Create an array with random values by using a pure splittable random number generator
 -- such as one provided by either [splitmix](https://www.stackage.org/package/splitmix) or
 -- [random](https://www.stackage.org/package/random) packages. If you don't have a
--- splittable generator consider using `randomArrayS` or `randomArrayIO` instead.
+-- splittable generator consider using `randomArrayS` or `randomArrayWS` instead.
 --
 -- Because of the pure nature of the generator and its splitability we are not only able
 -- to parallelize the random value generation, but also guarantee that it will be
diff --git a/src/Data/Massiv/Array/Ops/Sort.hs b/src/Data/Massiv/Array/Ops/Sort.hs
--- a/src/Data/Massiv/Array/Ops/Sort.hs
+++ b/src/Data/Massiv/Array/Ops/Sort.hs
@@ -10,16 +10,53 @@
 -- Portability : non-portable
 --
 module Data.Massiv.Array.Ops.Sort
-  ( quicksort
+  ( tally
+  , quicksort
   , quicksortM_
   , unsafeUnstablePartitionRegionM
   ) where
 
 import Control.Monad (when)
 import Control.Scheduler
+import Data.Massiv.Array.Delayed.Stream
 import Data.Massiv.Array.Mutable
+import Data.Massiv.Array.Ops.Transform
 import Data.Massiv.Core.Common
 import System.IO.Unsafe
+
+-- | Count how many occurance of each element there is in the array. Results will be
+-- sorted in ascending order of the element.
+--
+-- ==== __Example__
+--
+-- >>> import Data.Massiv.Array as A
+-- >>> xs = fromList Seq [2, 4, 3, 2, 4, 5, 2, 1] :: Array P Ix1 Int
+-- >>> xs
+-- Array P Seq (Sz1 8)
+--   [ 2, 4, 3, 2, 4, 5, 2, 1 ]
+-- >>> tally xs
+-- Array DS Seq (Sz1 5)
+--   [ (1,1), (2,3), (3,1), (4,2), (5,1) ]
+--
+-- @since 0.4.4
+tally :: (Mutable r Ix1 e, Resize r ix, Load r ix e, Ord e) => Array r ix e -> Array DS Ix1 (e, Int)
+tally arr
+  | isEmpty arr = setComp (getComp arr) empty
+  | otherwise = catMaybesS $ unfoldrN (sz + 1) count (0, 0, sorted ! 0)
+  where
+    sz@(Sz k) = size sorted
+    count (!i, !n, !prev)
+      | i < k =
+        let !e' = unsafeLinearIndex sorted i
+         in if prev == e'
+              then Just (Nothing, (i + 1, n + 1, prev))
+              else Just (Just (prev, n), (i + 1, 1, e'))
+      | otherwise = Just (Just (prev, n), (i + 1, n, prev))
+    {-# INLINE count #-}
+    sorted = quicksort $ flatten arr
+{-# INLINE tally #-}
+
+
 
 -- | Partition a segment of a vector. Starting and ending indices are unchecked.
 --
diff --git a/src/Data/Massiv/Array/Ops/Transform.hs b/src/Data/Massiv/Array/Ops/Transform.hs
--- a/src/Data/Massiv/Array/Ops/Transform.hs
+++ b/src/Data/Massiv/Array/Ops/Transform.hs
@@ -45,8 +45,10 @@
   , unsnocM
   -- , lastM
   -- , last'
+  , appendOuterM
   , appendM
   , append'
+  , concatOuterM
   , concatM
   , concat'
   , splitAtM
@@ -58,6 +60,7 @@
   , upsample
   , downsample
   -- ** Zoom
+  , zoom
   , zoomWithGrid
   -- ** Transform
   , transformM
@@ -886,7 +889,14 @@
 -- ==== __Example__
 --
 -- >>> import Data.Massiv.Array as A
--- >>> zoomWithGrid 0 (Stride (2 :. 3)) $ resize' (Sz2 3 2) (Ix1 1 ... 6)
+-- >>> arr = resize' (Sz2 3 2) (Ix1 1 ... 6)
+-- >>> arr
+-- Array D Seq (Sz (3 :. 2))
+--   [ [ 1, 2 ]
+--   , [ 3, 4 ]
+--   , [ 5, 6 ]
+--   ]
+-- >>> zoomWithGrid 0 (Stride (2 :. 3)) arr
 -- Array DL Seq (Sz (10 :. 9))
 --   [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
 --   , [ 0, 1, 1, 1, 0, 2, 2, 2, 0 ]
@@ -918,3 +928,53 @@
     !lastNewIx = liftIndex2 (*) kx $ unSz (size arr)
     !newSz = Sz (liftIndex (+1) lastNewIx)
 {-# INLINE zoomWithGrid #-}
+
+-- | Increaze the size of the array accoridng to the stride multiplier while replicating
+-- the same element to fill the neighbors. It is exactly the same as `zoomWithGrid`, but
+-- without the grid.
+--
+-- ==== __Example__
+--
+-- >>> import Data.Massiv.Array as A
+-- >>> arr = resize' (Sz3 1 3 2) (Ix1 1 ... 6)
+-- >>> arr
+-- Array D Seq (Sz (1 :> 3 :. 2))
+--   [ [ [ 1, 2 ]
+--     , [ 3, 4 ]
+--     , [ 5, 6 ]
+--     ]
+--   ]
+-- >>> zoom (Stride (2 :> 2 :. 3)) arr
+-- Array DL Seq (Sz (2 :> 6 :. 6))
+--   [ [ [ 1, 1, 1, 2, 2, 2 ]
+--     , [ 1, 1, 1, 2, 2, 2 ]
+--     , [ 3, 3, 3, 4, 4, 4 ]
+--     , [ 3, 3, 3, 4, 4, 4 ]
+--     , [ 5, 5, 5, 6, 6, 6 ]
+--     , [ 5, 5, 5, 6, 6, 6 ]
+--     ]
+--   , [ [ 1, 1, 1, 2, 2, 2 ]
+--     , [ 1, 1, 1, 2, 2, 2 ]
+--     , [ 3, 3, 3, 4, 4, 4 ]
+--     , [ 3, 3, 3, 4, 4, 4 ]
+--     , [ 5, 5, 5, 6, 6, 6 ]
+--     , [ 5, 5, 5, 6, 6, 6 ]
+--     ]
+--   ]
+--
+-- @since 0.4.4
+zoom ::
+     Source r ix e
+  => Stride ix -- ^ Scaling factor
+  -> Array r ix e -- ^ Source array
+  -> Array DL ix e
+zoom (Stride zoomFactor) arr =
+  unsafeMakeLoadArray Seq newSz Nothing $ \scheduler _ writeElement ->
+    iforSchedulerM_ scheduler arr $ \ !ix !e -> do
+      let !kix = liftIndex2 (*) ix zoomFactor
+      mapM_ (\ !ix' -> writeElement (toLinearIndex newSz ix') e) $
+        range Seq kix (liftIndex2 (+) kix zoomFactor)
+  where
+    !lastNewIx = liftIndex2 (*) zoomFactor $ unSz (size arr)
+    !newSz = Sz lastNewIx
+{-# INLINE zoom #-}
