Skip to content
Closed
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
23 changes: 22 additions & 1 deletion src/Data/List/ZipList.purs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module Data.List.ZipList
( ZipList(..)
, runZipList
, transpose
) where

import Prelude
Expand All @@ -13,9 +14,11 @@ import Control.Alternative (Alternative)
import Control.Plus (Plus)

import Data.Foldable (Foldable, foldMap, foldl, foldr)
import Data.List.Lazy (List(), repeat, zipWith)
import Data.List.Lazy (List(), repeat, zipWith, fromFoldable, toUnfoldable)
import Data.Monoid (Monoid, mempty)
import Data.Traversable (Traversable, traverse, sequence)
import Data.Foldable (Foldable)
import Data.Unfoldable (Unfoldable)

-- | `ZipList` is a newtype around `List` which provides a zippy
-- | `Applicative` instance.
Expand All @@ -25,6 +28,24 @@ newtype ZipList a = ZipList (List a)
runZipList :: forall a. ZipList a -> List a
runZipList (ZipList xs) = xs

-- | Transpose any structure which is both `Foldable` and `Unfoldable`,
-- | via `ZipList`; note that `sequence` for `ZipList` is the same as
-- | `transpose`.
transpose :: forall f a. (Foldable f, Unfoldable f) => f (f a) -> f (f a)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these overloaded functions have their place, but I'd prefer to expose a List version too, since we're going via that function anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about this, but what about name collisions? There's always straight sequence if you want to avoid all the other stuff, eg for performance.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm just concerned that a beginner won't know to use sequence, and may not understand the more general type signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We already have generalized type signatures for all kinds of things, though? foldr, replicate, (<>), even (+) and (*).

Just so that we're on the same page - are you suggesting we put specialized versions of transpose into Data.List and Data.List.Lazy, as well as this generalized version here?

transpose =
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice implementation 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

In my opinion, such a highly generalized implementation as the above transpose does not belong here, because it has no dependency on Data.List. (One can argue it belongs in Foldable or Unfoldable.)

Copy link
Contributor

Choose a reason for hiding this comment

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

My point is just that there is a difference between

  • a function which is initially written in terms of List and then generalized because Foldable is basically just toList, and
  • a function which is by definition, already generalized to work with some type class.

Foldable is a particularly confusing case since it basically defines a monoid homomorphism to List. But in the first case, a user can always choose to easily generalize to Foldable using a call to toList.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given that I don't have to worry about the core library dependency graph, I won't make any suggestions about where I think this should go, I'll leave that up to you ;)

I will say, though, that I don't see how this could go into foldable-traversable or unfoldable (at least, not without duplicating parts of lists) because this implementation does depend on the Traversable ZipList instance, and we can't depend on ZipList in foldable-traversable or unfoldable because lists already depends on both of those.

Maybe we should just start with a version defined in terms of List and not Foldable, then, and see what happens.

Come to think of it, why does ZipList wrap lazy lists and not strict ones? Would it make sense to have a zippy strict list newtype too?

Copy link
Contributor

Choose a reason for hiding this comment

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

Because pure is infinite 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, lol, of course.

Copy link
Contributor

Choose a reason for hiding this comment

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

Speaking of which, doesn't this diverge for empty input?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, yes it does.

toZipList
>>> map toZipList
>>> sequence
>>> map fromZipList
>>> fromZipList

where
toZipList :: forall a'. f a' -> ZipList a'
toZipList = ZipList <<< fromFoldable

fromZipList :: forall a'. ZipList a' -> f a'
fromZipList = toUnfoldable <<< runZipList

instance showZipList :: (Show a) => Show (ZipList a) where
show (ZipList xs) = "(ZipList " ++ show xs ++ ")"

Expand Down
32 changes: 32 additions & 0 deletions test/Test/Data/List/ZipList.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Test.Data.List.ZipList (testZipList) where

import Prelude
import Control.Monad.Eff (Eff())
import Control.Monad.Eff.Console (CONSOLE(), log)
import Data.Foldable
import Data.Monoid.Additive
import Data.List.Lazy as LazyList
import Data.List.ZipList (ZipList(..), transpose)
import Data.Maybe (Maybe(..), isNothing)
import Data.Maybe.Unsafe (fromJust)
import Data.Tuple (Tuple(..))
import Data.Array as Array
import Test.Assert (ASSERT(), assert)

testZipList :: forall eff. Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit
testZipList = do
log "ZipList Applicative instance should be zippy"
testZipWith (+) [1,2,3] [4,5,6]
testZipWith (*) [1,2,3] [4,5,6]
testZipWith const [1,2,3] [4,5,6]

log "ZipList.transpose"
assert $ transpose [[1,2,3],[4,5,6],[7,8,9]] == [[1,4,7],[2,5,8],[3,6,9]]

testZipWith :: forall a b c eff. (Eq c) => (a -> b -> c) -> Array a -> Array b -> Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit
testZipWith f xs ys =
assert $ (f <$> l xs <*> l ys) == l (Array.zipWith f xs ys)

-- Shortcut for constructing a ZipList
l :: forall a. Array a -> ZipList a
l = ZipList <<< LazyList.fromFoldable
2 changes: 2 additions & 0 deletions test/Test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import Control.Monad.Eff.Console (CONSOLE())
import Test.Assert (ASSERT())
import Test.Data.List
import Test.Data.List.Lazy
import Test.Data.List.ZipList
import Test.Data.List.Unsafe

main :: forall eff. Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit
main = do
testList
testListLazy
testZipList
testListUnsafe