Skip to content

Commit 15b5227

Browse files
Add Extend and Comonad instances (#3)
* Implement Extend and Comonad instances for the zipper * Add Arbitrary instance for ArrayZipper * Implement coarbitrary instance for ArrayZipper * Verify type class instance laws via quickcheck-laws * Fix one bug in implementation * Fix Arbitrary instance to ensure array is not empty * Fix extend instance: focus all elements with other other changes * Remove unuesd imports
1 parent 4508558 commit 15b5227

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

spago.dhall

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ Welcome to a Spago project!
33
You can edit this file as you like.
44
-}
55
{ name = "my-project"
6-
, dependencies = [ "arrays", "psci-support", "spec" ]
6+
, dependencies =
7+
[ "arrays"
8+
, "psci-support"
9+
, "quickcheck"
10+
, "quickcheck-laws"
11+
, "spec"
12+
, "transformers"
13+
]
714
, packages = ./packages.dhall
815
, sources = [ "src/**/*.purs", "test/**/*.purs" ]
916
, license = "MIT"

src/Data/Zipper/ArrayZipper.purs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,19 @@ module Data.Zipper.ArrayZipper
3838

3939
import Prelude
4040

41+
import Control.Comonad (class Comonad)
42+
import Control.Extend (class Extend)
43+
import Control.Monad.Gen (chooseInt)
4144
import Data.Array (findIndex, length, mapWithIndex, unsafeIndex)
45+
import Data.Array.NonEmpty as NEA
4246
import Data.Foldable (class Foldable, foldMapDefaultL, foldl, foldr)
4347
import Data.FoldableWithIndex (class FoldableWithIndex, foldMapWithIndex, foldlWithIndex, foldrWithIndex)
4448
import Data.FunctorWithIndex (class FunctorWithIndex)
4549
import Data.Maybe (Maybe(..), fromMaybe)
4650
import Data.Traversable (class Traversable, sequenceDefault, traverse)
4751
import Data.TraversableWithIndex (class TraversableWithIndex, traverseWithIndex)
4852
import Partial.Unsafe (unsafePartial)
53+
import Test.QuickCheck.Arbitrary (class Arbitrary, class Coarbitrary, arbitrary, coarbitrary)
4954

5055
-- | An immutable Zipper for an Array. Modifications are `O(n)` due to creating
5156
-- | a new array rather than mutating the underlying array. Navigating to a
@@ -89,6 +94,16 @@ instance traversableWithIndex :: TraversableWithIndex Int ArrayZipper where
8994
ar <- traverseWithIndex f r.array
9095
in (ArrayZipper r { array = ar })
9196

97+
instance extendArrayZipper :: Extend ArrayZipper where
98+
extend :: forall b a. (ArrayZipper a -> b) -> ArrayZipper a -> ArrayZipper b
99+
extend f (ArrayZipper rec) =
100+
let allFoci idx _ = f (ArrayZipper rec { focusIndex = idx })
101+
in ArrayZipper (rec { array = mapWithIndex allFoci rec.array})
102+
103+
instance comonadArrayZipper :: Comonad ArrayZipper where
104+
extract :: forall a. ArrayZipper a -> a
105+
extract = getFocus
106+
92107
-- | Creates an Array Zipper from a single element. This will be stored
93108
-- | internally as a 1-element array. To further build upon this array,
94109
-- | see `push*` functions.
@@ -281,3 +296,17 @@ pushNextRefocus a (ArrayZipper r) =
281296
, maxIndex = r.maxIndex + 1
282297
, array = unsafeInsertAt (r.focusIndex + 1) a r.array
283298
}
299+
300+
-- Test-related items
301+
instance arbitraryArrayZipper :: Arbitrary a => Arbitrary (ArrayZipper a) where
302+
arbitrary = do
303+
array <- NEA.toArray <$> arbitrary
304+
let maxIndex = length array - 1
305+
focusIndex <- chooseInt 0 maxIndex
306+
pure $ ArrayZipper { array, focusIndex, maxIndex }
307+
308+
instance coarbitraryArrayZipper :: Coarbitrary a => Coarbitrary (ArrayZipper a) where
309+
coarbitrary (ArrayZipper r) =
310+
coarbitrary r.array >>>
311+
coarbitrary r.maxIndex >>>
312+
coarbitrary r.focusIndex

test/Data/Zipper/ArrayZipper.purs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ import Prelude
44

55
import Data.Maybe (Maybe(..), fromJust)
66
import Data.Zipper.ArrayZipper (ArrayZipper, asArrayZipper, getFocus, modifyFocus, next, prev, pushNextRefocus, pushPrevRefocus, setFocus, shiftFocusBy, shiftFocusBy', shiftFocusByFind, shiftFocusByFind', shiftFocusTo, shiftFocusTo', shiftFocusFirst, shiftFocusLast, toArrayZipperAt, toArrayZipperAt', toArrayZipperFirst, toArrayZipperLast)
7+
import Effect.Class (liftEffect)
78
import Partial.Unsafe (unsafePartial)
9+
import Test.QuickCheck.Laws as Laws
10+
import Test.QuickCheck.Laws.Control (checkComonad, checkExtend)
11+
import Test.QuickCheck.Laws.Data (checkEq, checkFoldable, checkFoldableFunctor, checkFunctor, checkFunctorWithIndex, checkOrd)
812
import Test.Spec (Spec, describe, it)
913
import Test.Spec.Assertions (shouldEqual)
14+
import Type.Proxy (Proxy(..), Proxy2(..))
1015

1116
spec :: Spec Unit
1217
spec = describe "Array Zipper" do
@@ -138,6 +143,26 @@ spec = describe "Array Zipper" do
138143
pushNextRefocus 10 i0 `shouldEqual` mkZipper 1 [0, 10, 1, 2, 3, 4]
139144
pushNextRefocus 10 i4 `shouldEqual` mkZipper 5 [0, 1, 2, 3, 4, 10]
140145

146+
describe "Laws" do
147+
it "Eq" do
148+
liftEffect $ checkEq proxy1
149+
it "Ord" do
150+
liftEffect $ checkOrd proxy1
151+
152+
it "Functor" do
153+
liftEffect $ checkFunctor proxy2
154+
it "FunctorWithIndex" do
155+
liftEffect $ checkFunctorWithIndex proxy2
156+
it "Foldable via foldl/foldr" do
157+
liftEffect $ checkFoldable proxy2
158+
it "Foldable via foldMap" do
159+
liftEffect $ checkFoldableFunctor proxy2
160+
161+
it "Extend" do
162+
liftEffect $ checkExtend proxy2
163+
it "Comonad" do
164+
liftEffect $ checkComonad proxy2
165+
141166
where
142167
emptyArray :: Array Int
143168
emptyArray = []
@@ -156,3 +181,7 @@ spec = describe "Array Zipper" do
156181

157182
i4 :: ArrayZipper Int
158183
i4 = unsafePartial $ fromJust $ toArrayZipperAt 4 a4
184+
185+
proxy1 = Proxy :: Proxy (ArrayZipper Laws.A)
186+
187+
proxy2 = Proxy2 :: Proxy2 ArrayZipper

0 commit comments

Comments
 (0)