Skip to content

Commit

Permalink
Add rewrite rules for text conversion
Browse files Browse the repository at this point in the history
Closes #212.

I had to add `stringToText` and `textToString` functions in order to
specify inlining pragma as mentioned
[here](https://gitlab.haskell.org/ghc/ghc/issues/12632).

Benchmarks without rewrite rule:

```
benchmarked toText/toString
time                 1.013 ms   (875.1 μs .. 1.148 ms)
                     0.819 R²   (0.691 R² .. 0.904 R²)
mean                 1.621 ms   (1.268 ms .. 2.619 ms)
std dev              2.134 ms   (331.2 μs .. 4.071 ms)
variance introduced by outliers: 97% (severely inflated)
```

And with rewrite rule:

```
benchmarked toText/toString
time                 186.1 μs   (183.3 μs .. 188.4 μs)
                     0.998 R²   (0.996 R² .. 0.999 R²)
mean                 189.4 μs   (187.7 μs .. 191.8 μs)
std dev              7.508 μs   (5.823 μs .. 10.36 μs)
variance introduced by outliers: 20% (moderately inflated)
```
  • Loading branch information
Martoon-00 committed May 30, 2019
1 parent 30d348c commit 38f6832
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 22 deletions.
6 changes: 6 additions & 0 deletions benchmark/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ main = defaultMain
, bgroupConcatMap
, bgroupMember
, bgroupFold
, bgroupTextConversion
]

bgroupList :: forall a .
Expand Down Expand Up @@ -171,3 +172,8 @@ bgroupFold = do
bgroup "foldl'" [ bench "flipped" $ nf flipFoldl' testList
, bench "base" $ nf ghcFoldl' testList
]

bgroupTextConversion :: Benchmark
bgroupTextConversion =
let str = replicate 100000 'a'
in bench "toText/toString" $ whnf (length . toString . toText) str
26 changes: 20 additions & 6 deletions src/Universum/String/Conversion.hs
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,19 @@ instance ConvertUtf8 LT.Text LB.ByteString where
class ToText a where
toText :: a -> T.Text

instance ToText String where
toText = T.pack

instance ToText T.Text where
toText = id

instance ToText LT.Text where
toText = LT.toStrict

instance ToText String where
toText = stringToText

stringToText :: String -> Text
stringToText = T.pack
{-# NOINLINE [4] stringToText #-}

-- | Type class for converting other strings to 'LT.Text'.
class ToLText a where
toLText :: a -> LT.Text
Expand All @@ -153,12 +157,22 @@ class ToString a where
instance ToString String where
toString = id

instance ToString T.Text where
toString = T.unpack

instance ToString LT.Text where
toString = LT.unpack

instance ToString T.Text where
toString = textToString

textToString :: Text -> String
textToString = T.unpack
{-# NOINLINE [4] textToString #-}

-- This pattern may occur quite often because we tend to use 'Text'
-- rather than 'String' in function signatures.
{-# RULES
"toString/toText" [~3] forall a. textToString (stringToText a) = a
#-}

-- | Polymorhpic version of 'Text.Read.readEither'.
--
-- >>> readEither @Text @Int "123"
Expand Down
40 changes: 24 additions & 16 deletions test/Test/Universum/Property.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,32 @@ module Test.Universum.Property
( hedgehogTestTree
) where

import Universum
import Universum
import qualified Universum as U

import Data.List (nub)
import Hedgehog (Property, Gen, MonadGen, forAll, property, assert, (===))
import Test.Tasty (testGroup, TestTree)
import Hedgehog (Gen, MonadGen, Property, assert, forAll, property, (===))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog

import qualified Universum as U
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT

import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range


hedgehogTestTree :: TestTree
hedgehogTestTree = testGroup "Tests" [utfProps, listProps, boolMProps]
hedgehogTestTree = testGroup "Tests" [stringProps, utfProps, listProps, boolMProps]

stringProps :: TestTree
stringProps = testGroup "String conversions"
[ testProperty "toString . toText = id" prop_StringToTextAndBack
]

utfProps :: TestTree
utfProps = testGroup "utf8 conversion property tests"
utfProps = testGroup "utf8 conversion property tests"
[ testProperty "String to ByteString invertible" prop_StringToBytes
, testProperty "Text to ByteString invertible" prop_TextToBytes
, testProperty "ByteString to Text or String invertible" prop_BytesTo
Expand All @@ -48,7 +52,12 @@ utf8Bytes = Gen.utf8 (Range.linear 0 10000) unicode'
-- "\65534" fails, but this is from BU.toString
-- > import qualified Data.ByteString.UTF8 as BU
-- > BU.toString (BU.fromString "\65534") == "\65533"
-- > True
-- > True

prop_StringToTextAndBack :: Property
prop_StringToTextAndBack = property $ do
str <- forAll utf8String
toString (toText str) === str

prop_StringToBytes :: Property
prop_StringToBytes = property $ do
Expand All @@ -74,7 +83,7 @@ prop_BytesTo = property $ do
-- ordNub

listProps :: TestTree
listProps = testGroup "list function property tests"
listProps = testGroup "list function property tests"
[ testProperty "Hedgehog ordNub xs == nub xs" prop_ordNubCorrect
, testProperty "Hedgehog hashNub xs == nub xs" prop_hashNubCorrect
, testProperty "Hedgehog sortNub xs == sort $ nub xs" prop_sortNubCorrect
Expand Down Expand Up @@ -126,4 +135,3 @@ prop_orM :: Property
prop_orM = property $ do
bs <- forAll genBoolList
U.orM (return <$> bs) === ((return $ U.or bs) :: U.Maybe U.Bool)

0 comments on commit 38f6832

Please sign in to comment.