Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add (Int, +) finger trees #766

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions containers-tests/tests/seq-properties.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import Data.Sequence.Internal
, Digit (..)
, node2
, node3
, deep )
, deep
, unsafeMapNode )

import Data.Sequence

Expand Down Expand Up @@ -242,7 +243,7 @@ instance (Sized a, Valid a) => Valid (FingerTree a) where
s == size pr + size m + size sf && valid pr && valid m && valid sf

instance (Sized a, Valid a) => Valid (Node a) where
valid node = size node == sum (fmap size node) && all valid node
valid node = size node == sum (unsafeMapNode size node) && all valid node

instance Valid a => Valid (Digit a) where
valid = all valid
Expand Down
16 changes: 15 additions & 1 deletion containers/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Changelog for [`containers` package](http://github.com/haskell/containers)

## [0.6.4.1]
## 0.6.5.1

* Add support for finger trees with measurements in the `(Int, +)`
monoid.
* Export more `Data.Sequence` internals.
* Add a `Data.Sequence.StableInternal` module exporting functions
intended for use by external packages.
* Remove the `Functor` and `Traversable` instances from the
heretofore "internal" `FingerTree` and `Node` types, in favor
of type-specific mapping functions. These instances could
break data structure invariants.
* Remove the `Generic1 FingerTree` instance, which can no longer
be derived.

## 0.6.4.1

### Bug fixes

Expand Down
3 changes: 3 additions & 0 deletions containers/containers.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Library

exposed-modules:
Data.Containers.ListUtils
Data.FingerTree.IntPlus
Data.FingerTree.IntPlus.Unsafe
Data.IntMap
Data.IntMap.Lazy
Data.IntMap.Strict
Expand All @@ -65,6 +67,7 @@ Library
Data.Sequence
Data.Sequence.Internal
Data.Sequence.Internal.Sorting
Data.Sequence.StableInternal
Data.Tree
Utils.Containers.Internal.BitUtil
Utils.Containers.Internal.BitQueue
Expand Down
155 changes: 155 additions & 0 deletions containers/src/Data/FingerTree/IntPlus.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
{-# LANGUAGE CPP #-}
#include "containers.h"
{-# LANGUAGE BangPatterns #-}

#ifdef DEFINE_PATTERN_SYNONYMS
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
#endif

-- | This module exports a type of finger trees with measurements ("sizes") in
-- the @(Int, +)@ monoid. This type is used to implement sequences in
-- "Data.Sequence". It may occasionally be useful for other purposes.
--
-- Caution: splitting and lookup functions assume that the size of the tree is
-- at most @'maxBound' :: Int@. If this is not the case, then they may produce
-- errors and/or utter nonsense.

module Data.FingerTree.IntPlus
(
#ifdef DEFINE_PATTERN_SYNONYMS
FingerTree (Empty, (:<|), (:|>), Singleton)
#else
FingerTree
#endif
, Elem (..)
, Sized (..)
, Split (..)
, UncheckedSplit (..)
, ViewL (..)
, ViewR (..)
, (<|)
, (|>)
, (><)
, fromList
, viewl
, viewr
, split
, uncheckedSplit
) where

import Data.Sequence.Internal
( FingerTree (..), Sized (..), Elem (..) )
import qualified Data.Sequence.Internal as S
#if !MIN_VERSION_base(4,8,0)
import Data.Word (Word)
#endif

infixr 5 ><
infixr 5 <|, :<
infixl 5 |>, :>

(<|) :: Sized a => a -> FingerTree a -> FingerTree a
(<|) = S.consTree

(|>) :: Sized a => FingerTree a -> a -> FingerTree a
(|>) = S.snocTree

(><) :: Sized a => FingerTree a -> FingerTree a -> FingerTree a
(><) = S.appendTree

fromList :: Sized a => [a] -> FingerTree a
fromList = S.fromListFT

data ViewL a = a :< FingerTree a | EmptyL
data ViewR a = FingerTree a :> a | EmptyR

{-# INLINE viewl #-}
viewl :: Sized a => FingerTree a -> ViewL a
viewl t = case S.viewLTree t of
S.ConsLTree a as -> a :< as
S.EmptyLTree -> EmptyL

{-# INLINE viewr #-}
viewr :: Sized a => FingerTree a -> ViewR a
viewr t = case S.viewRTree t of
S.SnocRTree as a -> as :> a
S.EmptyRTree -> EmptyR

#ifdef DEFINE_PATTERN_SYNONYMS
infixr 5 :<|
infixl 5 :|>

#if __GLASGOW_HASKELL__ >= 801
{-# COMPLETE (:<|), Empty #-}
{-# COMPLETE (:|>), Empty #-}
#endif

-- | A bidirectional pattern synonym matching an empty finger tree.
pattern Empty :: S.FingerTree a
pattern Empty = S.EmptyT

-- | A bidirectional pattern synonym viewing the front of a non-empty
-- finger tree.
pattern (:<|) :: Sized a => a -> FingerTree a -> FingerTree a
pattern x :<| xs <- (viewl -> x :< xs)
where
x :<| xs = x <| xs

-- | A bidirectional pattern synonym viewing the rear of a non-empty
-- finger tree.
pattern (:|>) :: Sized a => FingerTree a -> a -> FingerTree a
pattern xs :|> x <- (viewr -> xs :> x)
where
xs :|> x = xs |> x

-- | A bidirectional pattern synonym for a singleton
-- sequence. @Singleton xs@ is equivalent to @xs :< Empty@.
pattern Singleton :: a -> FingerTree a
pattern Singleton x <- S.Single x
where
Singleton = S.Single
#endif

data Split a
= Split !(FingerTree a) a !(FingerTree a)
| EmptySplit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a debugging function that shows the split lying around? Would be useful to have.


data UncheckedSplit a
= UncheckedSplit !(FingerTree a) a !(FingerTree a)

-- | Split a finger tree around a measurement.
--
-- @split i xs = EmptySplit@ if and only if @xs = Empty@. Given that
--
-- @
-- split i xs = 'Split' l x r
-- @
--
-- it's guaranteed that
--
-- 1. @ xs = l <> (x <| r) @
-- 2. @i >= size l@ or @l = Empty@
-- 3. @i < size l + size x@ or @r = Empty@

split :: Sized a => Int -> FingerTree a -> Split a
split !_i S.EmptyT = EmptySplit
split i ft
| S.Split l m r <- S.splitTree i ft
= Split l m r

-- | Split a nonempty finger tree around a measurement. Given that
--
-- @
-- uncheckedSplit i xs = 'UncheckedSplit' l x r
-- @
--
-- it's guaranteed that
--
-- 1. @ xs = l <> (x <| r) @
-- 2. @i >= size l@ or @l = Empty@
-- 3. @i < size l + size x@ or @r = Empty@
uncheckedSplit :: Sized a => Int -> FingerTree a -> UncheckedSplit a
uncheckedSplit i ft
| S.Split l m r <- S.splitTree i ft
= UncheckedSplit l m r
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this partial function, why not only export split and let users deal with partial patterns if they want?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only concern is whether that extra stuff will inline away. Certain functions are extremely sensitive to the performance of splitting tiny little sequences (e.g., 2–5 elements) where any little extra time/allocation can matter a lot. I'm not sure how hard it would be to rejigger Data.Sequence.Internal.splitTree to make sure that works out okay.

46 changes: 46 additions & 0 deletions containers/src/Data/FingerTree/IntPlus/Unsafe.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{-# LANGUAGE CPP #-}
#include "containers.h"

-- | This module exports functions that can easily
-- produce finger trees violating the annotation invariants.
-- Trees violating these invariants will produce garbage
-- when split.
module Data.FingerTree.IntPlus.Unsafe
( unsafeMap
, unsafeTraverse
) where

import Data.Sequence.Internal
( FingerTree (..), Node (..) )
import qualified Data.Sequence.Internal as S
import Control.Applicative
#if !MIN_VERSION_base(4,8,0)
import Data.Traversable (traverse)
#endif

-- | Map over a 'FingerTree'. The following precondition
-- is assumed but not checked:
--
-- For each @a@ in the @FingerTree@, @size (f a) = size a@.
unsafeMap :: (a -> b) -> FingerTree a -> FingerTree b
unsafeMap = S.unsafeMapFT

-- | Traverse a 'FingerTree'. The following precondition is required
-- but not checked:
--
-- For each element @a@ in the 'FingerTree',
-- @size <$> f a = size a <$ f a@
unsafeTraverse :: Applicative f => (a -> f b) -> FingerTree a -> f (FingerTree b)
unsafeTraverse _ EmptyT = pure EmptyT
unsafeTraverse f (Single x) = Single <$> f x
unsafeTraverse f (Deep v pr m sf) =
liftA3 (Deep v) (traverse f pr) (unsafeTraverse (unsafeTraverseNode f) m) (traverse f sf)

-- | Traverse a 'Node'. The following precondition is required
-- but not checked:
--
-- For each element @a@ in the 'Node',
-- @size <$> f a = size a <$ f a@
unsafeTraverseNode :: Applicative f => (a -> f b) -> Node a -> f (Node b)
unsafeTraverseNode f (Node2 v a b) = liftA2 (Node2 v) (f a) (f b)
unsafeTraverseNode f (Node3 v a b c) = liftA3 (Node3 v) (f a) (f b) (f c)
Loading