linear-base-0.1.0: src/Data/Array/Mutable/Unlifted/Linear.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UnliftedNewtypes #-}
-- |
-- This module provides an unlifted mutable array with a pure
-- interface. Though the array itself is unlifted, it's elements are
-- lifted types. This is made possible by using linear types to make
-- sure array references are single threaded through reads and writes.
--
-- Accessing out-of-bounds indices causes undefined behaviour.
--
-- This module is meant to be imported qualified.
module Data.Array.Mutable.Unlifted.Linear
( Array#
, unArray#
, alloc
, allocBeside
, lseq
, size
, get
, set
, copyInto
, map
, toList
, freeze
, dup2
) where
import Data.Unrestricted.Linear hiding (lseq, dup2)
import Prelude (Int)
import qualified Prelude as Prelude
import qualified Unsafe.Linear as Unsafe
import qualified GHC.Exts as GHC
-- | A mutable array holding @a@s
newtype Array# a = Array# (GHC.MutableArray# GHC.RealWorld a)
-- | Extract the underlying 'GHC.MutableArray#', consuming the 'Array#'
-- in process.
unArray# :: (GHC.MutableArray# GHC.RealWorld a -> b) -> Array# a %1-> Ur b
unArray# f = Unsafe.toLinear (\(Array# a) -> Ur (f a))
-- | Consume an 'Array#'.
--
-- Note that we can not implement a 'Consumable' instance because 'Array#'
-- is unlifted.
lseq :: Array# a %1-> b %1-> b
lseq = Unsafe.toLinear2 (\_ b -> b)
-- | Allocate a mutable array of given size using a default value.
--
-- The size should be non-negative.
alloc :: Int -> a -> (Array# a %1-> Ur b) %1-> Ur b
alloc (GHC.I# s) a f =
let new = GHC.runRW# Prelude.$ \st ->
case GHC.newArray# s a st of
(# _, arr #) -> Array# arr
in f new
{-# NOINLINE alloc #-} -- prevents runRW# from floating outwards
-- For the reasoning behind these NOINLINE pragmas, see the discussion at:
-- https://github.com/tweag/linear-base/pull/187#pullrequestreview-489183531
-- | Allocate a mutable array of given size using a default value,
-- using another 'Array#' as a uniqueness proof.
--
-- The size should be non-negative.
allocBeside :: Int -> a -> Array# b %1-> (# Array# a, Array# b #)
allocBeside (GHC.I# s) a orig =
let new = GHC.runRW# Prelude.$ \st ->
case GHC.newArray# s a st of
(# _, arr #) -> Array# arr
in (# new, orig #)
{-# NOINLINE allocBeside #-} -- prevents runRW# from floating outwards
size :: Array# a %1-> (# Ur Int, Array# a #)
size = Unsafe.toLinear go
where
go :: Array# a -> (# Ur Int, Array# a #)
go (Array# arr) =
let !s = GHC.sizeofMutableArray# arr
in (# Ur (GHC.I# s), Array# arr #)
get :: Int -> Array# a %1-> (# Ur a, Array# a #)
get (GHC.I# i) = Unsafe.toLinear go
where
go :: Array# a -> (# Ur a, Array# a #)
go (Array# arr) =
case GHC.runRW# (GHC.readArray# arr i) of
(# _, ret #) -> (# Ur ret, Array# arr #)
{-# NOINLINE get #-} -- prevents the runRW# effect from being reordered
set :: Int -> a -> Array# a %1-> Array# a
set (GHC.I# i) (a :: a) = Unsafe.toLinear go
where
go :: Array# a -> Array# a
go (Array# arr) =
case GHC.runRW# (GHC.writeArray# arr i a) of
_ -> Array# arr
{-# NOINLINE set #-} -- prevents the runRW# effect from being reordered
-- | Copy the first mutable array into the second mutable array, starting
-- from the given index of the source array.
--
-- It copies fewer elements if the second array is smaller than the
-- first. 'n' should be within [0..size src).
--
-- @
-- copyInto n src dest:
-- dest[i] = src[n+i] for i < size dest, i < size src + n
-- @
copyInto :: Int -> Array# a %1-> Array# a %1-> (# Array# a, Array# a #)
copyInto start@(GHC.I# start#) = Unsafe.toLinear2 go
where
go :: Array# a -> Array# a -> (# Array# a, Array# a #)
go (Array# src) (Array# dst) =
let !(GHC.I# len#) = Prelude.min
(GHC.I# (GHC.sizeofMutableArray# src) Prelude.- start)
(GHC.I# (GHC.sizeofMutableArray# dst))
in case GHC.runRW# (GHC.copyMutableArray# src start# dst 0# len#) of
_ -> (# Array# src, Array# dst #)
{-# NOINLINE copyInto #-} -- prevents the runRW# effect from being reordered
map :: (a -> b) -> Array# a %1-> Array# b
map (f :: a -> b) arr =
size arr
`chain2` \(# Ur s, arr' #) -> go 0 s arr'
where
-- When we're mapping an array, we first insert `b`'s
-- inside an `Array# a` by unsafeCoerce'ing, and then we
-- unsafeCoerce the result to an `Array# b`.
go :: Int -> Int -> Array# a %1-> Array# b
go i s arr'
| i Prelude.== s =
Unsafe.toLinear GHC.unsafeCoerce# arr'
| Prelude.otherwise =
get i arr'
`chain2` \(# Ur a, arr'' #) -> set i (Unsafe.coerce (f a)) arr''
`chain` \arr''' -> go (i Prelude.+ 1) s arr'''
{-# NOINLINE map #-}
-- | Return the array elements as a lazy list.
toList :: Array# a %1-> Ur [a]
toList = unArray# Prelude.$ \arr ->
go
0
(GHC.I# (GHC.sizeofMutableArray# arr))
arr
where
go i len arr
| i Prelude.== len = []
| GHC.I# i# <- i =
case GHC.runRW# (GHC.readArray# arr i#) of
(# _, ret #) -> ret : go (i Prelude.+ 1) len arr
-- | /O(1)/ Convert an 'Array#' to an immutable 'GHC.Array#'.
freeze :: (GHC.Array# a -> b) -> Array# a %1-> Ur b
freeze f = unArray# go
where
go mut =
case GHC.runRW# (GHC.unsafeFreezeArray# mut) of
(# _, ret #) -> f ret
-- | Clone an array.
dup2 :: Array# a %1-> (# Array# a, Array# a #)
dup2 = Unsafe.toLinear go
where
go :: Array# a -> (# Array# a, Array# a #)
go (Array# arr) =
case GHC.runRW#
(GHC.cloneMutableArray# arr 0# (GHC.sizeofMutableArray# arr)) of
(# _, new #) -> (# Array# arr, Array# new #)
{-# NOINLINE dup2 #-}
-- * Internal library
-- Below two are variants of (&) specialized for taking commonly used
-- unlifted values and returning a levity-polymorphic result.
--
-- They are not polymorphic on their first parameter since levity-polymorphism
-- disallows binding to levity-polymorphic values.
chain :: forall (r :: GHC.RuntimeRep) a (b :: GHC.TYPE r).
Array# a %1-> (Array# a %1-> b) %1-> b
chain a f = f a
chain2 :: forall (r :: GHC.RuntimeRep) a b (c :: GHC.TYPE r).
(# b, Array# a #) %1-> ((# b, Array# a #) %1-> c) %1-> c
chain2 a f = f a
infixl 1 `chain`, `chain2`