on master help sign in
Add List and String types
commitee0c73b22c2c01c612fc6fa4c64a7895a2cbc87b authorHarendra Kumar <harendra.kumar@gmail.com> dateFri 16 Nov 2018, 13:41 (8 years ago) parentc28043f
4 files changed+239−69
src/Streamly/List.hs+127 src/Streamly/String.hs+28 streamly.cabal+2 test/PureStreams.hs+82−69
src/Streamly/List.hsnew file+127
@@ -0,0 +1,127 @@
1+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE PatternSynonyms #-}
3+{-# LANGUAGE TypeFamilies #-}
4+{-# LANGUAGE UndecidableInstances #-} -- XXX
5+{-# LANGUAGE ViewPatterns #-}
6+
7+-- |
8+-- Module : Streamly.List
9+-- Copyright : (c) 2018 Composewell Technologies
10+--
11+-- License : BSD3
12+-- Maintainer : harendra.kumar@gmail.com
13+-- Stability : experimental
14+-- Portability : GHC
15+--
16+-- The 'List' type in this module is just a newtype wrapper around the stream
17+-- type @SerialT Identity@. It provides better performance compared to the
18+-- standard Haskell lists and can be used almost as a drop-in replacement. The
19+-- type @List a@ is equivalent to @[a]@ with the following correspondences:
20+--
21+-- * constructor @[]@ corresponds to 'Nil'
22+-- * constructor @:@ corresponds to 'Cons'
23+--
24+-- If you use 'OverloadedLists', 'OverloadedStrings' and 'MonadComprehensions'
25+-- extensions then the standard list, string and list comprehension syntax can
26+-- be used. 'toList' and 'fromList' from the 'IsList' typeclass can be used to
27+-- convert to and from standard lists. Conversion to stream types is free.
28+-- Stream combinators can be used on lists by converting them to streams, such
29+-- as:
30+--
31+-- @
32+-- fromSerial $ S.map (+ 1) $ toSerial (1 \`Cons\` Nil)
33+-- @
34+--
35+-- This module provides combinators that work directly on the 'List' type and
36+-- therefore no conversion is needed.
37+--
38+-- See <docs/streamly-vs-lists.md> for more details and
39+-- <test/PureStreams.hs> for comprehensive usage examples.
40+--
41+module Streamly.List
42+ ( List
43+ , pattern Nil
44+ , pattern Cons
45+ , fromSerial
46+ , toSerial
47+ )
48+where
49+
50+import Control.Arrow (second)
51+import Control.DeepSeq (NFData(..), NFData1(..))
52+import Data.Functor.Identity (Identity, runIdentity)
53+import GHC.Exts (IsList(..), IsString(..))
54+import Text.Read (Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec,
55+ readListPrecDefault)
56+
57+import Streamly.Streams.Serial (SerialT)
58+import qualified Streamly.Streams.Prelude as P
59+import qualified Streamly.Streams.StreamK as K
60+
61+-- We implement list as a newtype instead of a type synonym to make type
62+-- inference easier when using -XOverloadedLists and -XOverloadedStrings. When
63+-- using a stream type the programmer needs to specify the Monad otherwise the
64+-- type remains ambiguous.
65+--
66+-- | A better replacement for the standard lists in 'base'.
67+--
68+-- @since 0.5.3
69+newtype List a = List { unList :: SerialT Identity a }
70+ deriving (Eq, Ord, IsList, NFData, NFData1
71+ , Semigroup, Monoid, Functor, Applicative, Monad)
72+
73+instance Show a => Show (List a) where {
74+ showsPrec p dl = showParen (p > 10) $
75+ showString "fromList " . shows (toList dl) };
76+
77+instance Read a => Read (List a) where {
78+ readPrec = parens $ prec 10 $ do {
79+ Ident "fromList" <- lexP;
80+ dl <- readPrec;
81+ return (fromList dl) };
82+ readListPrec = readListPrecDefault };
83+
84+instance (a ~ Char) => IsString (List a) where
85+ {-# INLINE fromString #-}
86+ fromString = List . P.fromList
87+
88+------------------------------------------------------------------------------
89+-- Patterns
90+------------------------------------------------------------------------------
91+
92+-- | An empty list constructor and pattern that matches an empty 'List'.
93+-- Corresponds to '[]' for Haskell lists.
94+--
95+-- @since 0.5.3
96+pattern Nil :: List a
97+pattern Nil <- (runIdentity . K.null . unList -> True) where
98+ Nil = List K.nil
99+
100+-- | A list constructor and pattern that deconstructs a 'List' into its head
101+-- and tail. Corresponds to ':' for Haskell lists.
102+--
103+-- @since 0.5.3
104+pattern Cons :: a -> List a -> List a
105+pattern Cons x xs <-
106+ (fmap (second List) . runIdentity . K.uncons . unList
107+ -> Just (x, xs)) where
108+ Cons x xs = List $ K.cons x (unList xs)
109+
110+------------------------------------------------------------------------------
111+-- Conversion to and from stream
112+------------------------------------------------------------------------------
113+
114+-- We do not expose the newtype wrapper as that would require the show
115+-- instance to show it which makes show and read more complicated.
116+
117+-- | Convert a 'Serial' stream to a 'List'.
118+--
119+-- @since 0.5.3
120+fromSerial :: SerialT Identity a -> List a
121+fromSerial = List
122+
123+-- | Convert a 'List' to a 'Serial' stream.
124+--
125+-- @since 0.5.3
126+toSerial :: List a -> SerialT Identity a
127+toSerial = unList
src/Streamly/String.hsnew file+28
@@ -0,0 +1,28 @@
1+-- |
2+-- Module : Streamly.String
3+-- Copyright : (c) 2018 Composewell Technologies
4+--
5+-- License : BSD3
6+-- Maintainer : harendra.kumar@gmail.com
7+-- Stability : experimental
8+-- Portability : GHC
9+--
10+-- The 'String' type in this module is just a synonym for the type @List Char@.
11+-- It provides better performance compared to the standard Haskell @String@
12+-- type and can be used almost as a drop-in replacement, especially when used
13+-- with @OverloadedStrings@ extension, with little differences.
14+--
15+-- See "Streamly.List", <docs/streamly-vs-lists.md> for more details and
16+-- <test/PureStreams.hs> for comprehensive usage examples.
17+--
18+--
19+module Streamly.String
20+ (
21+ String
22+ )
23+where
24+
25+import Streamly.List (List)
26+import Prelude hiding (String)
27+
28+type String = List Char
streamly.cabal+2
@@ -142,6 +142,8 @@ library
142142 , Streamly.Streams.Combinators
143143
144144 exposed-modules: Streamly.Prelude
145+ , Streamly.List
146+ , Streamly.String
145147 , Streamly.Time
146148 , Streamly
147149 , Streamly.Tutorial
test/PureStreams.hs+82−69
@@ -1,4 +1,5 @@
11{-# LANGUAGE FlexibleInstances #-}
2+{-# LANGUAGE MonadComprehensions #-}
23{-# LANGUAGE TypeFamilies #-}
34{-# LANGUAGE OverloadedStrings #-}
45{-# LANGUAGE OverloadedLists #-}
@@ -9,79 +10,91 @@
910module Main (main) where
1011
1112import Test.Hspec
12−import Data.Functor.Identity
1313import qualified GHC.Exts as GHC
14−import Prelude hiding (String)
1514
15+import Data.Functor.Identity
1616import Streamly
17+import Streamly.List (pattern Cons, pattern Nil)
1718import qualified Streamly.Prelude as S
18−
19−type List = SerialT Identity
20−type String = List Char
21−
22−------------------------------------------------------------------------------
23−-- Patterns
24−------------------------------------------------------------------------------
25−
26−-- | A pattern that matches an empty pure stream.
27−pattern Nil :: SerialT Identity a
28−pattern Nil <- (runIdentity . S.null -> True) where
29− Nil = S.nil
30−
31−-- | A pattern that deconstructs a pure stream into its head and tail.
32−pattern Cons :: a -> SerialT Identity a -> SerialT Identity a
33−pattern Cons x xs <- (runIdentity . S.uncons -> Just (x, xs)) where
34− Cons x xs = S.cons x xs
19+import qualified Streamly.List as S
3520
3621main :: IO ()
3722main = hspec $ do
38− -- Need type annotation to resolve the monad ambiguity
39− -- Use toList or runIdentity . S.toList to convert to Haskell lists
40−
41− it "overloaded lists" $ do
42− ([1..3] :: Main.List Int) `shouldBe` S.fromList [1..3]
43− GHC.toList ([1..3] :: Main.List Int) `shouldBe` [1..3]
44−
45− it "overloaded strings" $ do
46− ("hello" :: Main.String) `shouldBe` S.fromList "hello"
47−
48− it "pattern match constructs a list" $ do
49− ('x' `Cons` Nil) `shouldBe` ('x' `S.cons` S.nil :: Main.String)
50− (1 `Cons` [2] :: Main.List Int) `shouldBe` S.fromList [1,2]
51−
52− it "pattern match on empty string" $
53− case "" :: Main.String of
54− Nil -> return ()
55− _ -> expectationFailure "not reached"
56−
57− it "pattern matches on singleton string" $
58− case "a" :: Main.String of
59− Cons x Nil -> x `shouldBe` 'a'
60− _ -> expectationFailure "not reached"
61−
62− it "pattern matches on non-empty string" $
63− case "hello" <> "world" :: Main.String of
64− Cons x1 (Cons x2 xs) -> do
65− x1 `shouldBe` 'h'
66− x2 `shouldBe` 'e'
67− xs `shouldBe` "lloworld"
68− _ -> expectationFailure "not reached"
69−
70− it "pattern match on non-empty list" $
71− case S.map (+1) [1..10] :: Main.List Int of
72− Cons x xs -> do
73− x `shouldBe` 2
74− xs `shouldBe` [3..11]
75− _ -> expectationFailure "not reached"
76−
77− it "Shows as string" $ do
78− show (S.fromList [1..3] :: Main.List Int) `shouldBe` "fromList [1,2,3]"
79−
80− it "Reads from a string" $ do
81− (read "fromList [1,2,3]" :: Main.List Int) `shouldBe` S.fromList [1..3]
82−
83− it "Eq instance" $ do
84− (S.fromList [1,2,3] :: Main.List Int) == S.fromList [1,2,3] `shouldBe` True
85−
86− it "Ord instance" $ do
87− (S.fromList [1,2,3] :: Main.List Int) > S.fromList [1,2,1] `shouldBe` True
23+ describe "OverloadedLists for 'SerialT Identity' type" $ do
24+ it "overloaded lists" $ do
25+ ([1..3] :: SerialT Identity Int) `shouldBe` S.fromList [1..3]
26+ GHC.toList ([1..3] :: SerialT Identity Int) `shouldBe` [1..3]
27+
28+ it "Show instance" $ do
29+ show (S.fromList [1..3] :: SerialT Identity Int)
30+ `shouldBe` "fromList [1,2,3]"
31+ it "Read instance" $ do
32+ (read "fromList [1,2,3]" :: SerialT Identity Int) `shouldBe` [1..3]
33+
34+ it "Eq instance" $ do
35+ ([1,2,3] :: SerialT Identity Int) == [1,2,3] `shouldBe` True
36+
37+ it "Ord instance" $ do
38+ ([1,2,3] :: SerialT Identity Int) > [1,2,1] `shouldBe` True
39+
40+ it "Monad comprehension" $ do
41+ [(x,y) | x <- [1..2], y <- [1..2]] `shouldBe`
42+ ([(1,1), (1,2), (2,1), (2,2)] :: SerialT Identity (Int, Int))
43+
44+ describe "OverloadedLists for List type" $ do
45+ it "overloaded lists" $ do
46+ [1..3 :: Int] `shouldBe` S.fromSerial (S.fromList [1..3])
47+ GHC.toList ([1..3] :: S.List Int) `shouldBe` [1..3]
48+
49+ it "pattern constructor constructs a list" $ do
50+ ('x' `Cons` Nil) `shouldBe` ['x']
51+ (1 `Cons` [2 :: Int]) `shouldBe` [1,2]
52+
53+ it "pattern match on non-empty list" $
54+ case [1..10 :: Int] of
55+ Cons x xs -> do
56+ x `shouldBe` 1
57+ xs `shouldBe` [2..10]
58+ _ -> expectationFailure "not reached"
59+
60+ it "Show instance" $ do
61+ show ([1..3] :: S.List Int) `shouldBe` "fromList [1,2,3]"
62+
63+ it "Read instance" $ do
64+ (read "fromList [1,2,3]" :: S.List Int) `shouldBe` [1..3]
65+
66+ it "Eq instance" $ do
67+ ([1,2,3] :: S.List Int) == [1,2,3] `shouldBe` True
68+
69+ it "Ord instance" $ do
70+ ([1,2,3] :: S.List Int) > [1,2,1] `shouldBe` True
71+
72+ it "Monad comprehension" $ do
73+ [(x,y) | x <- [1..2], y <- [1..2]] `shouldBe`
74+ ([(1,1), (1,2), (2,1), (2,2)] :: S.List (Int, Int))
75+
76+ describe "OverloadedStrings for 'SerialT Identity' type" $ do
77+ it "overloaded strings" $ do
78+ ("hello" :: SerialT Identity Char) `shouldBe` S.fromList "hello"
79+
80+ describe "OverloadedStrings for List type" $ do
81+ it "overloaded strings" $ do
82+ "hello" `shouldBe` S.fromSerial (S.fromList "hello")
83+
84+ it "pattern match on empty string" $
85+ case "" of
86+ Nil -> return ()
87+ _ -> expectationFailure "not reached"
88+
89+ it "pattern matches on singleton string" $
90+ case "a" of
91+ Cons x Nil -> x `shouldBe` 'a'
92+ _ -> expectationFailure "not reached"
93+
94+ it "pattern matches on non-empty string" $
95+ case "hello" <> "world" of
96+ Cons x1 (Cons x2 xs) -> do
97+ x1 `shouldBe` 'h'
98+ x2 `shouldBe` 'e'
99+ xs `shouldBe` "lloworld"
100+ _ -> expectationFailure "not reached"