packages feed

short-vec-lens-0.1.0.0: src/Data/Vec/Short/Lens.hs

-- Copyright 2018-2021 Google LLC
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--      http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

-- | Lenses and related optics for 'Vec's.

-- Work around <https://ghc.haskell.org/trac/ghc/ticket/14511>
{-# OPTIONS_GHC -fno-float-in #-}
{-# OPTIONS_GHC -Wno-orphans #-}

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.Vec.Short.Lens
    (
    -- ** List-based lenses
      list
    -- ** Arity-based lenses
    , paired

    -- * List-like operators
    -- ** Constructor views
    -- *** The cons view
    , consed
    -- *** The snoc view
    , snoced
    -- ** Operator views
    -- *** The append view
    , chopped
    -- *** The concat view
    , subVecs
    -- *** The reverse view
    , reversed
    -- *** The transposition view
    , vtransposed

    -- * Misc lenses
    , midElem, ixElem, ix
    , sliced
    , rotated
    , vdiagonal
    ) where

import Prelude hiding ((++), concat, concatMap, iterate)

import qualified Data.Foldable as F
import GHC.ST (runST)
import GHC.TypeLits (KnownNat, type (+), type (<=), type (-))
import qualified GHC.TypeLits

import Control.Lens (Iso, Lens', iso, lens, from, swapped)

import Data.Fin.Int.Explicit (Fin, complementFin, finToInt, unsafeFin)
import Data.SInt (sintVal, unSInt)
import Data.Vec.Short.Internal

#if !MIN_VERSION_lens(5,0,0)
import qualified Control.Lens as L
import Data.Foldable.WithIndex (FoldableWithIndex(..))
import Data.Traversable.WithIndex (TraversableWithIndex(..))
#endif

--------------------------------------------------------------------------------

-- | A list (of the right length) is isomorphic to a vector.
-- The list-to-vector direction is partial.
list :: (KnownNat n) => Iso (Vec n a) (Vec n b)
                            [a]       [b]
list = iso F.toList (fromList sintVal)

--------------------------------------------------------------------------------

-- | Lens on a single element.
ix :: Fin n -> Lens' (Vec n a) a
ix i = i `seq` lens (! i) (upd i)
{-# INLINE ix #-}

----------------

-- | An isomorphism with an element added at the beginning of a vector.
consed :: Iso (a, Vec n a)    (b, Vec m b)
              (Vec (n + 1) a) (Vec (m + 1) b)
consed = from (unsafeIxElem (const 0))

-- | An isomorphism with an element added at the end of a vector.
snoced :: Iso (Vec n a, a)    (Vec m b, b)
              (Vec (n + 1) a) (Vec (m + 1) b)
snoced = from (unsafeIxElem id . swapped)

-- | Isomorphism between a vector with and without its middle element.
midElem :: forall m n a b. Iso (Vec (n+1) a) (Vec (m+1) b)
                               (a, Vec n a)  (b, Vec m b)
midElem = unsafeIxElem (`quot` 2)

----------------

-- | An isomorphism with a 'split' vector.
chopped :: (KnownNat m) => Iso (Vec (m + n) a)    (Vec (o + p) b)
                               (Vec m a, Vec n a) (Vec o b, Vec p b)
chopped = iso (split sintVal) (uncurry (++))

-- | A vector can be split (isomorphically) into a vector of vectors.
subVecs :: (KnownNat m, (n GHC.TypeLits.* m) ~ nm, (p GHC.TypeLits.* o) ~ po)
        => Iso (Vec nm a)        (Vec po b)
               (Vec n (Vec m a)) (Vec p (Vec o b))
subVecs = iso (reshape sintVal) concat

-- | A vector is isomorphic to its reversal.
reversed :: Iso (Vec n a) (Vec m b)
                (Vec n a) (Vec m b)
reversed = iso rev rev

--------------------------------
-- Other misc lenses

-- Unsafe version of ixElem, but with a function computing the
-- index from @valueOf @n@.
-- This allows unsafeIxElem to be used easily for midElem and snoced.
unsafeIxElem :: (Int -> Int) -> Iso (Vec (n+1) a) (Vec (m+1) b)
                                    (a, Vec n a)  (b, Vec m b)
unsafeIxElem fi = iso getf setf
  where getf xs =
          let !i = fi (vSize xs - 1)
          in  unsafeIndexK xs i $ \ xi ->
                let !xs' = unsafeRemove i xs
                in  (xi, xs')
        setf (xi, xs) =
          let !i = fi (vSize xs)
          in  unsafeInsert i xi xs
{-# INLINE unsafeIxElem #-}

-- | Isomorphism between a vector with and without a single element at
-- the given index.
ixElem :: forall n a b. Fin (n+1) -> Iso (Vec (n+1) a) (Vec (n+1) b)
                                         (a, Vec n a)  (b, Vec n b)
ixElem i = unsafeIxElem (const (finToInt i))


-- | A lens to a slice of the vector.
sliced
    :: forall m n a
    .  (KnownNat m, KnownNat n, m <= n)
    => Fin (n - m + 1)
    -> Lens' (Vec n a) (Vec m a)
sliced (finToInt -> !start) = lens getf setf
    where
    !m    = unSInt @m sintVal
    !n    = unSInt @n sintVal
    !end  = start + m
    !rest = n - end -- the length of the post-slice portion of the vector

    getf xs    = sliceVec xs start sintVal
    setf xs ys =
        createVec sintVal $ \mv -> do
            unsafeCopyVec xs 0   mv 0     start
            unsafeCopyVec ys 0   mv start m
            unsafeCopyVec xs end mv end   rest


-- | A two-element vector is isomorphic to a pair.
paired :: Iso (Vec 2 a) (Vec 2 b) (a, a) (b, b)
paired = iso unvec2 (uncurry vec2)
    where
    unvec2 v =
      indexK v (unsafeFin @Int 0) $ \x ->
      indexK v (unsafeFin @Int 1) $ \y -> (x,y)

-- | Isomorphism between a vector and a vector rotated @i@ steps.
-- The element at index 0 in the first vector is at index @i@ in the second.
-- E.g., @view (rotated 1) (fromList "ABCD") == fromList "DABC"@
rotated :: forall n a b. Fin n -> Iso (Vec n a) (Vec n b) (Vec n a) (Vec n b)
rotated i = iso (rot i) (\v -> rot (complementFin (svSize v) i) v)
{-# INLINE rotated #-}

-- | Isomorphism of transposed vectors.
vtransposed :: (KnownNat m, KnownNat p)
            => Iso (Vec n (Vec m a)) (Vec p (Vec o b))
                   (Vec m (Vec n a)) (Vec o (Vec p b))
vtransposed = iso (vtranspose sintVal) (vtranspose sintVal)

-- TODO: KnownNat not needed.
-- | Lens on the main diagonal.
vdiagonal :: forall n a. KnownNat n => Lens' (Vec n (Vec n a)) (Vec n a)
vdiagonal = lens getf setf
    where
    getf :: Vec n (Vec n a) -> Vec n a
    getf = imap (flip (!))

    setf :: Vec n (Vec n a) -> Vec n a -> Vec n (Vec n a)
    setf m d =
        mkVec sintVal $ \i ->
            indexK m i $ \mi ->
            indexK d i $ \di ->
            runST $ do
                mi' <- safeThawMV mi
                writeMV mi' i di
                unsafeFreezeMV mi'

#if !MIN_VERSION_lens(5,0,0)
instance L.FunctorWithIndex (Fin n) (Vec n) where imap = imap
instance KnownNat n => L.FoldableWithIndex (Fin n) (Vec n) where
  ifoldMap = ifoldMap
instance KnownNat n => L.TraversableWithIndex (Fin n) (Vec n) where
  itraverse = itraverse
#endif