├── .gitignore ├── .travis.yml ├── LICENSE ├── LP.hs ├── Makefile ├── Notes.org ├── README.md ├── Text └── PrettyPrint │ ├── Compact.hs │ └── Compact │ └── Core.hs ├── acmart.cls ├── acmthm.sty ├── bench ├── Benchmark.hs ├── big.json └── small.json ├── benchmark-40.dat ├── benchmark-80.dat ├── benchmark-random.dat ├── default.nix ├── paper ├── BenchmarkLibs.hs ├── BenchmarkXML.hs ├── Benchmarks.hs ├── ICFP-Mandatory-revisions.txt ├── ICFP2017-draft-reviews.txt ├── ICFP2017-reply.txt ├── Paper.hs ├── benchdata │ ├── 10k.json │ ├── 1k.json │ ├── cds.xml │ └── cdsb10k.xml ├── blog.md ├── lib │ └── PM.hs └── pretty-paper.cabal ├── pretty-compact.cabal ├── stack.yaml ├── styx.yaml └── talk ├── .gitignore ├── Balanced.png ├── HCat.png ├── Outline.org └── Random.png /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-newstyle 3 | .ghc.environment.* 4 | cabal-dev 5 | *~ 6 | *.o 7 | *.hi 8 | *.chi 9 | *.chs.h 10 | *.smt2 11 | .virtualenv 12 | .hsenv 13 | .cabal-sandbox/ 14 | cabal.sandbox.config 15 | cabal.config 16 | /blog.html 17 | /Prettiest.aux 18 | /Prettiest.boxes 19 | /Prettiest.log 20 | /Prettiest.pdf 21 | /Prettiest.ptb 22 | /Prettiest.tex 23 | /PM 24 | /texput.log 25 | /Prettiest.bbl 26 | /Prettiest.blg 27 | /.stack-work 28 | /paper/Prettiest.tex 29 | /Prettiest.out 30 | /.styx/ 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This Travis job script has been generated by a script via 2 | # 3 | # haskell-ci 'pretty-compact.cabal' 4 | # 5 | # To regenerate the script (for example after adjusting tested-with) run 6 | # 7 | # haskell-ci regenerate 8 | # 9 | # For more information, see https://github.com/haskell-CI/haskell-ci 10 | # 11 | # version: 0.10.2 12 | # 13 | version: ~> 1.0 14 | language: c 15 | os: linux 16 | dist: xenial 17 | git: 18 | # whether to recursively clone submodules 19 | submodules: false 20 | cache: 21 | directories: 22 | - $HOME/.cabal/packages 23 | - $HOME/.cabal/store 24 | - $HOME/.hlint 25 | before_cache: 26 | - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log 27 | # remove files that are regenerated by 'cabal update' 28 | - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.* 29 | - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json 30 | - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache 31 | - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar 32 | - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx 33 | - rm -rfv $CABALHOME/packages/head.hackage 34 | jobs: 35 | include: 36 | - compiler: ghc-8.10.1 37 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.10.1","cabal-install-3.2"]}} 38 | os: linux 39 | - compiler: ghc-8.8.3 40 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.8.3","cabal-install-3.2"]}} 41 | os: linux 42 | - compiler: ghc-8.6.5 43 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.5","cabal-install-3.2"]}} 44 | os: linux 45 | - compiler: ghc-8.4.4 46 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.4.4","cabal-install-3.2"]}} 47 | os: linux 48 | - compiler: ghc-8.2.2 49 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.2.2","cabal-install-3.2"]}} 50 | os: linux 51 | - compiler: ghc-8.0.2 52 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.0.2","cabal-install-3.2"]}} 53 | os: linux 54 | - compiler: ghc-7.10.3 55 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-7.10.3","cabal-install-3.2"]}} 56 | os: linux 57 | - compiler: ghc-7.8.4 58 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-7.8.4","cabal-install-3.2"]}} 59 | os: linux 60 | - compiler: ghc-7.6.3 61 | addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-7.6.3","cabal-install-3.2"]}} 62 | os: linux 63 | before_install: 64 | - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//') 65 | - WITHCOMPILER="-w $HC" 66 | - HADDOCK=$(echo "/opt/$CC/bin/haddock" | sed 's/-/\//') 67 | - HCPKG="$HC-pkg" 68 | - unset CC 69 | - CABAL=/opt/ghc/bin/cabal 70 | - CABALHOME=$HOME/.cabal 71 | - export PATH="$CABALHOME/bin:$PATH" 72 | - TOP=$(pwd) 73 | - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')" 74 | - echo $HCNUMVER 75 | - CABAL="$CABAL -vnormal+nowrap" 76 | - set -o pipefail 77 | - TEST=--enable-tests 78 | - BENCH=--enable-benchmarks 79 | - HEADHACKAGE=false 80 | - rm -f $CABALHOME/config 81 | - | 82 | echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config 83 | echo "remote-build-reporting: anonymous" >> $CABALHOME/config 84 | echo "write-ghc-environment-files: always" >> $CABALHOME/config 85 | echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config 86 | echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config 87 | echo "world-file: $CABALHOME/world" >> $CABALHOME/config 88 | echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config 89 | echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config 90 | echo "installdir: $CABALHOME/bin" >> $CABALHOME/config 91 | echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config 92 | echo "store-dir: $CABALHOME/store" >> $CABALHOME/config 93 | echo "install-dirs user" >> $CABALHOME/config 94 | echo " prefix: $CABALHOME" >> $CABALHOME/config 95 | echo "repository hackage.haskell.org" >> $CABALHOME/config 96 | echo " url: http://hackage.haskell.org/" >> $CABALHOME/config 97 | install: 98 | - ${CABAL} --version 99 | - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]" 100 | - | 101 | echo "program-default-options" >> $CABALHOME/config 102 | echo " ghc-options: $GHCJOBS +RTS -M6G -RTS" >> $CABALHOME/config 103 | - cat $CABALHOME/config 104 | - rm -fv cabal.project cabal.project.local cabal.project.freeze 105 | - travis_retry ${CABAL} v2-update -v 106 | # Generate cabal.project 107 | - rm -rf cabal.project cabal.project.local cabal.project.freeze 108 | - touch cabal.project 109 | - | 110 | echo "packages: ." >> cabal.project 111 | - if [ $HCNUMVER -ge 80200 ] ; then echo 'package pretty-compact' >> cabal.project ; fi 112 | - "if [ $HCNUMVER -ge 80200 ] ; then echo ' ghc-options: -Werror=missing-methods' >> cabal.project ; fi" 113 | - | 114 | - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(pretty-compact)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" 115 | - cat cabal.project || true 116 | - cat cabal.project.local || true 117 | - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi 118 | - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} 119 | - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'" 120 | - rm cabal.project.freeze 121 | - travis_wait 40 ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all 122 | - travis_wait 40 ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all 123 | script: 124 | - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX) 125 | # Packaging... 126 | - ${CABAL} v2-sdist all 127 | # Unpacking... 128 | - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/ 129 | - cd ${DISTDIR} || false 130 | - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \; 131 | - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \; 132 | - PKGDIR_pretty_compact="$(find . -maxdepth 1 -type d -regex '.*/pretty-compact-[0-9.]*')" 133 | # Generate cabal.project 134 | - rm -rf cabal.project cabal.project.local cabal.project.freeze 135 | - touch cabal.project 136 | - | 137 | echo "packages: ${PKGDIR_pretty_compact}" >> cabal.project 138 | - if [ $HCNUMVER -ge 80200 ] ; then echo 'package pretty-compact' >> cabal.project ; fi 139 | - "if [ $HCNUMVER -ge 80200 ] ; then echo ' ghc-options: -Werror=missing-methods' >> cabal.project ; fi" 140 | - | 141 | - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(pretty-compact)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" 142 | - cat cabal.project || true 143 | - cat cabal.project.local || true 144 | # Building... 145 | # this builds all libraries and executables (without tests/benchmarks) 146 | - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all 147 | # Building with tests and benchmarks... 148 | # build & run tests, build benchmarks 149 | - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all 150 | # cabal check... 151 | - (cd ${PKGDIR_pretty_compact} && ${CABAL} -vnormal check) 152 | # haddock... 153 | - ${CABAL} v2-haddock $WITHCOMPILER --with-haddock $HADDOCK ${TEST} ${BENCH} all 154 | # Building without installed constraints for packages in global-db... 155 | - rm -f cabal.project.local 156 | - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all 157 | 158 | # REGENDATA ("0.10.2",["pretty-compact.cabal"]) 159 | # EOF 160 | -------------------------------------------------------------------------------- /LP.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts, GADTs #-} 2 | import Data.LinearProgram 3 | import Data.LinearProgram.LinExpr 4 | -- import Control.Monad.Supply 5 | -- import Control.Monad.LPMonad 6 | import qualified Data.Map as M 7 | 8 | type Gen = LPT Var Double VSupply 9 | 10 | type Expr = LinExpr Var Double 11 | 12 | type Doc = Expr -> Expr -> Gen (Expr,Expr) 13 | 14 | var :: Num c => v -> LinExpr v c 15 | var x = LinExpr (M.singleton x 1) 0 16 | 17 | (.<=.) :: Expr -> Expr -> Gen () 18 | a .<=. b = do 19 | f `geqTo` (negate c) 20 | where LinExpr f c = b ^-^ a 21 | -- a <= b 22 | -- b - a >= 0 23 | -- f + c >= 0 24 | -- f >= -c 25 | 26 | space :: Doc 27 | space = text " " 28 | 29 | empty :: Doc 30 | empty = text "" 31 | 32 | (<+>) :: Doc -> Doc -> Doc 33 | a <+> b = a <> space <> b 34 | 35 | () :: Doc -> Doc -> Doc 36 | a b = a <> softBreak <> b 37 | 38 | sep :: [Doc] -> Doc 39 | sep xs i c = do 40 | v <- choice 41 | (foldr (\x y -> x <> newLineIf v <> y) empty xs) i c 42 | 43 | -- fsep = foldr () empty 44 | 45 | (<>) :: Doc -> Doc -> Doc 46 | (a <> b) i c0 = do 47 | (h1,c1) <- a i c0 48 | (h2,c2) <- b i c1 49 | return (h1 ^+^ h2, c2) 50 | 51 | con :: Int -> Expr 52 | con x = LinExpr zero (fromIntegral x) 53 | 54 | text :: String -> Doc 55 | text s _ c = do 56 | return (zero, c ^+^ con (length s)) 57 | 58 | pageWidth :: Int 59 | pageWidth = 80 60 | 61 | newline :: Doc 62 | newline i c = do 63 | c .<=. con pageWidth 64 | return (con 1,i) 65 | 66 | lpMax :: Expr -> Expr -> Gen Expr 67 | lpMax a b = do 68 | v <- supplyNew 69 | setVarKind v ContVar 70 | setVarBounds v (LBound 0) 71 | a .<=. var v 72 | b .<=. var v 73 | return (var v) 74 | 75 | newLineIf :: Var -> Doc 76 | newLineIf cr i c = do 77 | c .<=. con pageWidth 78 | m <- lpMax i (c ^-^ fromIntegral pageWidth *^ var cr) 79 | return (var cr,m) 80 | 81 | choice :: Gen Var 82 | choice = do 83 | v <- supplyNew 84 | setVarKind v BinVar 85 | return v 86 | 87 | softBreak :: Doc 88 | softBreak i c = do 89 | v <- choice 90 | newLineIf v i c 91 | 92 | render :: Doc -> IO () 93 | render x = do 94 | let problem = runVSupply $ execLPT $ 95 | do (LinExpr h _,c) <- x zero zero 96 | c .<=. con pageWidth 97 | setObjective h 98 | setDirection Min 99 | print problem 100 | solution <- glpSolveVars mipDefaults problem 101 | -- {brTech = HybridP} 102 | print solution 103 | 104 | data SExpr where 105 | SExpr :: [SExpr] -> SExpr 106 | Atom :: String -> SExpr 107 | deriving Show 108 | 109 | abcd :: SExpr 110 | abcd = SExpr [Atom "a",Atom "b",Atom "c",Atom "d"] 111 | 112 | testData :: SExpr 113 | testData = SExpr [SExpr [Atom "12345", abcd4], 114 | SExpr [Atom "12345678", abcd4]] 115 | where abcd4 = SExpr [abcd,abcd,abcd,abcd] 116 | 117 | testData2 = SExpr (replicate 10 testData) 118 | testData4 = SExpr (replicate 10 testData2) 119 | testData8 = SExpr (replicate 10 testData4) 120 | 121 | pretty :: SExpr -> Doc 122 | pretty (Atom x) = text x 123 | pretty (SExpr xs) = text "(" <> sep (map pretty xs) <> text ")" 124 | 125 | main :: IO () 126 | main = do 127 | render $ pretty $ testData2 128 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PRECIOUS: *.dat .styx/bin/paper 2 | 3 | default: Prettiest.pdf 4 | 5 | clean: 6 | rm -f *.aux *.ptb *.boxes *.log 7 | 8 | .styx/bin/paper .styx/bin/bench: paper/lib/PM.hs 9 | styx cabal install pretty-paper 10 | 11 | benchmark-80.dat: .styx/bin/bench 12 | .styx/bin/bench full 13 | 14 | benchmark-random.dat: .styx/bin/bench 15 | .styx/bin/bench random 16 | 17 | Prettiest.pdf: .styx/bin/paper 18 | styx exec -- paper 19 | styx exec -- pdflatex Prettiest 20 | styx exec -- bibtex Prettiest 21 | styx exec -- paper 22 | styx exec -- pdflatex Prettiest 23 | 24 | Prettiest.stack.pdf: benchmark-40.dat benchmark-80.dat paper/PM.hs 25 | stack build 26 | stack exec -- paper 27 | pdflatex Prettiest 28 | bibtex Prettiest 29 | stack exec -- paper 30 | pdflatex Prettiest 31 | 32 | blog: blog.html 33 | 34 | # %.html: %.org 35 | # pandoc --email-obfuscation=references --smart --standalone --css=home.css --from=org --to=html --output=$@ $< 36 | 37 | %.html: %.md 38 | pandoc --email-obfuscation=references --smart --standalone --css=home.css --from=markdown --to=html --output=$@ $< 39 | 40 | %.md: %.org 41 | pandoc --from=org --to=markdown --output=$@ $< 42 | -------------------------------------------------------------------------------- /Notes.org: -------------------------------------------------------------------------------- 1 | A couple more optimisations which can be done (not implemented nor documented in the paper) 2 | 3 | * Optimisation 1: bounds 4 | 5 | We can compute an upper bound (b), such that any produced layout which 6 | is above b can be discarded. 7 | 8 | Let us assume that a bound is a set of points (layout measures). A 9 | layout should be kept only if no point in the bound dominates it. 10 | Equivalently, if a single point in the bound dominates a layout x, x 11 | can be discarded. 12 | 13 | Consequently, 14 | - if a frontier contains the origin point then the search can be 15 | stopped (it will dominate any possible layout) 16 | - coordinates below zero can be replaced by zero in the bound. 17 | 18 | The computation of bounds can be done as follows: 19 | 20 | ** Bounds given by disjunction 21 | Consider a0 <|> a. Assuming that we have computed the 22 | pareto frontier for a0, we can use it as the bound for the document 23 | a. 24 | 25 | ** Bounds given by composition 26 | 27 | Consider a = x <> y. 28 | 29 | 30 | Recall that the (horizontal) composition of layout measures can be 31 | done as follows: 32 | 33 | h = h1 + h2 34 | w = max w1 (l1 + w2) 35 | l = l1 + l2 36 | 37 | Aside: we can also define the subtraction as follows: 38 | 39 | (h, w, l) - (h0,w0,l0) = (h2,w2,l2) 40 | where 41 | h2 = h - h1 42 | w2 <= w - l1 43 | l2 = l - l1 44 | 45 | 46 | Assume that we have 47 | - a~, the bound of a 48 | - x~; the pareto limit for x 49 | 50 | We can compute 51 | - y~, the bound for y 52 | 53 | For y to be valid, it suffices that, for any point p on x~, p <> y is 54 | better that any point q on b. Thus a set of possible points r for y 55 | is: 56 | 57 | y~ = {r | p <- x, q <- b, ¬ b < (x <> y)} 58 | 59 | we can rewrite the condition ¬ b < (x <> y), and obtain 60 | 61 | y~ = {(h2,w2,l2) | p <- (h1,w1,l1), 62 | q <- (h ,q ,l), 63 | h1 + h2 < h ∨ 64 | max w1 (l1+w2) < w ∨ 65 | l1 + l2 < l} 66 | or equivalently (because w1 < w will hold by construction) 67 | 68 | y~ = {(h2,w2,l2) | p <- (h1,w1,l1), 69 | q <- (h ,q ,l), 70 | h1 + h2 < h ∨ 71 | l1 + w2 < w ∨ 72 | l1 + l2 < l} 73 | 74 | but, it suffices to take the largest h2, l2, w2; so we can 75 | simplify the formula like so: 76 | 77 | 78 | y~ = {(h2,w2,l2) | p <- (h1,w1,l1), 79 | q <- (h ,w ,l), 80 | h2 = h - h1, 81 | w2 = w - l1, 82 | l2 = l - l1 } 83 | 84 | And, additionally, we can take the pareto frontier of y~ (because 85 | domination is transitive) 86 | 87 | ** Bounds given by flush 88 | 89 | x~ = {(h-1,w,w) | (h,w,l) <- (flush x)~} 90 | 91 | 92 | We see here that it is in fact not useful to remember the lastwidth in 93 | bounds. Indeed 94 | - it is forgotten as every "flush" 95 | - if the bound of the lastwidth is reached so is the bound of the 96 | width (because we process the documents left to right in <>) 97 | 98 | * Opt 2. 99 | 100 | 101 | In x <> y. Let (freeSpace = min {p ∈ x | width x - lastWidth x}). Then 102 | we do not have to care about layouts of y that are narrower than 103 | freeSpace at the expense of other factors /when computing y/. 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | prettiest 2 | ========= 3 | 4 | The Prettiest Printer 5 | -------------------------------------------------------------------------------- /Text/PrettyPrint/Compact.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | -- | Compact pretty-printer. 4 | -- 5 | -- == Examples 6 | -- 7 | -- Assume that we want to pretty print S-Expressions, which can either be atom or a list of S-Expressions. 8 | -- 9 | -- >>> data SExpr = SExpr [SExpr] | Atom String deriving Show 10 | -- >>> let pretty :: SExpr -> Doc (); pretty (Atom s) = text s; pretty (SExpr xs) = text "(" <> (sep $ map pretty xs) <> text ")" 11 | -- 12 | -- Using the above representation, the S-Expression @(a b c d)@ has the following encoding: 13 | -- 14 | -- >>> let abcd = SExpr [Atom "a",Atom "b",Atom "c",Atom "d"] 15 | -- 16 | -- The legible layouts of the @abcd@ S-Expression defined above would be either 17 | -- 18 | -- >>> putStrLn $ render $ pretty abcd 19 | -- (a b c d) 20 | -- 21 | -- or 22 | -- 23 | -- >>> putStrLn $ renderWith defaultOptions { optsPageWidth = 5 } $ pretty abcd 24 | -- (a 25 | -- b 26 | -- c 27 | -- d) 28 | -- 29 | -- The @testData@ S-Expression is specially crafted to 30 | -- demonstrate general shortcomings of both Hughes and Wadler libraries. 31 | -- 32 | -- >>> let abcd4 = SExpr [abcd,abcd,abcd,abcd] 33 | -- >>> let testData = SExpr [ SExpr [Atom "abcde", abcd4], SExpr [Atom "abcdefgh", abcd4]] 34 | -- >>> putStrLn $ render $ pretty testData 35 | -- ((abcde ((a b c d) (a b c d) (a b c d) (a b c d))) 36 | -- (abcdefgh ((a b c d) (a b c d) (a b c d) (a b c d)))) 37 | -- 38 | -- on 20-column-wide page 39 | -- 40 | -- >>> putStrLn $ renderWith defaultOptions { optsPageWidth = 20 } $ pretty testData 41 | -- ((abcde ((a b c d) 42 | -- (a b c d) 43 | -- (a b c d) 44 | -- (a b c d))) 45 | -- (abcdefgh 46 | -- ((a b c d) 47 | -- (a b c d) 48 | -- (a b c d) 49 | -- (a b c d)))) 50 | -- 51 | -- Yet, neither Hughes' nor Wadler's library can deliver those results. 52 | -- 53 | -- === Annotations 54 | -- 55 | -- For example we can annotate every /car/ element of S-Expressions, 56 | -- and in the rendering phase emphasise them by rendering them in uppercase. 57 | -- 58 | -- >>> let pretty' :: SExpr -> Doc Any; pretty' (Atom s) = text s; pretty' (SExpr []) = text "()"; pretty' (SExpr (x:xs)) = text "(" <> (sep $ annotate (Any True) (pretty' x) : map pretty' xs) <> text ")" 59 | -- >>> let render' = renderWith defaultOptions { optsAnnotate = \a x -> if a == Any True then map toUpper x else x } 60 | -- >>> putStrLn $ render' $ pretty' testData 61 | -- ((ABCDE ((A B C D) (A B C D) (A B C D) (A B C D))) 62 | -- (ABCDEFGH ((A B C D) (A b c d) (A b c d) (A b c d)))) 63 | -- 64 | module Text.PrettyPrint.Compact ( 65 | -- * Documents 66 | Doc, 67 | 68 | -- * Basic combinators 69 | module Data.Monoid, text, flush, char, 70 | 71 | hang, hangWith, encloseSep, list, tupled, semiBraces, 72 | 73 | -- * Operators 74 | (<+>), ($$), (), (), (<$$>), 75 | 76 | -- * List combinators 77 | hsep, sep, hcat, vcat, cat, punctuate, 78 | 79 | -- * Fill combiantors 80 | -- fillSep, fillCat, 81 | 82 | -- * Bracketing combinators 83 | enclose, squotes, dquotes, parens, angles, braces, brackets, 84 | 85 | -- * Character documents 86 | lparen, rparen, langle, rangle, lbrace, rbrace, lbracket, rbracket, 87 | squote, dquote, semi, colon, comma, space, dot, backslash, equals, 88 | 89 | -- * Primitive type documents 90 | string, int, integer, float, double, rational, 91 | bool, 92 | 93 | -- * Rendering 94 | renderWith, 95 | render, 96 | Options(..), 97 | defaultOptions, 98 | 99 | -- * Annotations 100 | annotate, 101 | 102 | -- * Undocumented 103 | -- column, nesting, width 104 | ) where 105 | 106 | import Data.Monoid 107 | 108 | import Text.PrettyPrint.Compact.Core as Text.PrettyPrint.Compact 109 | 110 | -- | Render the 'Doc' into 'String' omitting all annotations. 111 | render :: Annotation a => Doc a -> String 112 | render = renderWith defaultOptions 113 | 114 | defaultOptions :: Options a String 115 | defaultOptions = Options 116 | { optsAnnotate = \_ s -> s 117 | , optsPageWidth = 80 118 | } 119 | 120 | -- | The document @(list xs)@ comma separates the documents @xs@ and 121 | -- encloses them in square brackets. The documents are rendered 122 | -- horizontally if that fits the page. Otherwise they are aligned 123 | -- vertically. All comma separators are put in front of the elements. 124 | list :: Annotation a => [Doc a] -> Doc a 125 | list = encloseSep lbracket rbracket comma 126 | 127 | -- | The document @(tupled xs)@ comma separates the documents @xs@ and 128 | -- encloses them in parenthesis. The documents are rendered 129 | -- horizontally if that fits the page. Otherwise they are aligned 130 | -- vertically. All comma separators are put in front of the elements. 131 | tupled :: Annotation a => [Doc a] -> Doc a 132 | tupled = encloseSep lparen rparen comma 133 | 134 | 135 | -- | The document @(semiBraces xs)@ separates the documents @xs@ with 136 | -- semi colons and encloses them in braces. The documents are rendered 137 | -- horizontally if that fits the page. Otherwise they are aligned 138 | -- vertically. All semi colons are put in front of the elements. 139 | semiBraces :: Annotation a => [Doc a] -> Doc a 140 | semiBraces = encloseSep lbrace rbrace semi 141 | 142 | -- | The document @(enclosure l r sep xs)@ concatenates the documents 143 | -- @xs@ separated by @sep@ and encloses the resulting document by @l@ 144 | -- and @r@. The documents are rendered horizontally if that fits the 145 | -- page. Otherwise they are aligned vertically. All separators are put 146 | -- in front of the elements. For example, the combinator 'list' can be 147 | -- defined with @enclosure@: 148 | -- 149 | -- > list xs = enclosure lbracket rbracket comma xs 150 | -- > test = text "list" <+> (list (map int [10,200,3000])) 151 | -- 152 | -- Which is layed out with a page width of 20 as: 153 | -- 154 | -- @ 155 | -- list [10,200,3000] 156 | -- @ 157 | -- 158 | -- But when the page width is 15, it is layed out as: 159 | -- 160 | -- @ 161 | -- list [10 162 | -- ,200 163 | -- ,3000] 164 | -- @ 165 | encloseSep :: Annotation a => Doc a -> Doc a -> Doc a -> [Doc a] -> Doc a 166 | encloseSep left right separator ds 167 | = (<> right) $ case ds of 168 | [] -> left 169 | [d] -> left <> d 170 | (d:ds') -> cat (left <> d:map (separator <>) ds') 171 | 172 | ----------------------------------------------------------- 173 | -- punctuate p [d1,d2,...,dn] => [d1 <> p,d2 <> p, ... ,dn] 174 | ----------------------------------------------------------- 175 | 176 | 177 | -- | @(punctuate p xs)@ concatenates all documents in @xs@ with 178 | -- document @p@ except for the last document. 179 | -- 180 | -- > someText = map text ["words","in","a","tuple"] 181 | -- > test = parens (align (cat (punctuate comma someText))) 182 | -- 183 | -- This is layed out on a page width of 20 as: 184 | -- 185 | -- @ 186 | -- (words,in,a,tuple) 187 | -- @ 188 | -- 189 | -- But when the page width is 15, it is layed out as: 190 | -- 191 | -- @ 192 | -- (words, 193 | -- in, 194 | -- a, 195 | -- tuple) 196 | -- @ 197 | -- 198 | -- (If you want put the commas in front of their elements instead of 199 | -- at the end, you should use 'tupled' or, in general, 'encloseSep'.) 200 | punctuate :: Annotation a => Doc a -> [Doc a] -> [Doc a] 201 | punctuate _p [] = [] 202 | punctuate _p [d] = [d] 203 | punctuate p (d:ds) = (d <> p) : punctuate p ds 204 | 205 | 206 | ----------------------------------------------------------- 207 | -- high-level combinators 208 | ----------------------------------------------------------- 209 | 210 | 211 | -- | The document @(sep xs)@ concatenates all documents @xs@ either 212 | -- horizontally with @(\<+\>)@, if it fits the page, or vertically 213 | -- with @(\<$$\>)@. Documents on the left of horizontal concatenation 214 | -- must fit on a single line. 215 | -- 216 | sep :: Annotation a => [Doc a] -> Doc a 217 | sep xs = groupingBy " " (map (0,) xs) 218 | 219 | 220 | -- -- | The document @(fillSep xs)@ concatenates documents @xs@ 221 | -- -- horizontally with @(\<+\>)@ as long as its fits the page, than 222 | -- -- inserts a @line@ and continues doing that for all documents in 223 | -- -- @xs@. 224 | -- -- 225 | -- -- > fillSep xs = foldr (\<\/\>) empty xs 226 | -- fillSep :: Annotation a => [Doc a] -> Doc a 227 | -- fillSep = foldDoc () 228 | 229 | -- | The document @(hsep xs)@ concatenates all documents @xs@ 230 | -- horizontally with @(\<+\>)@. 231 | hsep :: Annotation a => [Doc a] -> Doc a 232 | hsep = foldDoc (<+>) 233 | 234 | -- | The document @(cat xs)@ concatenates all documents @xs@ either 235 | -- horizontally with @(\<\>)@, if it fits the page, or vertically with 236 | -- @(\<$$\>)@. 237 | -- 238 | cat :: Annotation a => [Doc a] -> Doc a 239 | cat xs = groupingBy "" (map (0,) xs) 240 | 241 | -- -- | The document @(fillCat xs)@ concatenates documents @xs@ 242 | -- -- horizontally with @(\<\>)@ as long as its fits the page, than inserts 243 | -- -- a @linebreak@ and continues doing that for all documents in @xs@. 244 | -- -- 245 | -- -- > fillCat xs = foldr (\<\/\/\>) empty xs 246 | -- fillCat :: Annotation a => [Doc a] -> Doc a 247 | -- fillCat = foldDoc () 248 | 249 | -- | The document @(hcat xs)@ concatenates all documents @xs@ 250 | -- horizontally with @(\<\>)@. 251 | hcat :: Annotation a => [Doc a] -> Doc a 252 | hcat = foldDoc (<>) 253 | 254 | -- | The document @(vcat xs)@ concatenates all documents @xs@ 255 | -- vertically with @($$)@. 256 | vcat :: Annotation a => [Doc a] -> Doc a 257 | vcat = foldDoc ($$) 258 | 259 | foldDoc :: Annotation a => (Doc a -> Doc a -> Doc a) -> [Doc a] -> Doc a 260 | foldDoc _ [] = mempty 261 | foldDoc f ds = foldr1 f ds 262 | 263 | -- | The document @(x \<+\> y)@ concatenates document @x@ and @y@ with a 264 | -- @space@ in between. (infixr 6) 265 | (<+>) :: Annotation a => Doc a -> Doc a -> Doc a 266 | x <+> y = x <> space <> y 267 | 268 | -- | The document @(x \<\/\> y)@ puts @x@ and @y@ either next to each other 269 | -- (with a @space@ in between) or underneath each other. (infixr 5) 270 | () :: Annotation a => Doc a -> Doc a -> Doc a 271 | x y = hang 0 x y 272 | 273 | -- | The document @(x \<\/\/\> y)@ puts @x@ and @y@ either right next 274 | -- to each other (if @x@ fits on a single line) or underneath each 275 | -- other. (infixr 5) 276 | () :: Annotation a => Doc a -> Doc a -> Doc a 277 | x y = hangWith "" 0 x y 278 | 279 | -- | The document @(x \<$$\> y)@ concatenates document @x@ and @y@ with 280 | -- a linebreak in between. (infixr 5) 281 | (<$$>) :: Annotation a => Doc a -> Doc a -> Doc a 282 | (<$$>) = ($$) 283 | 284 | -- | Document @(squotes x)@ encloses document @x@ with single quotes 285 | -- \"'\". 286 | squotes :: Annotation a => Doc a -> Doc a 287 | squotes = enclose squote squote 288 | 289 | -- | Document @(dquotes x)@ encloses document @x@ with double quotes 290 | -- '\"'. 291 | dquotes :: Annotation a => Doc a -> Doc a 292 | dquotes = enclose dquote dquote 293 | 294 | -- | Document @(braces x)@ encloses document @x@ in braces, \"{\" and 295 | -- \"}\". 296 | braces :: Annotation a => Doc a -> Doc a 297 | braces = enclose lbrace rbrace 298 | 299 | -- | Document @(parens x)@ encloses document @x@ in parenthesis, \"(\" 300 | -- and \")\". 301 | parens :: Annotation a => Doc a -> Doc a 302 | parens = enclose lparen rparen 303 | 304 | -- | Document @(angles x)@ encloses document @x@ in angles, \"\<\" and 305 | -- \"\>\". 306 | angles :: Annotation a => Doc a -> Doc a 307 | angles = enclose langle rangle 308 | 309 | -- | Document @(brackets x)@ encloses document @x@ in square brackets, 310 | -- \"[\" and \"]\". 311 | brackets :: Annotation a => Doc a -> Doc a 312 | brackets = enclose lbracket rbracket 313 | 314 | -- | The document @(enclose l r x)@ encloses document @x@ between 315 | -- documents @l@ and @r@ using @(\<\>)@. 316 | enclose :: Annotation a => Doc a -> Doc a -> Doc a -> Doc a 317 | enclose l r x = l <> x <> r 318 | 319 | char :: Annotation a => Char -> Doc a 320 | char x = text [x] 321 | 322 | -- | The document @lparen@ contains a left parenthesis, \"(\". 323 | lparen :: Annotation a => Doc a 324 | lparen = char '(' 325 | -- | The document @rparen@ contains a right parenthesis, \")\". 326 | rparen :: Annotation a => Doc a 327 | rparen = char ')' 328 | -- | The document @langle@ contains a left angle, \"\<\". 329 | langle :: Annotation a => Doc a 330 | langle = char '<' 331 | -- | The document @rangle@ contains a right angle, \">\". 332 | rangle :: Annotation a => Doc a 333 | rangle = char '>' 334 | -- | The document @lbrace@ contains a left brace, \"{\". 335 | lbrace :: Annotation a => Doc a 336 | lbrace = char '{' 337 | -- | The document @rbrace@ contains a right brace, \"}\". 338 | rbrace :: Annotation a => Doc a 339 | rbrace = char '}' 340 | -- | The document @lbracket@ contains a left square bracket, \"[\". 341 | lbracket :: Annotation a => Doc a 342 | lbracket = char '[' 343 | -- | The document @rbracket@ contains a right square bracket, \"]\". 344 | rbracket :: Annotation a => Doc a 345 | rbracket = char ']' 346 | 347 | 348 | -- | The document @squote@ contains a single quote, \"'\". 349 | squote :: Annotation a => Doc a 350 | squote = char '\'' 351 | -- | The document @dquote@ contains a double quote, '\"'. 352 | dquote :: Annotation a => Doc a 353 | dquote = char '"' 354 | -- | The document @semi@ contains a semi colon, \";\". 355 | semi :: Annotation a => Doc a 356 | semi = char ';' 357 | -- | The document @colon@ contains a colon, \":\". 358 | colon :: Annotation a => Doc a 359 | colon = char ':' 360 | -- | The document @comma@ contains a comma, \",\". 361 | comma :: Annotation a => Doc a 362 | comma = char ',' 363 | 364 | -- | The document @dot@ contains a single dot, \".\". 365 | dot :: Annotation a => Doc a 366 | dot = char '.' 367 | -- | The document @backslash@ contains a back slash, \"\\\". 368 | backslash :: Annotation a => Doc a 369 | backslash = char '\\' 370 | -- | The document @equals@ contains an equal sign, \"=\". 371 | equals :: Annotation a => Doc a 372 | equals = char '=' 373 | 374 | ----------------------------------------------------------- 375 | -- Combinators for prelude types 376 | ----------------------------------------------------------- 377 | 378 | -- string is like "text" but replaces '\n' by "line" 379 | 380 | -- | The document @(string s)@ concatenates all characters in @s@ 381 | -- using @line@ for newline characters and @char@ for all other 382 | -- characters. It is used instead of 'text' whenever the text contains 383 | -- newline characters. 384 | string :: Annotation a => String -> Doc a 385 | string = vcat . map text . lines 386 | 387 | bool :: Annotation a => Bool -> Doc a 388 | bool b = text (show b) 389 | 390 | -- | The document @(int i)@ shows the literal integer @i@ using 391 | -- 'text'. 392 | int :: Annotation a => Int -> Doc a 393 | int i = text (show i) 394 | 395 | -- | The document @(integer i)@ shows the literal integer @i@ using 396 | -- 'text'. 397 | integer :: Annotation a => Integer -> Doc a 398 | integer i = text (show i) 399 | 400 | -- | The document @(float f)@ shows the literal float @f@ using 401 | -- 'text'. 402 | float :: Annotation a => Float -> Doc a 403 | float f = text (show f) 404 | 405 | -- | The document @(double d)@ shows the literal double @d@ using 406 | -- 'text'. 407 | double :: Annotation a => Double -> Doc a 408 | double d = text (show d) 409 | 410 | -- | The document @(rational r)@ shows the literal rational @r@ using 411 | -- 'text'. 412 | rational :: Annotation a => Rational -> Doc a 413 | rational r = text (show r) 414 | 415 | 416 | 417 | -- | The hang combinator implements hanging indentation. The document 418 | -- @(hang i x y)@ either @x@ and @y@ concatenated with @\<+\>@ or @y@ 419 | -- below @x@ with an additional indentation of @i@. 420 | 421 | hang :: Annotation a => Int -> Doc a -> Doc a -> Doc a 422 | hang = hangWith " " 423 | 424 | 425 | -- | The hang combinator implements hanging indentation. The document 426 | -- @(hang separator i x y)@ either @x@ and @y@ concatenated with @\<\> 427 | -- text separator \<\>@ or @y@ below @x@ with an additional 428 | -- indentation of @i@. 429 | hangWith :: Annotation a => String -> Int -> Doc a -> Doc a -> Doc a 430 | hangWith separator n x y = groupingBy separator [(0,x), (n,y)] 431 | 432 | space :: Annotation a => Doc a 433 | space = text " " 434 | 435 | -- $setup 436 | -- >>> import Data.Monoid 437 | -- >>> import Data.Char 438 | -------------------------------------------------------------------------------- /Text/PrettyPrint/Compact/Core.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | {-# LANGUAGE ConstraintKinds #-} 3 | {-# LANGUAGE RecordWildCards #-} 4 | {-# LANGUAGE ScopedTypeVariables, TypeSynonymInstances, FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving, ViewPatterns, DeriveFunctor, DeriveFoldable, DeriveTraversable, LambdaCase #-} 5 | module Text.PrettyPrint.Compact.Core(Annotation,Layout(..),renderWith,Options(..),groupingBy,Doc,($$)) where 6 | 7 | import Prelude () 8 | import Prelude.Compat as P 9 | 10 | import Data.List.Compat (sortOn,groupBy,minimumBy) 11 | import Data.Function (on) 12 | import Data.Semigroup 13 | import Data.Sequence (singleton, Seq, viewl, viewr, ViewL(..), ViewR(..), (|>)) 14 | import Data.String 15 | import Data.Foldable (toList) 16 | import Control.Applicative (liftA2) 17 | -- | Annotated string, which consists of segments with separate (or no) annotations. 18 | -- 19 | -- We keep annotated segments in a container (list). 20 | -- The annotation is @Maybe a@, because the no-annotation case is common. 21 | -- 22 | -- /Note:/ with @Last x@ annotation, the 'annotate' will overwrite all annotations. 23 | -- 24 | -- /Note:/ if the list is changed into `Seq` or similar structure 25 | -- allowing fast viewr and viewl, then we can impose an additional 26 | -- invariant that there aren't two consequtive non-annotated segments; 27 | -- yet there is no performance reason to do so. 28 | -- 29 | data AS a = AS !Int [(a, String)] 30 | deriving (Eq,Ord,Show,Functor,Foldable,Traversable) 31 | 32 | -- | Tests the invariants of 'AS' 33 | _validAs :: AS a -> Bool 34 | _validAs (AS i s) = lengthInvariant && noNewlineInvariant 35 | where 36 | lengthInvariant = i == sum (map (length . snd) s) 37 | noNewlineInvariant = all (notElem '\n' . snd) s 38 | 39 | asLength :: AS a -> Int 40 | asLength (AS l _) = l 41 | 42 | -- | Make a non-annotated 'AS'. 43 | mkAS :: Monoid a => String -> AS a 44 | mkAS s = AS (length s) [(mempty, s)] 45 | 46 | instance Semigroup (AS a) where 47 | AS i xs <> AS j ys = AS (i + j) (xs <> ys) 48 | 49 | newtype L a = L (Seq (AS a)) -- non-empty sequence 50 | deriving (Eq,Ord,Show,Functor,Foldable,Traversable) 51 | 52 | instance Monoid a => Semigroup (L a) where 53 | L (viewr -> xs :> x) <> L (viewl -> y :< ys) = 54 | L (xs <> singleton (x <> y) <> indent ys) where 55 | 56 | n = asLength x 57 | pad = mkAS (P.replicate n ' ') 58 | indent = if n == 0 then id else fmap (pad <>) 59 | 60 | L _ <> L _ = error "<> @L: invariant violated, Seq is empty" 61 | 62 | instance Monoid a => Monoid (L a) where 63 | mempty = L (singleton (mkAS "")) 64 | 65 | instance Layout L where 66 | text = L . singleton . mkAS 67 | flush (L xs) = L (xs |> mkAS "") 68 | annotate a (L s') = L (fmap annotateAS s') 69 | where annotateAS (AS i s) = AS i (fmap annotatePart s) 70 | annotatePart (b, s) = (b `mappend` a, s) 71 | 72 | renderWithL :: (Monoid a, Monoid r) => Options a r -> L a -> r 73 | renderWithL opts (L xs) = intercalate (toList xs) 74 | where 75 | f = optsAnnotate opts 76 | f' (AS _ s) = foldMap (uncurry f) s 77 | sep = f mempty "\n" 78 | 79 | intercalate [] = mempty 80 | intercalate (y:ys) = f' y `mappend` foldMap (mappend sep . f') ys 81 | 82 | data Options a r = Options 83 | { optsPageWidth :: !Int -- ^ maximum page width 84 | , optsAnnotate :: a -> String -> r -- ^ how to annotate the string. /Note:/ the annotation should preserve the visible length of the string. 85 | } 86 | 87 | class Layout d where 88 | text :: Monoid a => String -> d a 89 | flush :: Monoid a => d a -> d a 90 | -- | `<>` new annotation to the 'Doc'. 91 | -- 92 | -- Example: 'Any True' annotation will transform the rendered 'Doc' into uppercase: 93 | -- 94 | -- >>> let r = putStrLn . renderWith defaultOptions { optsAnnotate = \a x -> if a == Any True then map toUpper x else x } 95 | -- >>> r $ text "hello" <$$> annotate (Any True) (text "world") 96 | -- hello 97 | -- WORLD 98 | -- 99 | annotate :: forall a. Monoid a => a -> d a -> d a 100 | 101 | -- type parameter is phantom. 102 | data M a = M {height :: Int, 103 | lastWidth :: Int, 104 | maxWidth :: Int 105 | } 106 | deriving (Show,Eq,Ord,Functor,Foldable,Traversable) 107 | 108 | instance Semigroup (M a) where 109 | a <> b = 110 | M {maxWidth = max (maxWidth a) (maxWidth b + lastWidth a), 111 | height = height a + height b, 112 | lastWidth = lastWidth a + lastWidth b} 113 | 114 | instance Monoid a => Monoid (M a) where 115 | mempty = text "" 116 | mappend = (<>) 117 | 118 | instance Layout M where 119 | text s = M {height = 0, maxWidth = length s, lastWidth = length s} 120 | flush a = M {maxWidth = maxWidth a, 121 | height = height a + 1, 122 | lastWidth = 0} 123 | annotate _ M{..} = M{..} 124 | class Poset a where 125 | (≺) :: a -> a -> Bool 126 | 127 | 128 | instance Poset (M a) where 129 | M c1 l1 s1 ≺ M c2 l2 s2 = c1 <= c2 && l1 <= l2 && s1 <= s2 130 | 131 | mergeOn :: Ord b => (a -> b) -> [a] -> [a] -> [a] 132 | mergeOn m = go 133 | where 134 | go [] xs = xs 135 | go xs [] = xs 136 | go (x:xs) (y:ys) 137 | | m x <= m y = x:go xs (y:ys) 138 | | otherwise = y:go (x:xs) ys 139 | 140 | mergeAllOn :: Ord b => (a -> b) -> [[a]] -> [a] 141 | mergeAllOn _ [] = [] 142 | mergeAllOn m (x:xs) = mergeOn m x (mergeAllOn m xs) 143 | 144 | bestsOn :: forall a b. (Poset b, Ord b) 145 | => (a -> b) -- ^ measure 146 | -> [[a]] -> [a] 147 | bestsOn m = paretoOn' m [] . mergeAllOn m 148 | 149 | -- | @paretoOn m = paretoOn' m []@ 150 | paretoOn' :: Poset b => (a -> b) -> [a] -> [a] -> [a] 151 | paretoOn' _ acc [] = P.reverse acc 152 | paretoOn' m acc (x:xs) = if any ((≺ m x) . m) acc 153 | then paretoOn' m acc xs 154 | else paretoOn' m (x:acc) xs 155 | -- because of the ordering, we have that 156 | -- for all y ∈ acc, y <= x, and thus x ≺ y 157 | -- is false. No need to refilter acc. 158 | 159 | -- list sorted by lexicographic order for the first component 160 | -- function argument is the page width 161 | newtype ODoc a = MkDoc {fromDoc :: Int -> [(Pair M L a)]} 162 | deriving Functor 163 | 164 | instance Monoid a => Semigroup (ODoc a) where 165 | MkDoc xs <> MkDoc ys = MkDoc $ \w -> bestsOn frst [ discardInvalid w [x <> y | y <- ys w] | x <- xs w] 166 | 167 | discardInvalid w = quasifilter (fits w . frst) 168 | 169 | quasifilter _ [] = [] 170 | quasifilter p zs = let fzs = filter p zs 171 | in if null fzs -- in case that there are no valid layouts, we take a narrow one. 172 | then [minimumBy (compare `on` (maxWidth . frst)) zs] 173 | else fzs 174 | 175 | instance Monoid a => Monoid (ODoc a) where 176 | mempty = text "" 177 | mappend = (<>) 178 | 179 | fits :: Int -> M a -> Bool 180 | fits w x = maxWidth x <= w 181 | 182 | instance Layout ODoc where 183 | text s = MkDoc $ \_ -> [text s] 184 | flush (MkDoc xs) = MkDoc $ \w -> fmap flush (xs w) 185 | annotate a (MkDoc xs) = MkDoc $ \w -> fmap (annotate a) (xs w) 186 | 187 | renderWith :: (Monoid r, Annotation a) 188 | => Options a r -- ^ rendering options 189 | -> ODoc a -- ^ renderable 190 | -> r 191 | renderWith opts d = case xs of 192 | [] -> error "No suitable layout found." 193 | ((_ :-: x):_) -> renderWithL opts x 194 | where 195 | pageWidth = optsPageWidth opts 196 | xs = discardInvalid pageWidth (fromDoc d pageWidth) 197 | 198 | onlySingleLine :: [Pair M L a] -> [Pair M L a] 199 | onlySingleLine = takeWhile (\(M{..} :-: _) -> height == 0) 200 | 201 | spaces :: (Monoid a,Layout l) => Int -> l a 202 | spaces n = text $ replicate n ' ' 203 | 204 | 205 | -- | The document @(x \$$> y)@ concatenates document @x@ and @y@ with 206 | -- a linebreak in between. (infixr 5) 207 | ($$) :: (Layout d, Monoid a, Semigroup (d a)) => d a -> d a -> d a 208 | a $$ b = flush a <> b 209 | 210 | second f (a,b) = (a, f b) 211 | 212 | groupingBy :: Monoid a => String -> [(Int,Doc a)] -> Doc a 213 | groupingBy _ [] = mempty 214 | groupingBy separator ms = MkDoc $ \w -> 215 | let mws = map (second (($ w) . fromDoc)) ms 216 | (_,lastMw) = last mws 217 | hcatElems = map (onlySingleLine . snd) (init mws) ++ [lastMw] -- all the elements except the first must fit on a single line 218 | vcatElems = map (\(indent,x) -> map (spaces indent <>) x) mws 219 | horizontal = discardInvalid w $ foldr1 (liftA2 (\x y -> x <> text separator <> y)) hcatElems 220 | vertical = foldr1 (\xs ys -> bestsOn frst [[x $$ y | y <- ys] | x <- xs]) vcatElems 221 | in bestsOn frst [horizontal,vertical] 222 | 223 | data Pair f g a = (:-:) {frst :: f a, scnd :: g a} 224 | deriving (Functor,Foldable,Traversable) 225 | 226 | instance (Semigroup (f a), Semigroup (g a)) => Semigroup (Pair f g a) where 227 | (x :-: y) <> (x' :-: y') = (x <> x') :-: (y <> y') 228 | instance (Monoid (f a), Monoid (g a)) => Monoid (Pair f g a) where 229 | mempty = mempty :-: mempty 230 | 231 | instance (Layout a, Layout b) => Layout (Pair a b) where 232 | text s = text s :-: text s 233 | flush (a:-:b) = (flush a:-: flush b) 234 | annotate x (a:-:b) = (annotate x a:-:annotate x b) 235 | 236 | instance Monoid a => IsString (Doc a) where 237 | fromString = text 238 | 239 | type Annotation a = (Monoid a) 240 | type Doc = ODoc 241 | 242 | -- tt :: Doc () 243 | -- tt = groupingBy " " $ map (4,) $ 244 | -- ((replicate 4 $ groupingBy " " (map (4,) (map text ["fw"]))) ++ 245 | -- [groupingBy " " (map (0,) (map text ["fw","arstnwfyut","arstin","arstaruf"]))]) 246 | 247 | -- $setup 248 | -- >>> import Text.PrettyPrint.Compact 249 | -- >>> import Data.Monoid 250 | -- >>> import Data.Char 251 | -------------------------------------------------------------------------------- /acmthm.sty: -------------------------------------------------------------------------------- 1 | %% 2 | %% This is file `acmthm.sty', 3 | %% generated with the docstrip utility. 4 | %% 5 | %% The original source files were: 6 | %% 7 | %% acmart.dtx (with options: `acmthm') 8 | %% 9 | %% IMPORTANT NOTICE: 10 | %% 11 | %% For the copyright see the source file. 12 | %% 13 | %% Any modified versions of this file must be renamed 14 | %% with new filenames distinct from acmthm.sty. 15 | %% 16 | %% For distribution of the original source see the terms 17 | %% for copying and modification in the file acmart.dtx. 18 | %% 19 | %% This generated file may be distributed as long as the 20 | %% original source files, as listed above, are part of the 21 | %% same distribution. (The sources need not necessarily be 22 | %% in the same archive or directory.) 23 | %% \CharacterTable 24 | %% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z 25 | %% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z 26 | %% Digits \0\1\2\3\4\5\6\7\8\9 27 | %% Exclamation \! Double quote \" Hash (number) \# 28 | %% Dollar \$ Percent \% Ampersand \& 29 | %% Acute accent \' Left paren \( Right paren \) 30 | %% Asterisk \* Plus \+ Comma \, 31 | %% Minus \- Point \. Solidus \/ 32 | %% Colon \: Semicolon \; Less than \< 33 | %% Equals \= Greater than \> Question mark \? 34 | %% Commercial at \@ Left bracket \[ Backslash \\ 35 | %% Right bracket \] Circumflex \^ Underscore \_ 36 | %% Grave accent \` Left brace \{ Vertical bar \| 37 | %% Right brace \} Tilde \~} 38 | \ProvidesPackage{acmthm} 39 | [2017/08/15 v1.45 Typesetting articles for Association of 40 | Computing Machinery] 41 | \def\@acmplainbodyfont{\itshape} 42 | \def\@acmplainindent{\parindent} 43 | \def\@acmplainheadfont{\scshape} 44 | \def\@acmplainnotefont{\@empty} 45 | \ifcase\ACM@format@nr 46 | \relax % manuscript 47 | \or % acmsmall 48 | \or % acmlarge 49 | \or % acmtog 50 | \or % sigconf 51 | \or % siggraph 52 | \or % sigplan 53 | \def\@acmplainbodyfont{\itshape} 54 | \def\@acmplainindent{\z@} 55 | \def\@acmplainheadfont{\bfseries} 56 | \def\@acmplainnotefont{\normalfont} 57 | \or % sigchi 58 | \or % sigchi-a 59 | \fi 60 | \newtheoremstyle{acmplain}% 61 | {.5\baselineskip\@plus.2\baselineskip 62 | \@minus.2\baselineskip}% space above 63 | {.5\baselineskip\@plus.2\baselineskip 64 | \@minus.2\baselineskip}% space below 65 | {\@acmplainbodyfont}% body font 66 | {\@acmplainindent}% indent amount 67 | {\@acmplainheadfont}% head font 68 | {.}% punctuation after head 69 | {.5em}% spacing after head 70 | {\thmname{#1}\thmnumber{ #2}\thmnote{ {\@acmplainnotefont(#3)}}}% head spec 71 | \def\@acmdefinitionbodyfont{\normalfont} 72 | \def\@acmdefinitionindent{\parindent} 73 | \def\@acmdefinitionheadfont{\itshape} 74 | \def\@acmdefinitionnotefont{\@empty} 75 | \ifcase\ACM@format@nr 76 | \relax % manuscript 77 | \or % acmsmall 78 | \or % acmlarge 79 | \or % acmtog 80 | \or % sigconf 81 | \or % siggraph 82 | \or % sigplan 83 | \def\@acmdefinitionbodyfont{\normalfont} 84 | \def\@acmdefinitionindent{\z@} 85 | \def\@acmdefinitionheadfont{\bfseries} 86 | \def\@acmdefinitionnotefont{\normalfont} 87 | \or % sigchi 88 | \or % sigchi-a 89 | \fi 90 | \newtheoremstyle{acmdefinition}% 91 | {.5\baselineskip\@plus.2\baselineskip 92 | \@minus.2\baselineskip}% space above 93 | {.5\baselineskip\@plus.2\baselineskip 94 | \@minus.2\baselineskip}% space below 95 | {\@acmdefinitionbodyfont}% body font 96 | {\@acmdefinitionindent}% indent amount 97 | {\@acmdefinitionheadfont}% head font 98 | {.}% punctuation after head 99 | {.5em}% spacing after head 100 | {\thmname{#1}\thmnumber{ #2}\thmnote{ {\@acmdefinitionnotefont(#3)}}}% head spec 101 | \theoremstyle{acmplain} 102 | \newtheorem{theorem}{Theorem}[section] 103 | \newtheorem{conjecture}[theorem]{Conjecture} 104 | \newtheorem{proposition}[theorem]{Proposition} 105 | \newtheorem{lemma}[theorem]{Lemma} 106 | \newtheorem{corollary}[theorem]{Corollary} 107 | \theoremstyle{acmdefinition} 108 | \newtheorem{example}[theorem]{Example} 109 | \newtheorem{definition}[theorem]{Definition} 110 | \theoremstyle{acmplain} 111 | \def\@proofnamefont{\scshape} 112 | \def\@proofindent{\indent} 113 | \ifcase\ACM@format@nr 114 | \relax % manuscript 115 | \or % acmsmall 116 | \or % acmlarge 117 | \or % acmtog 118 | \or % sigconf 119 | \or % siggraph 120 | \or % sigplan 121 | \def\@proofnamefont{\itshape} 122 | \def\@proofindent{\noindent} 123 | \or % sigchi 124 | \or % sigchi-a 125 | \fi 126 | \renewenvironment{proof}[1][\proofname]{\par 127 | \pushQED{\qed}% 128 | \normalfont \topsep6\p@\@plus6\p@\relax 129 | \trivlist 130 | \item[\@proofindent\hskip\labelsep 131 | {\@proofnamefont #1\@addpunct{.}}]\ignorespaces 132 | }{% 133 | \popQED\endtrivlist\@endpefalse 134 | } 135 | \endinput 136 | %% 137 | %% End of file `acmthm.sty'. 138 | -------------------------------------------------------------------------------- /bench/Benchmark.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Prelude () 4 | import Prelude.Compat 5 | 6 | import Control.DeepSeq (force) 7 | import Control.Exception (evaluate) 8 | import Data.Aeson (Value (..), decode) 9 | import Data.Foldable (toList) 10 | 11 | import qualified Criterion.Main as C 12 | import qualified Data.HashMap.Lazy as H 13 | import qualified Data.ByteString.Lazy as BSL 14 | import qualified Data.Text.Lazy as TL 15 | import qualified Data.Text.Lazy.Builder as TLB 16 | 17 | import qualified Text.PrettyPrint.Compact as PC 18 | import qualified Text.PrettyPrint.HughesPJ as HPJ 19 | import qualified Text.PrettyPrint.Leijen as WL 20 | 21 | prettiestJSON :: Value -> PC.Doc () 22 | prettiestJSON (Bool True) = PC.text "true" 23 | prettiestJSON (Bool False) = PC.text "false" 24 | prettiestJSON (Object o) = PC.encloseSep (PC.text "{") (PC.text "}") (PC.text ",") (map prettyKV $ H.toList o) 25 | where prettyKV (k,v) = PC.text (show k) PC.<> PC.text ":" PC.<+> prettiestJSON v 26 | prettiestJSON (String s) = PC.string (show s) 27 | prettiestJSON (Array a) = PC.encloseSep (PC.text "[") (PC.text "]") (PC.text ",") (map prettiestJSON $ toList a) 28 | prettiestJSON Null = PC.mempty 29 | prettiestJSON (Number n) = PC.text (show n) 30 | 31 | pcRenderText :: Monoid a => PC.Doc a -> TL.Text 32 | pcRenderText = TLB.toLazyText . PC.renderWith PC.defaultOptions 33 | { PC.optsAnnotate = \_ -> TLB.fromString } 34 | 35 | wlJSON :: Value -> WL.Doc 36 | wlJSON (Bool True) = WL.text "true" 37 | wlJSON (Bool False) = WL.text "false" 38 | wlJSON (Object o) = WL.encloseSep (WL.text "{") (WL.text "}") (WL.text ",") (map prettyKV $ H.toList o) 39 | where prettyKV (k,v) = WL.text (show k) WL.<> WL.text ":" WL.<+> wlJSON v 40 | wlJSON (String s) = WL.string (show s) 41 | wlJSON (Array a) = WL.encloseSep (WL.text "[") (WL.text "]") (WL.text ",") (map wlJSON $ toList a) 42 | wlJSON Null = WL.empty 43 | wlJSON (Number n) = WL.text (show n) 44 | 45 | wlRender :: WL.Doc -> String 46 | wlRender d = WL.displayS (WL.renderPretty 1 80 d) "" 47 | 48 | hpjEncloseSep :: HPJ.Doc -> HPJ.Doc -> HPJ.Doc -> [HPJ.Doc] -> HPJ.Doc 49 | hpjEncloseSep open close sep list = open HPJ.<> HPJ.sep (HPJ.punctuate sep list) HPJ.<> close 50 | 51 | hpjJSON :: Value -> HPJ.Doc 52 | hpjJSON (Bool True) = HPJ.text "true" 53 | hpjJSON (Bool False) = HPJ.text "false" 54 | hpjJSON (Object o) = hpjEncloseSep (HPJ.text "{") (HPJ.text "}") (HPJ.text ",") (map prettyKV $ H.toList o) 55 | where prettyKV (k,v) = HPJ.text (show k) HPJ.<> HPJ.text ":" HPJ.<+> hpjJSON v 56 | hpjJSON (String s) = HPJ.text (show s) 57 | hpjJSON (Array a) = hpjEncloseSep (HPJ.text "[") (HPJ.text "]") (HPJ.text ",") (map hpjJSON $ toList a) 58 | hpjJSON Null = HPJ.empty 59 | hpjJSON (Number n) = HPJ.text (show n) 60 | 61 | readJSONValue :: FilePath -> IO Value 62 | readJSONValue fname = do 63 | contents <- BSL.readFile fname 64 | let Just inpJson = decode contents 65 | return inpJson 66 | 67 | main :: IO () 68 | main = do 69 | smallValue <- evaluate . force =<< readJSONValue "bench/small.json" 70 | bigValue <- evaluate . force =<< readJSONValue "bench/big.json" 71 | 72 | C.defaultMain 73 | [ C.bgroup "small" 74 | [ C.bench "pretty-compact" $ C.nf (PC.render . prettiestJSON) smallValue 75 | , C.bench "pretty-compact Text" $ C.nf (pcRenderText . prettiestJSON) smallValue 76 | , C.bench "pretty" $ C.nf (HPJ.render . hpjJSON) smallValue 77 | , C.bench "wl-pprint" $ C.nf (wlRender . wlJSON) smallValue 78 | ] 79 | , C.bgroup "big" 80 | [ C.bench "pretty-compact" $ C.nf (PC.render . prettiestJSON) bigValue 81 | , C.bench "pretty-compact Text" $ C.nf (pcRenderText . prettiestJSON) bigValue 82 | , C.bench "pretty" $ C.nf (HPJ.render . hpjJSON) bigValue 83 | , C.bench "wl-pprint" $ C.nf (wlRender . wlJSON) bigValue 84 | ] 85 | ] 86 | -------------------------------------------------------------------------------- /bench/small.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "58fa643cbd123f5c27d3532c", 4 | "index": 0, 5 | "guid": "36503005-a114-4a3d-b196-4e88cb277b29", 6 | "isActive": false, 7 | "balance": "$3,794.48", 8 | "picture": "http://placehold.it/32x32", 9 | "age": 36, 10 | "eyeColor": "blue", 11 | "name": "Rosanne Alexander", 12 | "gender": "female", 13 | "company": "ISOTRONIC", 14 | "email": "rosannealexander@isotronic.com", 15 | "phone": "+1 (968) 401-2101", 16 | "address": "337 Front Street, Wintersburg, Hawaii, 4322", 17 | "registered": "2015-06-10T05:56:29 -02:00", 18 | "latitude": -42.698328, 19 | "longitude": -55.888376, 20 | "tags": [ 21 | "ullamco", 22 | "sit", 23 | "qui", 24 | "mollit", 25 | "consectetur", 26 | "ex", 27 | "aliquip" 28 | ], 29 | "friends": [ 30 | { 31 | "id": 0, 32 | "name": "Maryann Trujillo" 33 | }, 34 | { 35 | "id": 1, 36 | "name": "Horn Landry" 37 | }, 38 | { 39 | "id": 2, 40 | "name": "Ingrid Tillman" 41 | } 42 | ], 43 | "greeting": "Hello, Rosanne Alexander! You have 1 unread messages.", 44 | "favoriteFruit": "banana" 45 | }, 46 | { 47 | "_id": "58fa643cddc3be8eea3690aa", 48 | "index": 1, 49 | "guid": "87471ed2-12e0-4dab-b6ea-df2a81641680", 50 | "isActive": true, 51 | "balance": "$1,897.24", 52 | "picture": "http://placehold.it/32x32", 53 | "age": 28, 54 | "eyeColor": "brown", 55 | "name": "Marina Estes", 56 | "gender": "female", 57 | "company": "GENMEX", 58 | "email": "marinaestes@genmex.com", 59 | "phone": "+1 (840) 563-2577", 60 | "address": "445 Lenox Road, Sunwest, Virginia, 6753", 61 | "registered": "2015-02-23T10:42:02 -01:00", 62 | "latitude": -71.383786, 63 | "longitude": -115.65829, 64 | "tags": [ 65 | "sit", 66 | "veniam", 67 | "fugiat", 68 | "elit", 69 | "velit", 70 | "labore", 71 | "reprehenderit" 72 | ], 73 | "friends": [ 74 | { 75 | "id": 0, 76 | "name": "Sherri Lloyd" 77 | }, 78 | { 79 | "id": 1, 80 | "name": "Lorene Richmond" 81 | }, 82 | { 83 | "id": 2, 84 | "name": "Esther Peterson" 85 | } 86 | ], 87 | "greeting": "Hello, Marina Estes! You have 9 unread messages.", 88 | "favoriteFruit": "strawberry" 89 | }, 90 | { 91 | "_id": "58fa643c118561ef3cde7ee0", 92 | "index": 2, 93 | "guid": "510f8d52-443c-43cc-9570-7a0fc6b4b81b", 94 | "isActive": true, 95 | "balance": "$2,169.93", 96 | "picture": "http://placehold.it/32x32", 97 | "age": 37, 98 | "eyeColor": "green", 99 | "name": "Rasmussen Petersen", 100 | "gender": "male", 101 | "company": "RONBERT", 102 | "email": "rasmussenpetersen@ronbert.com", 103 | "phone": "+1 (834) 519-3009", 104 | "address": "208 Jefferson Street, Knowlton, New Mexico, 6246", 105 | "registered": "2016-02-26T07:34:57 -01:00", 106 | "latitude": 38.643915, 107 | "longitude": 67.605524, 108 | "tags": [ 109 | "voluptate", 110 | "anim", 111 | "est", 112 | "do", 113 | "aliquip", 114 | "ut", 115 | "voluptate" 116 | ], 117 | "friends": [ 118 | { 119 | "id": 0, 120 | "name": "Brigitte Booth" 121 | }, 122 | { 123 | "id": 1, 124 | "name": "Acosta Melendez" 125 | }, 126 | { 127 | "id": 2, 128 | "name": "Laurie Wall" 129 | } 130 | ], 131 | "greeting": "Hello, Rasmussen Petersen! You have 10 unread messages.", 132 | "favoriteFruit": "apple" 133 | }, 134 | { 135 | "_id": "58fa643cd0b4b4b99eb46feb", 136 | "index": 3, 137 | "guid": "699e4649-3b70-48ca-91df-03cd6bac0af2", 138 | "isActive": true, 139 | "balance": "$3,885.16", 140 | "picture": "http://placehold.it/32x32", 141 | "age": 34, 142 | "eyeColor": "blue", 143 | "name": "Aurora Morin", 144 | "gender": "female", 145 | "company": "ENVIRE", 146 | "email": "auroramorin@envire.com", 147 | "phone": "+1 (889) 453-3938", 148 | "address": "745 Huntington Street, Movico, Georgia, 9340", 149 | "registered": "2014-02-23T07:15:07 -01:00", 150 | "latitude": -10.217937, 151 | "longitude": 45.804184, 152 | "tags": [ 153 | "tempor", 154 | "laboris", 155 | "occaecat", 156 | "irure", 157 | "proident", 158 | "pariatur", 159 | "minim" 160 | ], 161 | "friends": [ 162 | { 163 | "id": 0, 164 | "name": "Espinoza Deleon" 165 | }, 166 | { 167 | "id": 1, 168 | "name": "Celia Browning" 169 | }, 170 | { 171 | "id": 2, 172 | "name": "Myrtle Summers" 173 | } 174 | ], 175 | "greeting": "Hello, Aurora Morin! You have 9 unread messages.", 176 | "favoriteFruit": "strawberry" 177 | }, 178 | { 179 | "_id": "58fa643c57051c60269da6e1", 180 | "index": 4, 181 | "guid": "dd73dd64-9861-4764-96f8-cf929c9b3362", 182 | "isActive": false, 183 | "balance": "$1,885.09", 184 | "picture": "http://placehold.it/32x32", 185 | "age": 27, 186 | "eyeColor": "green", 187 | "name": "Trina Barber", 188 | "gender": "female", 189 | "company": "PASTURIA", 190 | "email": "trinabarber@pasturia.com", 191 | "phone": "+1 (841) 424-3833", 192 | "address": "407 Mill Lane, Lodoga, Louisiana, 4725", 193 | "registered": "2015-09-11T10:05:43 -02:00", 194 | "latitude": -45.297408, 195 | "longitude": 62.77687, 196 | "tags": [ 197 | "est", 198 | "ad", 199 | "cillum", 200 | "et", 201 | "eiusmod", 202 | "culpa", 203 | "culpa" 204 | ], 205 | "friends": [ 206 | { 207 | "id": 0, 208 | "name": "Eva Hahn" 209 | }, 210 | { 211 | "id": 1, 212 | "name": "Gonzales Gill" 213 | }, 214 | { 215 | "id": 2, 216 | "name": "Steele Sweet" 217 | } 218 | ], 219 | "greeting": "Hello, Trina Barber! You have 9 unread messages.", 220 | "favoriteFruit": "apple" 221 | }, 222 | { 223 | "_id": "58fa643cce839d59932801e6", 224 | "index": 5, 225 | "guid": "ec2333f7-1512-48d5-8ac7-3e208c094dbd", 226 | "isActive": false, 227 | "balance": "$2,354.45", 228 | "picture": "http://placehold.it/32x32", 229 | "age": 29, 230 | "eyeColor": "blue", 231 | "name": "Frost Rodriquez", 232 | "gender": "male", 233 | "company": "CALCULA", 234 | "email": "frostrodriquez@calcula.com", 235 | "phone": "+1 (986) 589-2486", 236 | "address": "517 Oceanic Avenue, Yogaville, Wisconsin, 4850", 237 | "registered": "2016-06-15T02:04:20 -02:00", 238 | "latitude": 13.662388, 239 | "longitude": 98.331963, 240 | "tags": [ 241 | "aliqua", 242 | "qui", 243 | "aliqua", 244 | "consectetur", 245 | "et", 246 | "laborum", 247 | "do" 248 | ], 249 | "friends": [ 250 | { 251 | "id": 0, 252 | "name": "Bradley Cherry" 253 | }, 254 | { 255 | "id": 1, 256 | "name": "Bullock Vinson" 257 | }, 258 | { 259 | "id": 2, 260 | "name": "Reynolds Chan" 261 | } 262 | ], 263 | "greeting": "Hello, Frost Rodriquez! You have 3 unread messages.", 264 | "favoriteFruit": "apple" 265 | }, 266 | { 267 | "_id": "58fa643ca613f5d222d62c14", 268 | "index": 6, 269 | "guid": "1d944356-30c7-4c65-8ac3-c801f30287a2", 270 | "isActive": false, 271 | "balance": "$1,122.68", 272 | "picture": "http://placehold.it/32x32", 273 | "age": 28, 274 | "eyeColor": "brown", 275 | "name": "Haley Schmidt", 276 | "gender": "male", 277 | "company": "BILLMED", 278 | "email": "haleyschmidt@billmed.com", 279 | "phone": "+1 (841) 529-3058", 280 | "address": "487 Joval Court, Bawcomville, Ohio, 2673", 281 | "registered": "2015-05-11T04:35:03 -02:00", 282 | "latitude": 58.202743, 283 | "longitude": 60.443077, 284 | "tags": [ 285 | "culpa", 286 | "enim", 287 | "esse", 288 | "ea", 289 | "consequat", 290 | "culpa", 291 | "eu" 292 | ], 293 | "friends": [ 294 | { 295 | "id": 0, 296 | "name": "Riggs Griffith" 297 | }, 298 | { 299 | "id": 1, 300 | "name": "Mclean Holden" 301 | }, 302 | { 303 | "id": 2, 304 | "name": "Odonnell Bird" 305 | } 306 | ], 307 | "greeting": "Hello, Haley Schmidt! You have 7 unread messages.", 308 | "favoriteFruit": "strawberry" 309 | }, 310 | { 311 | "_id": "58fa643c2d279fea2675a463", 312 | "index": 7, 313 | "guid": "ddb3726a-a529-4ae4-a065-ab4dd86c6a12", 314 | "isActive": true, 315 | "balance": "$1,827.19", 316 | "picture": "http://placehold.it/32x32", 317 | "age": 36, 318 | "eyeColor": "blue", 319 | "name": "Talley Garcia", 320 | "gender": "male", 321 | "company": "EXOTECHNO", 322 | "email": "talleygarcia@exotechno.com", 323 | "phone": "+1 (892) 454-2158", 324 | "address": "601 Meeker Avenue, Retsof, Alaska, 6016", 325 | "registered": "2016-05-17T02:52:04 -02:00", 326 | "latitude": 44.18819, 327 | "longitude": -179.612989, 328 | "tags": [ 329 | "officia", 330 | "qui", 331 | "esse", 332 | "voluptate", 333 | "laborum", 334 | "quis", 335 | "dolor" 336 | ], 337 | "friends": [ 338 | { 339 | "id": 0, 340 | "name": "Sue Odom" 341 | }, 342 | { 343 | "id": 1, 344 | "name": "Molina Gaines" 345 | }, 346 | { 347 | "id": 2, 348 | "name": "Cleveland Hickman" 349 | } 350 | ], 351 | "greeting": "Hello, Talley Garcia! You have 1 unread messages.", 352 | "favoriteFruit": "banana" 353 | }, 354 | { 355 | "_id": "58fa643c782ec64986475d1b", 356 | "index": 8, 357 | "guid": "227cef3b-a430-4a58-ba70-3ce3932bd01e", 358 | "isActive": false, 359 | "balance": "$3,874.94", 360 | "picture": "http://placehold.it/32x32", 361 | "age": 32, 362 | "eyeColor": "brown", 363 | "name": "Loraine Freeman", 364 | "gender": "female", 365 | "company": "LEXICONDO", 366 | "email": "lorainefreeman@lexicondo.com", 367 | "phone": "+1 (859) 595-3824", 368 | "address": "191 President Street, Chapin, Puerto Rico, 4617", 369 | "registered": "2014-08-05T08:26:16 -02:00", 370 | "latitude": 18.061135, 371 | "longitude": -156.984174, 372 | "tags": [ 373 | "mollit", 374 | "excepteur", 375 | "aliqua", 376 | "et", 377 | "non", 378 | "veniam", 379 | "nulla" 380 | ], 381 | "friends": [ 382 | { 383 | "id": 0, 384 | "name": "Lesley Hamilton" 385 | }, 386 | { 387 | "id": 1, 388 | "name": "Mccormick Armstrong" 389 | }, 390 | { 391 | "id": 2, 392 | "name": "Therese Collier" 393 | } 394 | ], 395 | "greeting": "Hello, Loraine Freeman! You have 3 unread messages.", 396 | "favoriteFruit": "apple" 397 | }, 398 | { 399 | "_id": "58fa643ca9b9b5d3937b0ba0", 400 | "index": 9, 401 | "guid": "66188d94-1a53-40f5-bfb0-d2d1efc3074a", 402 | "isActive": false, 403 | "balance": "$3,089.00", 404 | "picture": "http://placehold.it/32x32", 405 | "age": 33, 406 | "eyeColor": "brown", 407 | "name": "Durham Flowers", 408 | "gender": "male", 409 | "company": "ZANITY", 410 | "email": "durhamflowers@zanity.com", 411 | "phone": "+1 (955) 425-2201", 412 | "address": "627 Seaview Court, Motley, Marshall Islands, 2146", 413 | "registered": "2016-10-09T10:38:52 -02:00", 414 | "latitude": 14.257012, 415 | "longitude": 60.594318, 416 | "tags": [ 417 | "deserunt", 418 | "aliquip", 419 | "esse", 420 | "ex", 421 | "qui", 422 | "elit", 423 | "do" 424 | ], 425 | "friends": [ 426 | { 427 | "id": 0, 428 | "name": "Hill Wyatt" 429 | }, 430 | { 431 | "id": 1, 432 | "name": "Sears Dickerson" 433 | }, 434 | { 435 | "id": 2, 436 | "name": "Buck Ray" 437 | } 438 | ], 439 | "greeting": "Hello, Durham Flowers! You have 3 unread messages.", 440 | "favoriteFruit": "strawberry" 441 | }, 442 | { 443 | "_id": "58fa643c79d018a46f546504", 444 | "index": 10, 445 | "guid": "3bee5ea2-33c6-4dbd-b5ec-b6f2da2e4226", 446 | "isActive": false, 447 | "balance": "$2,020.94", 448 | "picture": "http://placehold.it/32x32", 449 | "age": 25, 450 | "eyeColor": "brown", 451 | "name": "Chrystal Clarke", 452 | "gender": "female", 453 | "company": "MICROLUXE", 454 | "email": "chrystalclarke@microluxe.com", 455 | "phone": "+1 (924) 448-3560", 456 | "address": "894 Covert Street, Groveville, Arizona, 1416", 457 | "registered": "2014-08-18T12:37:02 -02:00", 458 | "latitude": -41.970666, 459 | "longitude": -56.761927, 460 | "tags": [ 461 | "aute", 462 | "ipsum", 463 | "ea", 464 | "elit", 465 | "qui", 466 | "mollit", 467 | "sint" 468 | ], 469 | "friends": [ 470 | { 471 | "id": 0, 472 | "name": "Castro Cash" 473 | }, 474 | { 475 | "id": 1, 476 | "name": "Amparo Burnett" 477 | }, 478 | { 479 | "id": 2, 480 | "name": "Mason Perry" 481 | } 482 | ], 483 | "greeting": "Hello, Chrystal Clarke! You have 1 unread messages.", 484 | "favoriteFruit": "banana" 485 | }, 486 | { 487 | "_id": "58fa643ca3833fadf3b911bf", 488 | "index": 11, 489 | "guid": "14b32e71-bf71-43ea-bb40-157e4a0f7b62", 490 | "isActive": false, 491 | "balance": "$1,092.35", 492 | "picture": "http://placehold.it/32x32", 493 | "age": 37, 494 | "eyeColor": "blue", 495 | "name": "Hughes Chandler", 496 | "gender": "male", 497 | "company": "CONCILITY", 498 | "email": "hugheschandler@concility.com", 499 | "phone": "+1 (856) 442-3944", 500 | "address": "503 Clifford Place, Sunriver, Pennsylvania, 2559", 501 | "registered": "2016-02-17T01:02:28 -01:00", 502 | "latitude": -26.363652, 503 | "longitude": 40.487093, 504 | "tags": [ 505 | "adipisicing", 506 | "amet", 507 | "ea", 508 | "dolore", 509 | "sit", 510 | "laboris", 511 | "est" 512 | ], 513 | "friends": [ 514 | { 515 | "id": 0, 516 | "name": "Grant Ross" 517 | }, 518 | { 519 | "id": 1, 520 | "name": "Spears Vazquez" 521 | }, 522 | { 523 | "id": 2, 524 | "name": "Rosario Barrett" 525 | } 526 | ], 527 | "greeting": "Hello, Hughes Chandler! You have 4 unread messages.", 528 | "favoriteFruit": "banana" 529 | }, 530 | { 531 | "_id": "58fa643c21ae7265d7026790", 532 | "index": 12, 533 | "guid": "fc1913cb-9d02-45ac-a179-eefcfd0b1c1a", 534 | "isActive": false, 535 | "balance": "$2,926.31", 536 | "picture": "http://placehold.it/32x32", 537 | "age": 35, 538 | "eyeColor": "brown", 539 | "name": "Horton Avery", 540 | "gender": "male", 541 | "company": "TERASCAPE", 542 | "email": "hortonavery@terascape.com", 543 | "phone": "+1 (972) 402-3023", 544 | "address": "892 Withers Street, Shelby, Utah, 4803", 545 | "registered": "2015-10-19T01:37:12 -02:00", 546 | "latitude": -13.81, 547 | "longitude": 178.728114, 548 | "tags": [ 549 | "non", 550 | "eiusmod", 551 | "aliquip", 552 | "anim", 553 | "quis", 554 | "aliquip", 555 | "esse" 556 | ], 557 | "friends": [ 558 | { 559 | "id": 0, 560 | "name": "Collier Page" 561 | }, 562 | { 563 | "id": 1, 564 | "name": "Stevens Tucker" 565 | }, 566 | { 567 | "id": 2, 568 | "name": "Barnett Bryant" 569 | } 570 | ], 571 | "greeting": "Hello, Horton Avery! You have 10 unread messages.", 572 | "favoriteFruit": "strawberry" 573 | }, 574 | { 575 | "_id": "58fa643c9134ac3a44c6afaa", 576 | "index": 13, 577 | "guid": "9eb39bd4-92bb-467c-bd92-d19c2a3b21e3", 578 | "isActive": true, 579 | "balance": "$2,818.67", 580 | "picture": "http://placehold.it/32x32", 581 | "age": 25, 582 | "eyeColor": "brown", 583 | "name": "Salinas Duncan", 584 | "gender": "male", 585 | "company": "DEVILTOE", 586 | "email": "salinasduncan@deviltoe.com", 587 | "phone": "+1 (906) 497-2245", 588 | "address": "215 Cumberland Street, Kenvil, District Of Columbia, 8746", 589 | "registered": "2016-10-10T01:37:54 -02:00", 590 | "latitude": 75.956315, 591 | "longitude": 67.504447, 592 | "tags": [ 593 | "sit", 594 | "do", 595 | "commodo", 596 | "consectetur", 597 | "aliquip", 598 | "culpa", 599 | "minim" 600 | ], 601 | "friends": [ 602 | { 603 | "id": 0, 604 | "name": "Leonor Dalton" 605 | }, 606 | { 607 | "id": 1, 608 | "name": "Leblanc Graham" 609 | }, 610 | { 611 | "id": 2, 612 | "name": "Mendoza Francis" 613 | } 614 | ], 615 | "greeting": "Hello, Salinas Duncan! You have 6 unread messages.", 616 | "favoriteFruit": "strawberry" 617 | }, 618 | { 619 | "_id": "58fa643c2bdc62a160ef3425", 620 | "index": 14, 621 | "guid": "8de03765-107a-4b45-b86b-35df8619f336", 622 | "isActive": true, 623 | "balance": "$2,157.72", 624 | "picture": "http://placehold.it/32x32", 625 | "age": 24, 626 | "eyeColor": "brown", 627 | "name": "Mccarthy Mcclure", 628 | "gender": "male", 629 | "company": "ATOMICA", 630 | "email": "mccarthymcclure@atomica.com", 631 | "phone": "+1 (906) 408-3267", 632 | "address": "218 Pioneer Street, Brule, Virgin Islands, 2285", 633 | "registered": "2015-07-23T04:14:26 -02:00", 634 | "latitude": -6.665274, 635 | "longitude": 38.314708, 636 | "tags": [ 637 | "proident", 638 | "laborum", 639 | "officia", 640 | "aliqua", 641 | "et", 642 | "duis", 643 | "eiusmod" 644 | ], 645 | "friends": [ 646 | { 647 | "id": 0, 648 | "name": "Lela Gillespie" 649 | }, 650 | { 651 | "id": 1, 652 | "name": "Oneill Small" 653 | }, 654 | { 655 | "id": 2, 656 | "name": "Sanchez Russo" 657 | } 658 | ], 659 | "greeting": "Hello, Mccarthy Mcclure! You have 9 unread messages.", 660 | "favoriteFruit": "banana" 661 | }, 662 | { 663 | "_id": "58fa643ce6f7a9aa846e85d5", 664 | "index": 15, 665 | "guid": "6761812c-fb68-4e88-a650-22b8c16d78c0", 666 | "isActive": false, 667 | "balance": "$3,670.55", 668 | "picture": "http://placehold.it/32x32", 669 | "age": 22, 670 | "eyeColor": "brown", 671 | "name": "Wise Reese", 672 | "gender": "male", 673 | "company": "FLEETMIX", 674 | "email": "wisereese@fleetmix.com", 675 | "phone": "+1 (814) 535-2355", 676 | "address": "969 Summit Street, Grapeview, North Dakota, 7689", 677 | "registered": "2016-05-28T11:21:53 -02:00", 678 | "latitude": -49.333375, 679 | "longitude": 88.321634, 680 | "tags": [ 681 | "elit", 682 | "nostrud", 683 | "enim", 684 | "nulla", 685 | "reprehenderit", 686 | "sint", 687 | "esse" 688 | ], 689 | "friends": [ 690 | { 691 | "id": 0, 692 | "name": "Jefferson Stone" 693 | }, 694 | { 695 | "id": 1, 696 | "name": "Ada Wagner" 697 | }, 698 | { 699 | "id": 2, 700 | "name": "Eliza Yang" 701 | } 702 | ], 703 | "greeting": "Hello, Wise Reese! You have 5 unread messages.", 704 | "favoriteFruit": "strawberry" 705 | }, 706 | { 707 | "_id": "58fa643c55b0d27820618287", 708 | "index": 16, 709 | "guid": "1c295ac9-28e2-40e4-b992-cc4781e0c050", 710 | "isActive": true, 711 | "balance": "$1,082.81", 712 | "picture": "http://placehold.it/32x32", 713 | "age": 37, 714 | "eyeColor": "blue", 715 | "name": "Orr Hoover", 716 | "gender": "male", 717 | "company": "PEARLESSA", 718 | "email": "orrhoover@pearlessa.com", 719 | "phone": "+1 (932) 425-3016", 720 | "address": "511 Schenck Place, Rossmore, South Dakota, 1277", 721 | "registered": "2016-11-23T11:25:28 -01:00", 722 | "latitude": -2.662484, 723 | "longitude": 123.398385, 724 | "tags": [ 725 | "sunt", 726 | "veniam", 727 | "proident", 728 | "est", 729 | "quis", 730 | "minim", 731 | "esse" 732 | ], 733 | "friends": [ 734 | { 735 | "id": 0, 736 | "name": "Denise Carver" 737 | }, 738 | { 739 | "id": 1, 740 | "name": "Quinn Beasley" 741 | }, 742 | { 743 | "id": 2, 744 | "name": "Millie Dillard" 745 | } 746 | ], 747 | "greeting": "Hello, Orr Hoover! You have 4 unread messages.", 748 | "favoriteFruit": "apple" 749 | }, 750 | { 751 | "_id": "58fa643cc2597f9e50259a02", 752 | "index": 17, 753 | "guid": "39af3183-c113-4de7-a7b5-b33802991661", 754 | "isActive": false, 755 | "balance": "$1,801.67", 756 | "picture": "http://placehold.it/32x32", 757 | "age": 40, 758 | "eyeColor": "blue", 759 | "name": "Wendi Garner", 760 | "gender": "female", 761 | "company": "CENTREE", 762 | "email": "wendigarner@centree.com", 763 | "phone": "+1 (966) 412-3825", 764 | "address": "202 Douglass Street, Blandburg, Maine, 717", 765 | "registered": "2014-07-01T04:17:02 -02:00", 766 | "latitude": 40.694531, 767 | "longitude": -143.447607, 768 | "tags": [ 769 | "consequat", 770 | "anim", 771 | "pariatur", 772 | "dolor", 773 | "cupidatat", 774 | "proident", 775 | "ipsum" 776 | ], 777 | "friends": [ 778 | { 779 | "id": 0, 780 | "name": "Dorthy Parrish" 781 | }, 782 | { 783 | "id": 1, 784 | "name": "Richmond Buckley" 785 | }, 786 | { 787 | "id": 2, 788 | "name": "Charlotte Oneill" 789 | } 790 | ], 791 | "greeting": "Hello, Wendi Garner! You have 1 unread messages.", 792 | "favoriteFruit": "apple" 793 | }, 794 | { 795 | "_id": "58fa643c89d45bdc943c8a41", 796 | "index": 18, 797 | "guid": "c9934a91-46be-4da7-b5ef-83b0d344e7d6", 798 | "isActive": false, 799 | "balance": "$1,707.63", 800 | "picture": "http://placehold.it/32x32", 801 | "age": 21, 802 | "eyeColor": "green", 803 | "name": "Rhea Henry", 804 | "gender": "female", 805 | "company": "AUSTECH", 806 | "email": "rheahenry@austech.com", 807 | "phone": "+1 (991) 405-3614", 808 | "address": "693 Chase Court, Hall, Arkansas, 5924", 809 | "registered": "2015-10-18T12:16:15 -02:00", 810 | "latitude": -26.759549, 811 | "longitude": 75.479041, 812 | "tags": [ 813 | "ex", 814 | "commodo", 815 | "non", 816 | "labore", 817 | "in", 818 | "proident", 819 | "cillum" 820 | ], 821 | "friends": [ 822 | { 823 | "id": 0, 824 | "name": "Reed Sherman" 825 | }, 826 | { 827 | "id": 1, 828 | "name": "Julia Sanchez" 829 | }, 830 | { 831 | "id": 2, 832 | "name": "Weaver Benjamin" 833 | } 834 | ], 835 | "greeting": "Hello, Rhea Henry! You have 7 unread messages.", 836 | "favoriteFruit": "apple" 837 | }, 838 | { 839 | "_id": "58fa643c60d9148464335a69", 840 | "index": 19, 841 | "guid": "3f0d130f-7c2d-40cf-96fd-3598614c0bff", 842 | "isActive": true, 843 | "balance": "$3,497.28", 844 | "picture": "http://placehold.it/32x32", 845 | "age": 23, 846 | "eyeColor": "green", 847 | "name": "Rivera Cervantes", 848 | "gender": "male", 849 | "company": "FUELTON", 850 | "email": "riveracervantes@fuelton.com", 851 | "phone": "+1 (907) 446-3694", 852 | "address": "830 Canarsie Road, Hilltop, Missouri, 5746", 853 | "registered": "2014-08-14T01:30:39 -02:00", 854 | "latitude": -2.53255, 855 | "longitude": 28.384326, 856 | "tags": [ 857 | "eiusmod", 858 | "est", 859 | "officia", 860 | "officia", 861 | "amet", 862 | "sunt", 863 | "consectetur" 864 | ], 865 | "friends": [ 866 | { 867 | "id": 0, 868 | "name": "Leach Hammond" 869 | }, 870 | { 871 | "id": 1, 872 | "name": "Kim Wiggins" 873 | }, 874 | { 875 | "id": 2, 876 | "name": "Rochelle Harris" 877 | } 878 | ], 879 | "greeting": "Hello, Rivera Cervantes! You have 6 unread messages.", 880 | "favoriteFruit": "banana" 881 | }, 882 | { 883 | "_id": "58fa643cf470605b10bda614", 884 | "index": 20, 885 | "guid": "3d211b55-c5f8-43a9-85f5-eb52ba7e73c8", 886 | "isActive": true, 887 | "balance": "$2,143.91", 888 | "picture": "http://placehold.it/32x32", 889 | "age": 35, 890 | "eyeColor": "blue", 891 | "name": "Carolyn Brock", 892 | "gender": "female", 893 | "company": "COLAIRE", 894 | "email": "carolynbrock@colaire.com", 895 | "phone": "+1 (802) 449-3410", 896 | "address": "628 Herbert Street, Foxworth, Northern Mariana Islands, 6950", 897 | "registered": "2015-11-11T06:48:12 -01:00", 898 | "latitude": 8.890931, 899 | "longitude": 47.838565, 900 | "tags": [ 901 | "exercitation", 902 | "veniam", 903 | "nisi", 904 | "Lorem", 905 | "in", 906 | "in", 907 | "aliquip" 908 | ], 909 | "friends": [ 910 | { 911 | "id": 0, 912 | "name": "Alicia Tyson" 913 | }, 914 | { 915 | "id": 1, 916 | "name": "Crosby Harvey" 917 | }, 918 | { 919 | "id": 2, 920 | "name": "Weeks Richards" 921 | } 922 | ], 923 | "greeting": "Hello, Carolyn Brock! You have 8 unread messages.", 924 | "favoriteFruit": "apple" 925 | }, 926 | { 927 | "_id": "58fa643cf8cce9f086327741", 928 | "index": 21, 929 | "guid": "cede4f9a-0fae-4f48-bb15-9c43f9245b24", 930 | "isActive": true, 931 | "balance": "$1,704.77", 932 | "picture": "http://placehold.it/32x32", 933 | "age": 21, 934 | "eyeColor": "green", 935 | "name": "Johns England", 936 | "gender": "male", 937 | "company": "IDEGO", 938 | "email": "johnsengland@idego.com", 939 | "phone": "+1 (845) 521-3334", 940 | "address": "783 Baughman Place, Jennings, New Hampshire, 9370", 941 | "registered": "2017-03-01T04:02:22 -01:00", 942 | "latitude": -83.160798, 943 | "longitude": 87.211201, 944 | "tags": [ 945 | "aliqua", 946 | "est", 947 | "sunt", 948 | "fugiat", 949 | "tempor", 950 | "amet", 951 | "labore" 952 | ], 953 | "friends": [ 954 | { 955 | "id": 0, 956 | "name": "Becker Holt" 957 | }, 958 | { 959 | "id": 1, 960 | "name": "Sweet Salinas" 961 | }, 962 | { 963 | "id": 2, 964 | "name": "Short Howard" 965 | } 966 | ], 967 | "greeting": "Hello, Johns England! You have 5 unread messages.", 968 | "favoriteFruit": "apple" 969 | }, 970 | { 971 | "_id": "58fa643cd6b551057ada4d5f", 972 | "index": 22, 973 | "guid": "412420c4-dbe3-4051-8eda-c71e72350c83", 974 | "isActive": false, 975 | "balance": "$3,607.60", 976 | "picture": "http://placehold.it/32x32", 977 | "age": 27, 978 | "eyeColor": "blue", 979 | "name": "Calderon Marsh", 980 | "gender": "male", 981 | "company": "ACCUPHARM", 982 | "email": "calderonmarsh@accupharm.com", 983 | "phone": "+1 (817) 506-2759", 984 | "address": "895 Olive Street, Cleary, Massachusetts, 748", 985 | "registered": "2015-08-27T10:16:42 -02:00", 986 | "latitude": -77.307137, 987 | "longitude": -97.875602, 988 | "tags": [ 989 | "deserunt", 990 | "ut", 991 | "elit", 992 | "exercitation", 993 | "deserunt", 994 | "consequat", 995 | "ullamco" 996 | ], 997 | "friends": [ 998 | { 999 | "id": 0, 1000 | "name": "Dorsey Peck" 1001 | }, 1002 | { 1003 | "id": 1, 1004 | "name": "Victoria Bishop" 1005 | }, 1006 | { 1007 | "id": 2, 1008 | "name": "Lynnette Atkins" 1009 | } 1010 | ], 1011 | "greeting": "Hello, Calderon Marsh! You have 9 unread messages.", 1012 | "favoriteFruit": "banana" 1013 | }, 1014 | { 1015 | "_id": "58fa643c1610f66f919fcf27", 1016 | "index": 23, 1017 | "guid": "2efa99c1-706f-4cee-a652-6f4a6d57b019", 1018 | "isActive": true, 1019 | "balance": "$3,642.75", 1020 | "picture": "http://placehold.it/32x32", 1021 | "age": 33, 1022 | "eyeColor": "green", 1023 | "name": "Donaldson Bartlett", 1024 | "gender": "male", 1025 | "company": "VIAGRAND", 1026 | "email": "donaldsonbartlett@viagrand.com", 1027 | "phone": "+1 (800) 508-3608", 1028 | "address": "660 Irving Street, Canoochee, Delaware, 1011", 1029 | "registered": "2014-12-09T12:22:35 -01:00", 1030 | "latitude": -87.279775, 1031 | "longitude": 156.516545, 1032 | "tags": [ 1033 | "reprehenderit", 1034 | "fugiat", 1035 | "tempor", 1036 | "sunt", 1037 | "proident", 1038 | "aliqua", 1039 | "qui" 1040 | ], 1041 | "friends": [ 1042 | { 1043 | "id": 0, 1044 | "name": "Whitfield Cannon" 1045 | }, 1046 | { 1047 | "id": 1, 1048 | "name": "Glenna Rice" 1049 | }, 1050 | { 1051 | "id": 2, 1052 | "name": "Cox Mendez" 1053 | } 1054 | ], 1055 | "greeting": "Hello, Donaldson Bartlett! You have 7 unread messages.", 1056 | "favoriteFruit": "banana" 1057 | } 1058 | ] 1059 | -------------------------------------------------------------------------------- /benchmark-40.dat: -------------------------------------------------------------------------------- 1 | [(1,1,Estimate {estPoint = 3.7151087251034575e-6, estLowerBound = 3.7091288532977598e-6, estUpperBound = 3.7228319176463368e-6, estConfidenceLevel = 0.95}),(2,1,Estimate {estPoint = 2.395507603799458e-5, estLowerBound = 2.3433701772053454e-5, estUpperBound = 2.511440695894828e-5, estConfidenceLevel = 0.95}),(3,2,Estimate {estPoint = 1.0574913227106933e-4, estLowerBound = 1.0484459731212165e-4, estUpperBound = 1.0750552059616911e-4, estConfidenceLevel = 0.95}),(4,4,Estimate {estPoint = 4.085844661961092e-4, estLowerBound = 4.0693891274438493e-4, estUpperBound = 4.1314984645160084e-4, estConfidenceLevel = 0.95}),(5,8,Estimate {estPoint = 1.3053139882584046e-3, estLowerBound = 1.299077497251588e-3, estUpperBound = 1.319207973438388e-3, estConfidenceLevel = 0.95}),(6,16,Estimate {estPoint = 3.3697662213044294e-3, estLowerBound = 3.3582360373502853e-3, estUpperBound = 3.3865541947420393e-3, estConfidenceLevel = 0.95}),(7,32,Estimate {estPoint = 7.85637646319005e-3, estLowerBound = 7.817367216874791e-3, estUpperBound = 7.94980280736924e-3, estConfidenceLevel = 0.95}),(8,64,Estimate {estPoint = 1.7236721337228117e-2, estLowerBound = 1.7089640941335558e-2, estUpperBound = 1.7435179517581147e-2, estConfidenceLevel = 0.95}),(9,128,Estimate {estPoint = 3.60097879485946e-2, estLowerBound = 3.583506533739833e-2, estUpperBound = 3.6359155262747696e-2, estConfidenceLevel = 0.95}),(10,256,Estimate {estPoint = 7.385129052159707e-2, estLowerBound = 7.325958034344801e-2, estUpperBound = 7.499184725955058e-2, estConfidenceLevel = 0.95}),(11,512,Estimate {estPoint = 0.14920988456994957, estLowerBound = 0.14810853823731182, estUpperBound = 0.15024893351137014, estConfidenceLevel = 0.95}),(12,1024,Estimate {estPoint = 0.2997344183492022, estLowerBound = 0.297437859397056, estUpperBound = 0.3030698797313743, estConfidenceLevel = 0.95}),(13,2048,Estimate {estPoint = 0.6164856374494551, estLowerBound = 0.6002555360806846, estUpperBound = 0.6249599051429429, estConfidenceLevel = 0.95}),(14,4096,Estimate {estPoint = 1.2268213426997416, estLowerBound = 1.2012620388301267, estUpperBound = 1.270919359106761, estConfidenceLevel = 0.95}),(15,8192,Estimate {estPoint = 2.4949951178665066, estLowerBound = 2.43343768366254, estUpperBound = 2.5431781173481514, estConfidenceLevel = 0.95})] -------------------------------------------------------------------------------- /benchmark-80.dat: -------------------------------------------------------------------------------- 1 | [(1,1,Estimate {estPoint = 9.976395855385608e-7, estLowerBound = 9.974700678317964e-7, estUpperBound = 9.978243979518375e-7, estConfidenceLevel = 0.95}),(2,1,Estimate {estPoint = 5.182008680451049e-6, estLowerBound = 5.17762536675147e-6, estUpperBound = 5.187120574381438e-6, estConfidenceLevel = 0.95}),(3,1,Estimate {estPoint = 2.4318160010848243e-5, estLowerBound = 2.4310866215935263e-5, estUpperBound = 2.4326345205417397e-5, estConfidenceLevel = 0.95}),(4,1,Estimate {estPoint = 1.0134413094513647e-4, estLowerBound = 1.0125432576448135e-4, estUpperBound = 1.0154745001400137e-4, estConfidenceLevel = 0.95}),(5,2,Estimate {estPoint = 3.899475193786045e-4, estLowerBound = 3.8980142315864533e-4, estUpperBound = 3.9012286781700057e-4, estConfidenceLevel = 0.95}),(6,4,Estimate {estPoint = 1.2820843708179804e-3, estLowerBound = 1.2727884667766243e-3, estUpperBound = 1.297717634812151e-3, estConfidenceLevel = 0.95}),(7,8,Estimate {estPoint = 3.5860865730375944e-3, estLowerBound = 3.5799549269530514e-3, estUpperBound = 3.608363379294703e-3, estConfidenceLevel = 0.95}),(8,16,Estimate {estPoint = 8.402313851597962e-3, estLowerBound = 8.384041090695358e-3, estUpperBound = 8.487575906639563e-3, estConfidenceLevel = 0.95}),(9,32,Estimate {estPoint = 1.8784319771934836e-2, estLowerBound = 1.8759830523107045e-2, estUpperBound = 1.882784334050846e-2, estConfidenceLevel = 0.95}),(10,64,Estimate {estPoint = 3.97566967387092e-2, estLowerBound = 3.9680491050843585e-2, estUpperBound = 3.9990946106976226e-2, estConfidenceLevel = 0.95}),(11,128,Estimate {estPoint = 8.304278968548114e-2, estLowerBound = 8.28789462435401e-2, estUpperBound = 8.359425969145687e-2, estConfidenceLevel = 0.95}),(12,256,Estimate {estPoint = 0.17149253458910443, estLowerBound = 0.17136251425849047, estUpperBound = 0.17180432756491712, estConfidenceLevel = 0.95}),(13,512,Estimate {estPoint = 0.3502415523815839, estLowerBound = 0.349935909253637, estUpperBound = 0.3505028145207188, estConfidenceLevel = 0.95}),(14,1025,Estimate {estPoint = 0.7065993079608451, estLowerBound = 0.7059085884349412, estUpperBound = 0.7072640010063217, estConfidenceLevel = 0.95}),(15,2052,Estimate {estPoint = 1.4207846643302944, estLowerBound = 1.418598025557677, estUpperBound = 1.4219966099209973, estConfidenceLevel = 0.95})] -------------------------------------------------------------------------------- /benchmark-random.dat: -------------------------------------------------------------------------------- 1 | [(1,1,Estimate {estPoint = 6.644624485600602e-7, estLowerBound = 6.642248784979817e-7, estUpperBound = 6.647123643393149e-7, estConfidenceLevel = 0.95}),(2,1,Estimate {estPoint = 6.681206190015363e-7, estLowerBound = 6.67888352569233e-7, estUpperBound = 6.685578439003214e-7, estConfidenceLevel = 0.95}),(3,1,Estimate {estPoint = 6.686960561132868e-7, estLowerBound = 6.6846327626785e-7, estUpperBound = 6.689773443351583e-7, estConfidenceLevel = 0.95}),(4,1,Estimate {estPoint = 6.685674313837654e-7, estLowerBound = 6.682799716375463e-7, estUpperBound = 6.688551903337443e-7, estConfidenceLevel = 0.95}),(5,1,Estimate {estPoint = 6.691154065986784e-7, estLowerBound = 6.689133845204945e-7, estUpperBound = 6.693197421397023e-7, estConfidenceLevel = 0.95}),(6,1,Estimate {estPoint = 6.68325216029288e-7, estLowerBound = 6.680928188691085e-7, estUpperBound = 6.685740418237066e-7, estConfidenceLevel = 0.95}),(7,1,Estimate {estPoint = 6.687464674344803e-7, estLowerBound = 6.685057078538176e-7, estUpperBound = 6.689873563592131e-7, estConfidenceLevel = 0.95}),(8,1,Estimate {estPoint = 6.696848476291483e-7, estLowerBound = 6.694467300909132e-7, estUpperBound = 6.699295025413032e-7, estConfidenceLevel = 0.95}),(9,1,Estimate {estPoint = 6.688124026465396e-7, estLowerBound = 6.685917928773739e-7, estUpperBound = 6.690549399471816e-7, estConfidenceLevel = 0.95}),(10,1,Estimate {estPoint = 2.4516690143842055e-6, estLowerBound = 2.4507767244791015e-6, estUpperBound = 2.4527227762601224e-6, estConfidenceLevel = 0.95}),(11,1,Estimate {estPoint = 2.4590283561231637e-6, estLowerBound = 2.458487451045234e-6, estUpperBound = 2.459554110767415e-6, estConfidenceLevel = 0.95}),(12,1,Estimate {estPoint = 2.071320840307728e-6, estLowerBound = 2.067087487325186e-6, estUpperBound = 2.0757400233642084e-6, estConfidenceLevel = 0.95}),(13,1,Estimate {estPoint = 2.070839621855829e-6, estLowerBound = 2.067234667867506e-6, estUpperBound = 2.0740806465161934e-6, estConfidenceLevel = 0.95}),(14,1,Estimate {estPoint = 2.4560769564132194e-6, estLowerBound = 2.455101577163432e-6, estUpperBound = 2.457196398423878e-6, estConfidenceLevel = 0.95}),(15,1,Estimate {estPoint = 3.2208250147971343e-6, estLowerBound = 3.217887325964865e-6, estUpperBound = 3.224503088054143e-6, estConfidenceLevel = 0.95}),(16,1,Estimate {estPoint = 6.859665356052545e-6, estLowerBound = 6.853335637879508e-6, estUpperBound = 6.867476152505506e-6, estConfidenceLevel = 0.95}),(17,1,Estimate {estPoint = 3.222313379976406e-6, estLowerBound = 3.2187624780830156e-6, estUpperBound = 3.2257733211948318e-6, estConfidenceLevel = 0.95}),(18,1,Estimate {estPoint = 4.429779831483987e-6, estLowerBound = 4.4277843396780594e-6, estUpperBound = 4.432298236618889e-6, estConfidenceLevel = 0.95}),(19,1,Estimate {estPoint = 6.4832202487720915e-6, estLowerBound = 6.4799692534498964e-6, estUpperBound = 6.486980238806437e-6, estConfidenceLevel = 0.95}),(20,1,Estimate {estPoint = 1.1505950284919645e-5, estLowerBound = 1.1500743634338238e-5, estUpperBound = 1.1512502748763946e-5, estConfidenceLevel = 0.95}),(21,1,Estimate {estPoint = 1.740239657733145e-5, estLowerBound = 1.739366103664915e-5, estUpperBound = 1.7411415543717616e-5, estConfidenceLevel = 0.95}),(22,1,Estimate {estPoint = 1.0385375506065036e-5, estLowerBound = 1.0377901951411407e-5, estUpperBound = 1.0392354598018254e-5, estConfidenceLevel = 0.95}),(23,1,Estimate {estPoint = 1.263509252327211e-5, estLowerBound = 1.2626027546331158e-5, estUpperBound = 1.2644967200587266e-5, estConfidenceLevel = 0.95}),(24,1,Estimate {estPoint = 9.840655258052606e-6, estLowerBound = 9.811818979029135e-6, estUpperBound = 9.94727605885071e-6, estConfidenceLevel = 0.95}),(25,1,Estimate {estPoint = 6.19995491326754e-5, estLowerBound = 6.198892762554592e-5, estUpperBound = 6.201109699450588e-5, estConfidenceLevel = 0.95}),(26,1,Estimate {estPoint = 1.712216938857124e-5, estLowerBound = 1.7072169917295066e-5, estUpperBound = 1.7283789628792664e-5, estConfidenceLevel = 0.95}),(27,1,Estimate {estPoint = 4.1962244743996434e-5, estLowerBound = 4.1956032967523625e-5, estUpperBound = 4.197020038907427e-5, estConfidenceLevel = 0.95}),(28,1,Estimate {estPoint = 7.707443580577238e-5, estLowerBound = 7.677157851739942e-5, estUpperBound = 7.810924417145337e-5, estConfidenceLevel = 0.95}),(29,1,Estimate {estPoint = 4.20538835962544e-5, estLowerBound = 4.161103130370585e-5, estUpperBound = 4.250662981112048e-5, estConfidenceLevel = 0.95}),(30,1,Estimate {estPoint = 6.52824836940292e-5, estLowerBound = 6.525600762415725e-5, estUpperBound = 6.531525788740985e-5, estConfidenceLevel = 0.95}),(31,1,Estimate {estPoint = 3.1227900977566335e-5, estLowerBound = 3.120912846551492e-5, estUpperBound = 3.1262893264405674e-5, estConfidenceLevel = 0.95}),(32,1,Estimate {estPoint = 2.2958134089614649e-4, estLowerBound = 2.2956345818263545e-4, estUpperBound = 2.296032394558274e-4, estConfidenceLevel = 0.95}),(33,3,Estimate {estPoint = 5.062712280808083e-4, estLowerBound = 5.044948475706175e-4, estUpperBound = 5.083304107988112e-4, estConfidenceLevel = 0.95}),(34,3,Estimate {estPoint = 1.605401503619543e-4, estLowerBound = 1.6051630643976593e-4, estUpperBound = 1.605643602946483e-4, estConfidenceLevel = 0.95}),(35,5,Estimate {estPoint = 3.1621251296906736e-4, estLowerBound = 3.161867640020541e-4, estUpperBound = 3.1624273162764784e-4, estConfidenceLevel = 0.95}),(36,3,Estimate {estPoint = 8.703482202397579e-5, estLowerBound = 8.702207504797319e-5, estUpperBound = 8.704644753490254e-5, estConfidenceLevel = 0.95}),(37,9,Estimate {estPoint = 5.182140101856885e-4, estLowerBound = 5.155190830652726e-4, estUpperBound = 5.217292884990793e-4, estConfidenceLevel = 0.95}),(38,7,Estimate {estPoint = 5.543648541931377e-4, estLowerBound = 5.52077865947657e-4, estUpperBound = 5.565640170689199e-4, estConfidenceLevel = 0.95}),(39,5,Estimate {estPoint = 2.1353217977205564e-4, estLowerBound = 2.1248587709159735e-4, estUpperBound = 2.1464192268750505e-4, estConfidenceLevel = 0.95}),(40,7,Estimate {estPoint = 4.7992992046613076e-4, estLowerBound = 4.782952268609564e-4, estUpperBound = 4.8176296321162694e-4, estConfidenceLevel = 0.95}),(41,13,Estimate {estPoint = 9.888298582145948e-4, estLowerBound = 9.848914413276555e-4, estUpperBound = 9.956240079381272e-4, estConfidenceLevel = 0.95}),(42,19,Estimate {estPoint = 4.933554303828974e-4, estLowerBound = 4.918905150305174e-4, estUpperBound = 4.953353966275465e-4, estConfidenceLevel = 0.95}),(43,19,Estimate {estPoint = 7.508386055872756e-4, estLowerBound = 7.472013768307216e-4, estUpperBound = 7.544000788787172e-4, estConfidenceLevel = 0.95}),(44,25,Estimate {estPoint = 3.5123541297854815e-4, estLowerBound = 3.504358392821456e-4, estUpperBound = 3.524683041445389e-4, estConfidenceLevel = 0.95}),(45,17,Estimate {estPoint = 1.0578048408695446e-3, estLowerBound = 1.0540237416677e-3, estUpperBound = 1.0606157843453606e-3, estConfidenceLevel = 0.95}),(46,17,Estimate {estPoint = 1.6707746868592577e-3, estLowerBound = 1.6652208484533623e-3, estUpperBound = 1.675335380532068e-3, estConfidenceLevel = 0.95}),(47,25,Estimate {estPoint = 1.2648390416047262e-3, estLowerBound = 1.2591786552045487e-3, estUpperBound = 1.2700518475672658e-3, estConfidenceLevel = 0.95}),(48,25,Estimate {estPoint = 2.4195801313050182e-3, estLowerBound = 2.4125912791546395e-3, estUpperBound = 2.428265409574023e-3, estConfidenceLevel = 0.95}),(49,29,Estimate {estPoint = 2.745459258487619e-3, estLowerBound = 2.733522828725753e-3, estUpperBound = 2.759943148308382e-3, estConfidenceLevel = 0.95}),(50,47,Estimate {estPoint = 5.077120873802574e-4, estLowerBound = 5.043085652804884e-4, estUpperBound = 5.117074968907072e-4, estConfidenceLevel = 0.95}),(51,35,Estimate {estPoint = 2.0668108459718773e-3, estLowerBound = 2.0515965986930593e-3, estUpperBound = 2.080686292726242e-3, estConfidenceLevel = 0.95}),(52,49,Estimate {estPoint = 3.406726352773277e-3, estLowerBound = 3.3988496497787167e-3, estUpperBound = 3.4163517088936007e-3, estConfidenceLevel = 0.95}),(53,55,Estimate {estPoint = 3.1147315576168576e-3, estLowerBound = 3.103487848196032e-3, estUpperBound = 3.1335536859429707e-3, estConfidenceLevel = 0.95}),(54,43,Estimate {estPoint = 3.3887440708420223e-3, estLowerBound = 3.3759643213381618e-3, estUpperBound = 3.4102417980489013e-3, estConfidenceLevel = 0.95}),(55,45,Estimate {estPoint = 4.177346730279699e-3, estLowerBound = 4.167495272091152e-3, estUpperBound = 4.186404320807954e-3, estConfidenceLevel = 0.95}),(56,41,Estimate {estPoint = 4.133804219127328e-3, estLowerBound = 4.124160819335379e-3, estUpperBound = 4.1438783539466915e-3, estConfidenceLevel = 0.95}),(57,51,Estimate {estPoint = 4.340396882565733e-3, estLowerBound = 4.326043149922912e-3, estUpperBound = 4.362142122245852e-3, estConfidenceLevel = 0.95}),(58,79,Estimate {estPoint = 4.193872369251759e-3, estLowerBound = 4.1814433482315035e-3, estUpperBound = 4.21007788481938e-3, estConfidenceLevel = 0.95}),(59,93,Estimate {estPoint = 6.782820909250412e-3, estLowerBound = 6.7561792923451726e-3, estUpperBound = 6.8129496222344085e-3, estConfidenceLevel = 0.95}),(60,137,Estimate {estPoint = 1.1617619795890098e-2, estLowerBound = 1.1575684783758962e-2, estUpperBound = 1.165510047074422e-2, estConfidenceLevel = 0.95}),(61,77,Estimate {estPoint = 7.533678994068502e-3, estLowerBound = 7.517298988814767e-3, estUpperBound = 7.547922167003825e-3, estConfidenceLevel = 0.95}),(62,127,Estimate {estPoint = 1.2063656437066575e-2, estLowerBound = 1.2040213188382672e-2, estUpperBound = 1.2087940851240236e-2, estConfidenceLevel = 0.95}),(63,149,Estimate {estPoint = 1.0393127957906385e-2, estLowerBound = 1.0353183058579093e-2, estUpperBound = 1.0459297978124516e-2, estConfidenceLevel = 0.95}),(64,183,Estimate {estPoint = 1.4789380890704324e-2, estLowerBound = 1.4746144926751983e-2, estUpperBound = 1.4824711446444613e-2, estConfidenceLevel = 0.95}),(65,269,Estimate {estPoint = 1.552882863286248e-2, estLowerBound = 1.5483315005822808e-2, estUpperBound = 1.5570566667621051e-2, estConfidenceLevel = 0.95}),(66,145,Estimate {estPoint = 1.4666148391476471e-2, estLowerBound = 1.4637179042417618e-2, estUpperBound = 1.4696640447953522e-2, estConfidenceLevel = 0.95}),(67,133,Estimate {estPoint = 1.4186361441265628e-2, estLowerBound = 1.4148449455457237e-2, estUpperBound = 1.4218206719850668e-2, estConfidenceLevel = 0.95}),(68,151,Estimate {estPoint = 1.3971546560317334e-2, estLowerBound = 1.3923593361107127e-2, estUpperBound = 1.4017567478314619e-2, estConfidenceLevel = 0.95}),(69,203,Estimate {estPoint = 2.1988169654487345e-2, estLowerBound = 2.1922729477582177e-2, estUpperBound = 2.205907254665841e-2, estConfidenceLevel = 0.95}),(70,207,Estimate {estPoint = 2.9617950592030037e-2, estLowerBound = 2.9560357049813992e-2, estUpperBound = 2.972666019059935e-2, estConfidenceLevel = 0.95}),(71,231,Estimate {estPoint = 1.849660982731293e-2, estLowerBound = 1.8432742435574398e-2, estUpperBound = 1.8602853123208363e-2, estConfidenceLevel = 0.95}),(72,345,Estimate {estPoint = 2.467664214187238e-2, estLowerBound = 2.4594456171905775e-2, estUpperBound = 2.4801940243059463e-2, estConfidenceLevel = 0.95}),(73,305,Estimate {estPoint = 3.2637383334204954e-2, estLowerBound = 3.256695264784176e-2, estUpperBound = 3.272858599459522e-2, estConfidenceLevel = 0.95}),(74,241,Estimate {estPoint = 2.67563654175144e-2, estLowerBound = 2.6660379291977267e-2, estUpperBound = 2.710416876467753e-2, estConfidenceLevel = 0.95}),(75,277,Estimate {estPoint = 3.408190811605574e-2, estLowerBound = 3.403897095212308e-2, estUpperBound = 3.416658906262928e-2, estConfidenceLevel = 0.95}),(76,345,Estimate {estPoint = 2.765159890548578e-2, estLowerBound = 2.7532603880151676e-2, estUpperBound = 2.775914256508485e-2, estConfidenceLevel = 0.95}),(77,485,Estimate {estPoint = 4.737277470247736e-2, estLowerBound = 4.725559045010093e-2, estUpperBound = 4.746581517129507e-2, estConfidenceLevel = 0.95}),(78,421,Estimate {estPoint = 4.447613251602191e-2, estLowerBound = 4.431018528652689e-2, estUpperBound = 4.46213229735744e-2, estConfidenceLevel = 0.95}),(79,493,Estimate {estPoint = 5.178746337763676e-2, estLowerBound = 5.1654836587994524e-2, estUpperBound = 5.1884012325977724e-2, estConfidenceLevel = 0.95}),(80,487,Estimate {estPoint = 4.8079120283811665e-2, estLowerBound = 4.795210467581056e-2, estUpperBound = 4.823772773898419e-2, estConfidenceLevel = 0.95}),(81,577,Estimate {estPoint = 5.859393568670635e-2, estLowerBound = 5.8470360355678076e-2, estUpperBound = 5.876713573365527e-2, estConfidenceLevel = 0.95}),(82,723,Estimate {estPoint = 6.711519282635509e-2, estLowerBound = 6.689479744303688e-2, estUpperBound = 6.737302498965779e-2, estConfidenceLevel = 0.95}),(83,603,Estimate {estPoint = 5.667432125250895e-2, estLowerBound = 5.6573673470409674e-2, estUpperBound = 5.682798864395409e-2, estConfidenceLevel = 0.95}),(84,727,Estimate {estPoint = 6.713981528555175e-2, estLowerBound = 6.694180006094703e-2, estUpperBound = 6.738780839852576e-2, estConfidenceLevel = 0.95}),(85,907,Estimate {estPoint = 9.175256761670576e-2, estLowerBound = 9.157565650814621e-2, estUpperBound = 9.207081652609315e-2, estConfidenceLevel = 0.95}),(86,1159,Estimate {estPoint = 8.400726349810456e-2, estLowerBound = 8.3650761500867e-2, estUpperBound = 8.433768752690085e-2, estConfidenceLevel = 0.95}),(87,893,Estimate {estPoint = 0.10350581785246164, estLowerBound = 0.10322319498543488, estUpperBound = 0.10374080214476257, estConfidenceLevel = 0.95}),(88,1119,Estimate {estPoint = 9.490526141014022e-2, estLowerBound = 9.45140115022998e-2, estUpperBound = 9.51821034578898e-2, estConfidenceLevel = 0.95}),(89,1265,Estimate {estPoint = 0.11742485368448707, estLowerBound = 0.11709592981681974, estUpperBound = 0.11793545984670198, estConfidenceLevel = 0.95}),(90,1295,Estimate {estPoint = 9.108632150735033e-2, estLowerBound = 9.095233644647584e-2, estUpperBound = 9.118222446882371e-2, estConfidenceLevel = 0.95}),(91,1629,Estimate {estPoint = 0.13117301644978926, estLowerBound = 0.13053108999220994, estUpperBound = 0.1316728848694164, estConfidenceLevel = 0.95}),(92,2765,Estimate {estPoint = 0.19530393792518197, estLowerBound = 0.1947722538077417, estUpperBound = 0.19563330506003596, estConfidenceLevel = 0.95}),(93,2543,Estimate {estPoint = 0.20614416295127108, estLowerBound = 0.20587797899285806, estUpperBound = 0.2066332279152149, estConfidenceLevel = 0.95})] -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, containers, stdenv }: 2 | mkDerivation { 3 | pname = "pretty-compact"; 4 | version = "2.0"; 5 | src = ./.; 6 | libraryHaskellDepends = [ base containers ]; 7 | description = "Pretty-printing library"; 8 | license = "GPL"; 9 | } 10 | -------------------------------------------------------------------------------- /paper/BenchmarkLibs.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveFunctor #-} 2 | {-# LANGUAGE FlexibleInstances, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, FlexibleContexts, RankNTypes, TypeFamilies, LambdaCase #-} 3 | 4 | module BenchmarkLibs where 5 | 6 | import Control.Monad (ap, forM, forM_) 7 | import Control.Monad.IO.Class 8 | import Criterion (nf) 9 | import Criterion.Internal (runAndAnalyseOne) 10 | import Criterion.Types (DataRecord(..), Report(..), SampleAnalysis(..), Benchmarkable) 11 | import Statistics.Resampling.Bootstrap (Estimate(..)) 12 | import System.Random 13 | import qualified Criterion.Main.Options as C 14 | import qualified Criterion.Monad as C 15 | import Data.Maybe 16 | -- import Data.List 17 | import Data.Aeson.Parser 18 | import Data.Aeson.Types 19 | import Data.Attoparsec.ByteString 20 | import qualified Data.ByteString as BS 21 | import qualified Text.PrettyPrint.Compact as PC 22 | import qualified Data.HashMap.Lazy as H 23 | import qualified Data.Text as T 24 | import Data.Foldable (toList) 25 | import qualified Text.PrettyPrint.Leijen as WL 26 | import qualified Text.PrettyPrint.HughesPJ as HPJ 27 | import Data.Monoid 28 | 29 | prettiestJSON :: Value -> PC.Doc () 30 | prettiestJSON (Bool True) = PC.text "true" 31 | prettiestJSON (Bool False) = PC.text "false" 32 | prettiestJSON (Object o) = PC.encloseSep (PC.text "{") (PC.text "}") (PC.text ",") (map prettyKV $ H.toList o) 33 | where prettyKV (k,v) = PC.text (show k) PC.<> PC.text ":" PC.<+> prettiestJSON v 34 | prettiestJSON (String s) = PC.string (show s) 35 | prettiestJSON (Array a) = PC.encloseSep (PC.text "[") (PC.text "]") (PC.text ",") (map prettiestJSON $ toList a) 36 | prettiestJSON Null = PC.mempty 37 | prettiestJSON (Number n) = PC.text (show n) 38 | 39 | wlJSON :: Value -> WL.Doc 40 | wlJSON (Bool True) = WL.text "true" 41 | wlJSON (Bool False) = WL.text "false" 42 | wlJSON (Object o) = WL.encloseSep (WL.text "{") (WL.text "}") (WL.text ",") (map prettyKV $ H.toList o) 43 | where prettyKV (k,v) = WL.text (show k) WL.<> WL.text ":" WL.<+> wlJSON v 44 | wlJSON (String s) = WL.string (show s) 45 | wlJSON (Array a) = WL.encloseSep (WL.text "[") (WL.text "]") (WL.text ",") (map wlJSON $ toList a) 46 | wlJSON Null = WL.empty 47 | wlJSON (Number n) = WL.text (show n) 48 | 49 | hpjEncloseSep :: HPJ.Doc -> HPJ.Doc -> HPJ.Doc -> [HPJ.Doc] -> HPJ.Doc 50 | hpjEncloseSep open close sep list = open HPJ.<> HPJ.sep (HPJ.punctuate sep list) HPJ.<> close 51 | 52 | hpjJSON :: Value -> HPJ.Doc 53 | hpjJSON (Bool True) = HPJ.text "true" 54 | hpjJSON (Bool False) = HPJ.text "false" 55 | hpjJSON (Object o) = hpjEncloseSep (HPJ.text "{") (HPJ.text "}") (HPJ.text ",") (map prettyKV $ H.toList o) 56 | where prettyKV (k,v) = HPJ.text (show k) HPJ.<> HPJ.text ":" HPJ.<+> hpjJSON v 57 | hpjJSON (String s) = HPJ.text (show s) 58 | hpjJSON (Array a) = hpjEncloseSep (HPJ.text "[") (HPJ.text "]") (HPJ.text ",") (map hpjJSON $ toList a) 59 | hpjJSON Null = HPJ.empty 60 | hpjJSON (Number n) = HPJ.text (show n) 61 | 62 | pcTest, wlTest, hpjTest :: Value -> String 63 | pcTest = PC.render . prettiestJSON 64 | wlTest inpJson = WL.displayS (WL.renderPretty 1 80 (wlJSON inpJson)) "" 65 | hpjTest = HPJ.render . hpjJSON 66 | 67 | readJSONValue fname = do 68 | inptxt <- BS.readFile fname 69 | let Right inpJson = parseOnly json' inptxt 70 | return inpJson 71 | 72 | testLibs :: IO () 73 | testLibs = do 74 | inpJson <- readJSONValue "small.json" 75 | putStrLn $ PC.render $ prettiestJSON $ inpJson 76 | putStrLn $ WL.displayS (WL.renderPretty 1 80 (wlJSON inpJson)) "" 77 | putStrLn $ HPJ.render $ hpjJSON $ inpJson 78 | 79 | {-> testLibs 80 | 81 | [{"email": "parsonsgutierrez@jumpstack.com", 82 | "phone": "+1 (939) 549-3574", 83 | "_id": "58f859497aedc03cdeca7c49", 84 | "latitude": 87.026801, 85 | "favoriteFruit": "apple", 86 | "age": 34.0, 87 | "balance": "$3,560.54", 88 | "address": "303 Brigham Street, Boyd, Idaho, 301", 89 | "greeting": "Hello, Parsons Gutierrez! You have 10 unread messages.", 90 | "eyeColor": "brown", 91 | "picture": "http://placehold.it/32x32", 92 | "gender": "male", 93 | "name": "Parsons Gutierrez", 94 | "guid": "6b93a496-e422-41a2-abb9-86627a7804f6", 95 | "company": "JUMPSTACK", 96 | "registered": "2014-05-21T04:36:34 -02:00", 97 | "longitude": -114.952111, 98 | "index": 0.0, 99 | "isActive": false, 100 | "tags": ["reprehenderit", 101 | "excepteur", 102 | "consequat", 103 | "est", 104 | "anim", 105 | "dolor", 106 | "commodo"], 107 | "friends": [{"name": "Sabrina Banks", "id": 0.0}, 108 | {"name": "Tanya Walters", "id": 1.0}, 109 | {"name": "Heath Gould", "id": 2.0}]}] 110 | -} 111 | 112 | -- Local Variables: 113 | -- dante-project-root: "~/repo/prettiest/paper" 114 | -- dante-target: "bench" 115 | -- dante-repl-command-line: ("nix-shell" "../.styx/shell.nix" "--run" "cabal --sandbox-config-file=../cabal.sandbox.config repl --only bench") 116 | -- End: 117 | -------------------------------------------------------------------------------- /paper/BenchmarkXML.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RecordWildCards #-} 2 | {-# LANGUAGE DeriveFunctor #-} 3 | {-# LANGUAGE FlexibleInstances, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, FlexibleContexts, RankNTypes, TypeFamilies, LambdaCase #-} 4 | 5 | module BenchmarkXML where 6 | 7 | import qualified Text.PrettyPrint.Compact as PC 8 | import qualified Text.PrettyPrint.Leijen as WL 9 | import qualified Text.PrettyPrint.HughesPJ as HPJ 10 | import Data.Monoid 11 | import Text.XML.Light.Input 12 | import Text.XML.Light.Types 13 | 14 | type DOC = PC.Doc () 15 | 16 | pcXML :: Element -> DOC 17 | pcXML Element{..} = 18 | PC.encloseSep 19 | (PC.encloseSep (PC.text "<" <> PC.text (qName elName)) (PC.text ">") 20 | (PC.text " ") (map pcAttrib (elAttribs))) 21 | mempty 22 | (PC.text " PC.text (qName elName) <> PC.text ">") 23 | (map pcContent elContent) 24 | 25 | pcAttrib :: Attr -> DOC 26 | pcAttrib Attr{..} = PC.text (qName attrKey) <> PC.text "=" <> PC.text (show attrVal) 27 | 28 | pcContent :: Content -> DOC 29 | pcContent (Elem e) = pcXML e 30 | pcContent (Text CData{..}) = PC.text cdData 31 | pcContent (CRef r) = PC.text r 32 | 33 | wlXML :: Element -> WL.Doc 34 | wlXML Element{..} = 35 | WL.encloseSep 36 | (WL.encloseSep (WL.text "<" WL.<> WL.text (qName elName)) (WL.text ">") 37 | (WL.text " ") (map wlAttrib (elAttribs))) 38 | WL.empty 39 | (WL.text " WL.text (qName elName) WL.<> WL.text ">") 40 | (map wlContent elContent) 41 | 42 | wlAttrib :: Attr -> WL.Doc 43 | wlAttrib Attr{..} = WL.text (qName attrKey) WL.<> WL.text "=" WL.<> WL.text (show attrVal) 44 | 45 | wlContent :: Content -> WL.Doc 46 | wlContent (Elem e) = wlXML e 47 | wlContent (Text CData{..}) = WL.text cdData 48 | wlContent (CRef r) = WL.text r 49 | 50 | hpjEncloseSep :: HPJ.Doc -> HPJ.Doc -> HPJ.Doc -> [HPJ.Doc] -> HPJ.Doc 51 | hpjEncloseSep open close sep list = open HPJ.<> HPJ.sep (HPJ.punctuate sep list) HPJ.<> close 52 | 53 | hpjXML :: Element -> HPJ.Doc 54 | hpjXML Element{..} = 55 | hpjEncloseSep 56 | (hpjEncloseSep (HPJ.text "<" HPJ.<> HPJ.text (qName elName)) (HPJ.text ">") 57 | (HPJ.text " ") (map hpjAttrib (elAttribs))) 58 | HPJ.empty 59 | (HPJ.text " HPJ.text (qName elName) HPJ.<> HPJ.text ">") 60 | (map hpjContent elContent) 61 | 62 | hpjAttrib :: Attr -> HPJ.Doc 63 | hpjAttrib Attr{..} = HPJ.text (qName attrKey) HPJ.<> HPJ.text "=" HPJ.<> HPJ.text (show attrVal) 64 | 65 | hpjContent :: Content -> HPJ.Doc 66 | hpjContent (Elem e) = hpjXML e 67 | hpjContent (Text CData{..}) = HPJ.text cdData 68 | hpjContent (CRef r) = HPJ.text r 69 | 70 | pcTest, wlTest, hpjTest :: Element -> String 71 | pcTest = PC.render . pcXML 72 | wlTest inpXml = WL.displayS (WL.renderPretty 1 80 (wlXML inpXml)) "" 73 | hpjTest = HPJ.render . hpjXML 74 | 75 | readXMLValue :: String -> IO Element 76 | readXMLValue fname = do 77 | inptxt <- readFile fname 78 | case parseXMLDoc inptxt of 79 | Just x -> return x 80 | Nothing -> error "XML Parse error" 81 | 82 | testLibs :: IO () 83 | testLibs = do 84 | inpXml <- readXMLValue "benchdata/cds.xml" 85 | putStrLn $ pcTest inpXml 86 | putStrLn $ WL.displayS (WL.renderPretty 1 80 (wlXML inpXml)) "" 87 | putStrLn $ HPJ.render $ hpjXML $ inpXml 88 | 89 | {-> testLibs 90 | 91 | 92 | 93 | 94 | Empire Burlesque 95 | 96 | Bob Dylan 97 | 98 | USA 99 | 100 | Columbia 101 | 102 | 10.90 103 | 104 | 1985 105 | 106 | 107 | 108 | 109 | Hide your heart 110 | 111 | Bonnie Tyler 112 | 113 | UK 114 | 115 | CBS Records 116 | 117 | 9.90 118 | 119 | 1988 120 | 121 | 122 | 123 | 124 | Greatest Hits 125 | 126 | Dolly Parton 127 | 128 | USA 129 | 130 | RCA 131 | 132 | 9.90 133 | 134 | 1982 135 | 136 | 137 | 138 | 139 | Still got the blues 140 | 141 | Gary Moore 142 | 143 | UK 144 | 145 | Virgin records 146 | 147 | 10.20 148 | 149 | 1990 150 | 151 | 152 | 153 | 154 | Eros 155 | 156 | Eros Ramazzotti 157 | 158 | EU 159 | 160 | BMG 161 | 162 | 9.90 163 | 164 | 1997 165 | 166 | 167 | 168 | 169 | One night only 170 | 171 | Bee Gees 172 | 173 | UK 174 | 175 | Polydor 176 | 177 | 10.90 178 | 179 | 1998 180 | 181 | 182 | 183 | 184 | Sylvias Mother 185 | 186 | Dr.Hook 187 | 188 | UK 189 | 190 | CBS 191 | 192 | 8.10 193 | 194 | 1973 195 | 196 | 197 | 198 | 199 | Maggie May 200 | 201 | Rod Stewart 202 | 203 | UK 204 | 205 | Pickwick 206 | 207 | 8.50 208 | 209 | 1990 210 | 211 | 212 | 213 | 214 | Romanza 215 | 216 | Andrea Bocelli 217 | 218 | EU 219 | 220 | Polydor 221 | 222 | 10.80 223 | 224 | 1996 225 | 226 | 227 | 228 | 229 | When a man loves a woman 230 | 231 | Percy Sledge 232 | 233 | USA 234 | 235 | Atlantic 236 | 237 | 8.70 238 | 239 | 1987 240 | 241 | 242 | 243 | 244 | Black angel 245 | 246 | Savage Rose 247 | 248 | EU 249 | 250 | Mega 251 | 252 | 10.90 253 | 254 | 1995 255 | 256 | 257 | 258 | 259 | 1999 Grammy Nominees 260 | 261 | Many 262 | 263 | USA 264 | 265 | Grammy 266 | 267 | 10.20 268 | 269 | 1999 270 | 271 | 272 | 273 | 274 | For the good times 275 | 276 | Kenny Rogers 277 | 278 | UK 279 | 280 | Mucik Master 281 | 282 | 8.70 283 | 284 | 1995 285 | 286 | 287 | 288 | 289 | Big Willie style 290 | 291 | Will Smith 292 | 293 | USA 294 | 295 | Columbia 296 | 297 | 9.90 298 | 299 | 1997 300 | 301 | 302 | 303 | 304 | Tupelo Honey 305 | 306 | Van Morrison 307 | 308 | UK 309 | 310 | Polydor 311 | 312 | 8.20 313 | 314 | 1971 315 | 316 | 317 | 318 | 319 | Soulsville 320 | 321 | Jorn Hoel 322 | 323 | Norway 324 | 325 | WEA 326 | 327 | 7.90 328 | 329 | 1996 330 | 331 | 332 | 333 | 334 | The very best of 335 | 336 | Cat Stevens 337 | 338 | UK 339 | 340 | Island 341 | 342 | 8.90 343 | 344 | 1990 345 | 346 | 347 | 348 | 349 | Stop 350 | 351 | Sam Brown 352 | 353 | UK 354 | 355 | A and M 356 | 357 | 8.90 358 | 359 | 1988 360 | 361 | 362 | 363 | 364 | Bridge of Spies 365 | 366 | T'Pau 367 | 368 | UK 369 | 370 | Siren 371 | 372 | 7.90 373 | 374 | 1987 375 | 376 | 377 | 378 | 379 | Private Dancer 380 | 381 | Tina Turner 382 | 383 | UK 384 | 385 | Capitol 386 | 387 | 8.90 388 | 389 | 1983 390 | 391 | 392 | 393 | 394 | Midt om natten 395 | 396 | Kim Larsen 397 | 398 | EU 399 | 400 | Medley 401 | 402 | 7.80 403 | 404 | 1983 405 | 406 | 407 | 408 | 409 | Pavarotti Gala Concert 410 | 411 | Luciano Pavarotti 412 | 413 | UK 414 | 415 | DECCA 416 | 417 | 9.90 418 | 419 | 1991 420 | 421 | 422 | 423 | 424 | The dock of the bay 425 | 426 | Otis Redding 427 | 428 | USA 429 | 430 | Stax Records 431 | 432 | 7.90 433 | 434 | 1968 435 | 436 | 437 | 438 | 439 | Picture book 440 | 441 | Simply Red 442 | 443 | EU 444 | 445 | Elektra 446 | 447 | 7.20 448 | 449 | 1985 450 | 451 | 452 | 453 | 454 | Red 455 | 456 | The Communards 457 | 458 | UK 459 | 460 | London 461 | 462 | 7.80 463 | 464 | 1987 465 | 466 | 467 | 468 | 469 | Unchain my heart 470 | 471 | Joe Cocker 472 | 473 | USA 474 | 475 | EMI 476 | 477 | 8.20 478 | 479 | 1987 480 | 481 | 482 | 483 | 484 | 485 | -} 486 | 487 | -- Local Variables: 488 | -- dante-project-root: "~/repo/prettiest/paper" 489 | -- dante-target: "bench" 490 | -- dante-repl-command-line: ("nix-shell" "../.styx/shell.nix" "--run" "cabal --sandbox-config-file=../cabal.sandbox.config repl --only bench") 491 | -- End: 492 | -------------------------------------------------------------------------------- /paper/Benchmarks.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveFunctor #-} 2 | {-# LANGUAGE FlexibleInstances, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, FlexibleContexts, RankNTypes, TypeFamilies, LambdaCase #-} 3 | 4 | import Control.Monad (ap, forM, forM_) 5 | import Control.Monad.IO.Class 6 | import Criterion (nf) 7 | import Criterion.Internal (runAndAnalyseOne) 8 | import Criterion.Types (DataRecord(..), Report(..), SampleAnalysis(..), Benchmarkable) 9 | import PM (render, L, M(..), DM(..), NoDom(..), pretty, SExpr(..), dataFileName, testExpr, OC(..), randomDyck) 10 | import Statistics.Resampling.Bootstrap (Estimate(..)) 11 | import System.Random 12 | import qualified Criterion.Main.Options as C 13 | import qualified Criterion.Monad as C 14 | import Data.Maybe 15 | import Data.List 16 | import System.Environment (getArgs) 17 | import BenchmarkLibs 18 | import qualified BenchmarkXML as XML 19 | 20 | newtype OCP a = OCP {runOCP :: [OC] -> ([OC],a)} deriving (Functor) 21 | instance Applicative OCP where 22 | pure = return 23 | (<*>) = ap 24 | instance Monad OCP where 25 | return x = OCP (\xs -> (xs,x)) 26 | OCP k >>= f = OCP (\i0 -> let (i1,x) = k i0 in runOCP (f x) i1) 27 | 28 | look :: OCP (Maybe OC) 29 | look = OCP $ \case 30 | [] -> ([],Nothing) 31 | (x:xs) -> (x:xs,Just x) 32 | 33 | skip :: OCP () 34 | skip = OCP $ \case 35 | [] -> ([],()) 36 | (_:xs) -> (xs,()) 37 | 38 | ocToSExpr :: OCP SExpr 39 | ocToSExpr = do 40 | skip -- open assumed 41 | x <- ocToSExprs 42 | skip -- close or eof 43 | return (SExpr x) 44 | 45 | ocToSExprs :: OCP [SExpr] 46 | ocToSExprs = do 47 | l <- look 48 | case l of 49 | Just A -> do 50 | skip 51 | rest <- ocToSExprs 52 | return (Atom "a":rest) 53 | Just Open -> do 54 | h <- ocToSExpr 55 | rest <- ocToSExprs 56 | return (h:rest) 57 | _ -> return [] 58 | 59 | 60 | randExpr :: Int -> IO SExpr 61 | randExpr maxlen = do 62 | oc <- randomDyck maxlen 63 | return $ SExpr $ snd $ runOCP ocToSExprs $ intersperse A oc 64 | 65 | testLayout :: SExpr -> Maybe Int 66 | testLayout input = case (pretty input :: DM) of 67 | [] -> Nothing 68 | mm -> Just (1 + (height (minimum mm))) 69 | 70 | testLayoutNoDom :: SExpr -> Maybe Int 71 | testLayoutNoDom input = case (pretty input :: NoDom) of 72 | NoDom [] -> Nothing 73 | NoDom mm -> Just (1 + (height (minimum mm))) 74 | 75 | benchmark testLayout size = nf testLayout size 76 | 77 | fitting :: SExpr -> Bool 78 | fitting = isJust . testLayout 79 | 80 | performanceAnalysisRandom :: IO () 81 | performanceAnalysisRandom = do 82 | putStrLn "performanceAnalysisRandom..." 83 | let n = 100 -- number of data points 84 | maxsz = 4000 -- max number of open parens 85 | f = exp (log maxsz / n) 86 | exprs <- filter fitting <$> forM [0..n] (\i -> randExpr (floor (f**i))) 87 | putStrLn "If the program gets stuck now it is due to a bug in criterion. (It does not work on MacOS)" 88 | an <- C.withConfig C.defaultConfig $ do 89 | forM (zip exprs [1..]) $ \(e,i) -> do 90 | liftIO $ putStrLn $ "running bench " ++ show i 91 | (Analysed (Report { reportAnalysis = SampleAnalysis {anMean = dt}})) <- 92 | runAndAnalyseOne i ("bench " ++ show i) (benchmark testLayout e) 93 | return (i,fromJust (testLayout e), dt) 94 | writeFile "benchmark-random.dat" $ show an 95 | 96 | performanceAnalysis testLayout = do 97 | putStrLn "performanceAnalysis..." 98 | putStrLn "If the program gets stuck now it is due to a bug in criterion. (It does not work on MacOS)" 99 | an <- C.withConfig C.defaultConfig $ do 100 | forM [1..16] $ \size -> do 101 | liftIO $ putStrLn $ "running for " ++ show size 102 | (Analysed (Report { reportAnalysis = SampleAnalysis {anMean = dt}})) <- 103 | runAndAnalyseOne size ("bench " ++ show size) ((benchmark testLayout . testExpr) size) 104 | return (size,fromJust (testLayout $ testExpr size), dt) 105 | writeFile dataFileName $ show an 106 | 107 | performanceAnalysisJSON, performanceAnalysisXML :: [FilePath] -> IO () 108 | performanceAnalysisJSON = performanceAnalysisRW readJSONValue [(pcTest,"PC"), (wlTest,"WL"), (hpjTest,"HPJ")] 109 | performanceAnalysisXML = performanceAnalysisRW XML.readXMLValue [(XML.pcTest,"PC"), (XML.wlTest,"WL"), (XML.hpjTest,"HPJ")] 110 | 111 | performanceAnalysisRW :: (String -> IO x) -> [(x -> String, String)] -> [FilePath] -> IO () 112 | performanceAnalysisRW reader tests fnames = do 113 | putStrLn "performanceAnalysisRW..." 114 | putStrLn "If the program gets stuck now it is due to a bug in criterion. (It does not work on MacOS)" 115 | an <- C.withConfig C.defaultConfig $ do 116 | forM fnames $ \fname -> do 117 | forM tests $ \(f,name) -> do 118 | liftIO $ putStrLn $ "running for " ++ name 119 | j <- liftIO $ reader fname 120 | (Analysed (Report { reportAnalysis = SampleAnalysis {anMean = dt}})) <- 121 | runAndAnalyseOne 7 ("bench-" ++ fname ++ name) ((\x -> nf f x) j) 122 | return (name, dt) 123 | writeFile "perf.dat" $ show an 124 | 125 | 126 | main :: IO () 127 | main = do 128 | as <- getArgs 129 | case as of 130 | ["all"] -> do 131 | performanceAnalysis testLayout 132 | performanceAnalysisRandom 133 | performanceAnalysisJSON ["1k.json","4k.json","10k.json"] 134 | performanceAnalysisXML ["benchdata/cds.xml"] 135 | ["full"] -> performanceAnalysis testLayout 136 | ["nodom"] -> performanceAnalysis testLayoutNoDom 137 | ["random"] -> performanceAnalysisRandom 138 | ["json", f] -> performanceAnalysisJSON [f] 139 | ["xml", f] -> performanceAnalysisXML [f] 140 | 141 | -- Local Variables: 142 | -- dante-project-root: "~/repo/prettiest/paper" 143 | -- dante-target: "bench" 144 | -- dante-repl-command-line: ("nix-shell" "../.styx/shell.nix" "--run" "cabal --sandbox-config-file=../cabal.sandbox.config repl --only bench") 145 | -- End: 146 | -------------------------------------------------------------------------------- /paper/ICFP-Mandatory-revisions.txt: -------------------------------------------------------------------------------- 1 | I have addressed the mandatory revisions as follows: 2 | 3 | 1. I have changed the citation style as requested. 4 | 5 | 2. I have extended the systematic testing to random s-expressions 6 | (sec. 7.1) and added a plot with the results. 7 | 8 | 3. I have tested the pretty printing algorithm without domination 9 | pruning. In this situation it behaves exponentially, preventing to 10 | test on any non-trivial input. This is reported at the end of 11 | sec. 7.1. 12 | 13 | 4. I have performed a new series of tests which exercises the pretty 14 | printer in typical conditions (sec 7.2). To do so I've pretty 15 | printed several typical XML and JSON inputs. In this series of 16 | tests I've measured the rendering time (not just the runtime of the 17 | layout algorithm). I have also run the test with Wadler-Leijen and 18 | Hughes-Peyton Jones libraries. 19 | 20 | -------------------------------------------------------------------------------- /paper/ICFP2017-draft-reviews.txt: -------------------------------------------------------------------------------- 1 | > =========================================================================== 2 | > ICFP 2017 Review #1A 3 | > --------------------------------------------------------------------------- 4 | > Paper #1: Functional Pearl: a pretty but not greedy printer 5 | > --------------------------------------------------------------------------- 6 | > 7 | > Overall merit: C. Weak paper, though I will not fight 8 | > strongly against it 9 | > Confidence: X. I am an expert in this area 10 | > 11 | > ===== Paper summary ===== 12 | > 13 | > This paper presents a functional pearl that describes the development 14 | > of a pretty printer. The paper selects three criterion for what it 15 | > means to be pretty: visibility, legibility and frugality. It argues 16 | > that the two main prior works on this problem (by Wadler and Hughes), 17 | > fall short on this measure; both fall short because they are concerned 18 | > with efficiency and so present greedy algorithms. 19 | > 20 | > This paper shows that by relaxing the requirement of efficiency, an 21 | > algorithm can be presented that satisfies all three conditions of 22 | > being pretty. While the algorithm that is presented is not as 23 | > efficicient, and indeed, the first version shown is horribly slow, the 24 | > initial version is refined into another that has reasonable 25 | > performance in practice. 26 | > 27 | > The final version of the algorithm is developed by applying some 28 | > heuristics and calculating, using a few laws that are introduced. The 29 | > fact that this new algorithm has reasonable performance is justified 30 | > by some benchmarking that shows how long it takes to print expressions 31 | > of increasing size; the plot indicates that the final algorithm 32 | > becomes linear. 33 | > 34 | > ===== Comments to authors ===== 35 | > 36 | > The paper is on scope for the conference, and I enjoyed reading it. 37 | > The overall idea for the paper is suitable for a pearl; this is a 38 | > demonstration of working from a specification to a solution via 39 | > calculation. In a pearl, I'm usually looking for writing and 40 | > exposition of the highest standards, and while I enjoyed the material 41 | > here, I think there wasn't quite enough polish applied to this 42 | > submission (yet). I have a number of minor comments--see below--that 43 | > should be addressed. 44 | > 45 | > One aspect I really enjoyed about this paper is the introduction of 46 | > the three principles that make a pretty printer. They are informally 47 | > introduced on p1, which is good, but I would also like to have a 48 | > formally explained version somewhere; this is brushed on in S4.3, 49 | > where it's clear the functions `mostFrugal` and `filter visible` play 50 | > a part, but I think that this should be elaborated and turned into a 51 | > more formal part of the story. 52 | 53 | Even though the draft paper does not emphasize it, I believe that 54 | `mostFrugal` is a crisp specification of the frugality principle, and 55 | that `filter visible` is a crisp specification of visibility. 56 | 57 | It would be helpful if the reviewer could point to a crisper 58 | specification if they have one in mind. 59 | 60 | 61 | 62 | > There are some gaps in the literature review that need to be plugged: 63 | > this is a very minor point that can easily be cleared up, but there 64 | > should probably be a reference to the chapter on pretty printing 65 | > in Bird's "Thinking Functionally with Haskell" book, and also to the 66 | > more recent work by Olaf Chitil "Pretty Printing with lazy deques" 2005. 67 | 68 | I agree that this gap should be adressed in the final version of the paper. 69 | 70 | > 71 | > Minor Comments 72 | > -------------- 73 | > 74 | > p1, footnote 3: looks like latex killed the url. 75 | 76 | Fixed 77 | 78 | > p1, bottom, "first informally": but then I don't see it done formally. 79 | 80 | Clarified 81 | 82 | > p2, "S-Expressions": these are never explained. 83 | 84 | Done 85 | 86 | > p2, "Visibility, Frugality and Legibility": the order is now 87 | > different to earlier: consistency. 88 | 89 | Fixed 90 | 91 | > p2, "concatenated horizontally, or aligned vertically": there should 92 | > be some discussin about how this interactds with frugality. 93 | 94 | Improved. 95 | 96 | > p2 "API is similar to Hughes'": It should be clearer where exactly 97 | > this specification is the same and where it differs. 98 | 99 | Improved. 100 | 101 | > p2, def of <+>: Not clear why <> (horizontal) is used int the 102 | > definition; it should be justified/discussed. 103 | 104 | Clarified 105 | 106 | > p3, Fig 1 (and elsewhere): Some of these figures have a new line between 107 | > the column width indicators and the text that follows, others do not. 108 | > This should stay consistent. 109 | 110 | Fixed 111 | 112 | > p3, "foldr1": never discussed or introduced. Why should we assume the 113 | > documents are not empty? 114 | 115 | Clarified 116 | 117 | > p3, def of `pretty`: Not clear why `text "(" <> ..." is the right 118 | > thing to use: I can imagine a layout like any of the following: 119 | > 120 | > ( a ( ( a b ... 121 | > b a 122 | > ... b 123 | > ... 124 | > Of course, this is all subjective, but given the approach proposed I 125 | > would like more discussion. 126 | 127 | Clarified earlier in sec 2. This is a working assumption for 128 | illustrative purposes. 129 | 130 | > p3 "Remember that we would like elements [...] either aligned 131 | > vertically or concatineated horizontally": What about some 132 | > combination of the two? How can visibility hinder legibility? Again, 133 | > I expect a bit more discussion. 134 | 135 | Clarified that this is an assumption 136 | 137 | > p3 "we require the output displayed in Fig. 1.": I think this needs 138 | > more explanation, rather than a simple requirement. 139 | 140 | Done 141 | 142 | > p5, para starting "How does Wadler's library": I found the explanation 143 | > of Wadler's restrictive API confusing. Can this be tightened up? 144 | 145 | [Not sure what to do] 146 | 147 | > p5, para starting "The above result": great discussion! I happened to 148 | > like that layout, but nice argument about why it is unsatisfying. 149 | 150 | Thanks :) 151 | 152 | > p6, para 1, "However, as Hughes', Leijen's implementation": Awkward 153 | > phrasing. 154 | 155 | Improved 156 | 157 | > p6, "intercalate": not defined or introduced (I've seen this a few 158 | > times: I know these are standard definitions in the libraries, but I 159 | > still think there's value in explaining such standard definitions in 160 | > a pearl). 161 | 162 | Fixed 163 | 164 | > p6, "Hughes' advice": should be a citation here, and also, the quote 165 | > is not correct: Hughes says "[...] so that its first character abuts 166 | > against the last [...]". 167 | 168 | Fixed 169 | 170 | > p7, def (<>): I think there should be a reminder about why `[x ++ y]` 171 | > is correct: the key is that these are Strings. 172 | 173 | Added type annotation 174 | 175 | > p8, QuickCheck properties: It's nice to see these properties here, but 176 | > the way they have been layed out in the paper makes them very hard to 177 | > parse. Perhaps some spacing and approrpiate alignment would help. 178 | > Alternatively, can some of these properties be given as equations 179 | > before rendered as code? 180 | 181 | Typesetting improved 182 | 183 | > p8 "One might expect this law to hold": I expected maybe something 184 | > like `flush a <> flush b = flush (a <> b)`, but in any case, I'd 185 | > like some intuition as to why one might expect yours to hold 186 | > (though, as you say, it doesn't) 187 | 188 | Improved to use the proposed law 189 | 190 | > p9: It seems like idioms/applicatives are being assumed. This needs 191 | > more explanation. 192 | 193 | Clarified. 194 | 195 | > p10 "is insanely slow": Avoid the use of "insane". 196 | 197 | Fixed 198 | 199 | > p10 "construct fully layouts": spelling. 200 | 201 | Fixed 202 | 203 | > p10, diagrams bottom: perhaps the subscripts here should be `a` and 204 | > `b` to match the code (or the code should match these subscripts?) 205 | 206 | Fixed 207 | 208 | > p11 "The correctness [...] relies on intuition": This sentence rings 209 | > alarm bells for me: I think the correctness should be justified 210 | > somewhere formally, or we should simply rely on intuition and not 211 | > assert correctness. 212 | 213 | 214 | Precisely. It is in fact explained in the next sentence that we must 215 | give a formal explanation, and the formal development follows. 216 | 217 | > p12 `valid`: This function is not introduced, but I suspect it is the 218 | > same as `validMeasure`. 219 | 220 | Fixed 221 | 222 | > p12 Proof: The definition uses <=, not <. 223 | 224 | Fixed 225 | 226 | > p12 def `ctx`: I don't think `ctx` has been explained. This becomes 227 | > more important in Thm 5.4, since now we rely on the notion. 228 | 229 | There is a universal quantification over contexts and ctx is the 230 | variable name. I've improved the typesetting in order to make this 231 | clearer. 232 | 233 | > p15 graph: Please label axis. 234 | 235 | Done 236 | 237 | > p17 "avoided to cut any corner": grammar. 238 | 239 | Fixed 240 | 241 | > =========================================================================== 242 | > ICFP 2017 Review #1B 243 | > --------------------------------------------------------------------------- 244 | > Paper #1: Functional Pearl: a pretty but not greedy printer 245 | > --------------------------------------------------------------------------- 246 | > 247 | > Overall merit: B. OK paper, but I will not champion 248 | > it 249 | > Confidence: X. I am an expert in this area 250 | > 251 | > ===== Paper summary ===== 252 | > 253 | > This functional pearl shows how to develop a pretty-printing 254 | > library that selects the best layout that satisfies a number of 255 | > design principles, in contrast to existing libraries that only 256 | > aim to select a good layout to increase efficiency. The library 257 | > is developed in a stepwise manner using program calculation 258 | > techniques, and the end result is reasonably efficient. 259 | > 260 | > ===== Comments to authors ===== 261 | > 262 | > General comments: 263 | > 264 | > I enjoyed reading this paper - it's a clear and simple example of 265 | > the power of functional programming and program calculation, and 266 | > should be readily understandable by anyone with some prior exposure 267 | > to pretty-printing combinators and equational reasoning. 268 | > 269 | > However, in its present form the presentation of the paper does 270 | > not quite meet the high standard required for a functional pearl, 271 | > as indicated by the large number of 'specific comments' below. 272 | > In addition, the benchmarking results that are presented in 273 | > section 6 are unsatisfactory in a number of aspects: 274 | > 275 | > * I am not convinced that full binary trees of S-expressions are 276 | > "representative outputs", as they have a very regular structure. 277 | > Why not consider random trees up to a given depth, and also try 278 | > applying the system to examples involving real program code? 279 | > 280 | > * The benchmarking appears to only be done for the version that 281 | > calculates how much space the layout takes, rather than the 282 | > one that actually does the layout, which is what is important. 283 | > 284 | > * It is also necessary to benchmark against other pretty-printing 285 | > libraries, to give an indication of relative performance. 286 | > 287 | > Nonetheless, if all the comments in this review were properly 288 | > addressed, the paper could make good functional pearl, and 289 | > may merit an A rating rather than the current B rating. If 290 | > the author did an excellent job with this, I could imagine 291 | > recommending the final version to my collagues and students, 292 | > which is a good 'litmus test' for a functional pearl. 293 | 294 | I agree with the above comments. I believe that they can be 295 | addressed for the second round of reviews, as the ICFP reviewing 296 | process provides. 297 | 298 | > 299 | > Specific comments: 300 | > 301 | > p1, In the final version of the paper the code should be made 302 | > publically available and a link included in the introduction. 303 | 304 | Added 305 | 306 | > p1, The paper would benefit from the addition of a short abstract 307 | > to provide a summary of the paper to attract potential readers. 308 | 309 | Added 310 | 311 | > p1, "clever use" -> "appropriate use" ? 312 | 313 | Changed 314 | 315 | > p2, At the end of section 1 it would be worth mentioning explicitly 316 | > that the principles may be in conflict, i.e. it may be necessary to 317 | > violate one principle in order for another to be satisfied, hence 318 | > the need for imposing an ordering on the principles. 319 | 320 | Improved 321 | 322 | > p2, Explain what S-expressions are to make the paper self contained; 323 | > not all potential readers will be familiar with these. 324 | 325 | Done 326 | 327 | > p2, Clarify that Haskell is used throughout for coding purposes. 328 | 329 | Done 330 | 331 | > p2, Displayed examples are currently not indented, but code is. It 332 | > would be beneficial to also indent the examples a few spaces too. 333 | 334 | TODO 335 | 336 | > p2, When first mentioning horizontal composition, it would good to 337 | > give a forward reference to section 4.1 where its meaning is 338 | > clarified, as many readers will be wondering at this point if the 339 | > meaning is the same as Hughes', which it is. 340 | 341 | Text is now improved 342 | 343 | > p3, "child's play" -> "straightforward". 344 | 345 | Fixed 346 | 347 | > p3, "Wadler's or Hughes'" -> "Wadler's nor Hughes'". 348 | 349 | Fixed 350 | 351 | > p3, The page break in the middle of the example is unfortunate, 352 | > and could be avoided by putting the example in a figure. 353 | 354 | TODO 355 | 356 | > p3, The example could be simplified -- in particular, two instances 357 | > of "abcd" rather than five would suffice to illustrate the problem. 358 | 359 | I chose to leave as it is for dramatic purposes. 360 | 361 | > p4, There is no need to start a new section at 3.1, as this just 362 | > carries on directly from the problem that was identified. 363 | 364 | This heading is used as an anchor point for reference a few pages 365 | later. I do believe that it is useful. 366 | 367 | > p4, "do decide" -> "to decide". 368 | 369 | Fixed 370 | 371 | > p5, "Let us say" -> "Suppose". 372 | 373 | Fixed 374 | 375 | > p5, "after the atom abcdefgh" -> "after the atom abcde" ? 376 | 377 | Fixed 378 | 379 | > p5, "ml-style" -> "ML-style". 380 | 381 | Fixed 382 | 383 | > p6, "In sum" -> "In conclusion" (in two places). 384 | 385 | I decided to use "in summary", which I believe is more appropriate 386 | 387 | > p6, "Daan Leijen's" -> "Leijen's". 388 | 389 | Change 390 | 391 | > p6, The sentence "But; let us not..." does not parse. 392 | 393 | Improved 394 | 395 | > p7, There is no need to include both versions of the <> picture -- 396 | > the diagrammatic version suffices on its own. 397 | 398 | TODO 399 | 400 | > It would also be 401 | > beneficial to colour in one of the boxes, e.g. in light grey. 402 | 403 | Done 404 | 405 | > p7, "as it becomes" -> "as becomes". 406 | 407 | Changed 408 | 409 | > p8, Can the Quickcheck properties be checked using Quickcheck? 410 | > If so, it would be useful to say this explicitly. 411 | 412 | Clarified: they can be checked only if properly monomorphized using 413 | either of the concrete implementations provided later. 414 | 415 | > p8, Including the types for the properties makes them difficult to 416 | > read - it would be better to omit the types and say either that they 417 | > can all be inferred or are straightforward to define. 418 | 419 | Unfortunately the types cannot be properly inferred due to the 420 | monomorphism restriction of Haskell. This sort of combines badly with 421 | the problem mentioned in the reply to the previous comment. So, I've 422 | decided to keep the type signatures and relax the typesetting a bit 423 | for legibility. 424 | 425 | > p8, "right away" -> "straight away". 426 | 427 | Changed 428 | 429 | > p9, The definition of render is nice! But it may be better to write 430 | > 431 | > render = render . minimumBy size . filter visible 432 | > 433 | > and define 434 | > 435 | > size = compare `on` length 436 | > 437 | > as this makes the definition for render more naturally 'readable'. 438 | 439 | Per request of another reviewer, I've rewritten this paragraph to make 440 | the formalities more obvious. I've also added the `size` definition. 441 | 442 | > p9, The use of "..." at the bottom of the page is too informal. 443 | 444 | Improved 445 | 446 | > p10, There is no need to take a new paragraph at the start of the page. 447 | 448 | Fixed. 449 | 450 | > p10, "insanely slow" is too informal for an ICFP paper. 451 | 452 | Fixed 453 | 454 | > p10, Some introductory text for section 5 is required, to explain 455 | > that the aim now is to apply program fusion techniques to obtain a 456 | > more efficient version of the render function. 457 | 458 | Added 459 | 460 | > p10, "construct fully" -> "fully construct". 461 | 462 | Fixed 463 | 464 | > p11, Can the laws be checked automatically using QuickCheck? 465 | 466 | Clarified 467 | 468 | > p11, Some introductory text is required before validMeasure. It also 469 | > seems that validMeasure should actually be called 'valid'? 470 | 471 | Fixed 472 | 473 | > p12, Explain the choice of the term 'monotonous'. Isn't there a 474 | > standard mathematical way of saying that if a property is true when 475 | > an operator is applied then its also true of the arguments, i.e. the 476 | > opposite of saying that the property is preserved by an operator? 477 | 478 | I have changed the formulation 479 | 480 | > p12, Lemma 5.3 is not necessary, and should be removed. 481 | 482 | TODO 483 | 484 | > p12, Missing space after the closing bracket in the name of thm 5.4. 485 | 486 | Fixed 487 | 488 | > p13, "thus make" -> "thus we can make". 489 | 490 | Fixed 491 | 492 | > p13, The proofs of lemmas 5.5 and 5.6 should either be omitted, or 493 | > moved to the appendix, as they are just simple calculations. 494 | 495 | TODO 496 | 497 | > p14, A reference for 'Pareto frontier' is required, along with some 498 | > explanation of what this means to make the paper self-contained. 499 | 500 | Fixed 501 | 502 | > p14, "pareto" -> "Pareto". 503 | 504 | Fixed 505 | 506 | > p14, Some concluding text is required for section 5. 507 | 508 | Added 509 | 510 | > p15, Some latex math symbols $ are showing up in the text. 511 | 512 | Fixed 513 | 514 | > p15, Some introductory text is required for section 7. 515 | 516 | Added 517 | 518 | > p17, The conclusion is too short at just over five lines, and seems 519 | > to have been written in rather a hurry at the last minute. This 520 | > needs to be expanded to a proper conclusion. 521 | 522 | Expanded to frame the paper in a more general background. 523 | 524 | > p18, The appendix should be omitted in the published version, as it 525 | > doesn't add much that most readers would be interested in. 526 | 527 | To be discussed with the editors. 528 | 529 | > =========================================================================== 530 | > ICFP 2017 Review #1C 531 | > --------------------------------------------------------------------------- 532 | > Paper #1: Functional Pearl: a pretty but not greedy printer 533 | > --------------------------------------------------------------------------- 534 | > 535 | > Overall merit: A. Good paper, I will champion it 536 | > Confidence: Y. I am knowledgeable in this area, 537 | > but not an expert 538 | > 539 | > ===== Paper summary ===== 540 | > 541 | > This paper defines interesting and reasonable criteria for "pretty 542 | > printing": visibility, legibility and frugality. Then it presents a 543 | > simple yet practical algorithm for finding an optimal solution 544 | > satisfying the criteria. Unlike other existing algorithms, the 545 | > presented algorithm finds an optimal solution rather than a greedy 546 | > solution. 547 | > 548 | > ===== Comments to authors ===== 549 | > 550 | > This paper well motivates the problem, clearly defines the goal, 551 | > presents a simple solution with a proof that it satisfies the goal 552 | > step by step, and demonstrates its practicality by giving an 553 | > evaluation result. The paper is fun to read and easy to follow. To me 554 | > this is a good "functional pearl" paper. 555 | > 556 | > One downside is that the evaluation result is not so extensive. I 557 | > have two specific suggestions for improvement. 558 | > 559 | > - First, I'd like to see evaluation results for pretty printing real 560 | > world code, and comparison with other existing algorithms. I think 561 | > one can easily find real-world html, xml or json files with 1K or 562 | > even 10K LOC. 563 | > 564 | > - Second, I wonder how effective the domination pruning is. The 565 | > domination criteria seems to be so strict that it may not prune out 566 | > many internal results. In order to answer this question, the author 567 | > can simply include evaluation results without the domination pruning 568 | > as well. 569 | > 570 | > * Minor comments 571 | > 572 | > I think Theorem 5.4 should take validity into account. In other words: 573 | > ``` 574 | > a < b -> valid (ctx b) -> valid (ctx a) /\ height (ctx a) <= height (ctx b) 575 | > ``` 576 | > instead of 577 | > ``` 578 | > a < b -> height (ctx a) <= height (ctx b) 579 | > ``` 580 | > Please correct me if I am wrong. 581 | 582 | I think that the paper is correct. The key is that the premiss is the 583 | domination relation: it does not require just a relation on the height 584 | but also on the two other components of the measure. 585 | 586 | > * Typos 587 | > 588 | > page 3 589 | > - "horizontally (for Legibility)," -> 590 | > "horizontally (for Legibility)." 591 | 592 | I did not understand this one. 593 | 594 | > page 5 595 | > - "after the atom abcdefgh" -> "after the atom abcde" 596 | 597 | Fixed 598 | 599 | > page 7 600 | > - "Horizontal concatenation is then:" -> "Vertical concatenation is then:" 601 | 602 | Fixed 603 | 604 | > page 16 605 | > - "on the right-hand-side of horizontal concatenation" -> "on the right-hand-side of vertical concatenation" 606 | > (I am not sure about this) 607 | 608 | Paper was wrong. Fixed 609 | 610 | -------------------------------------------------------------------------------- /paper/ICFP2017-reply.txt: -------------------------------------------------------------------------------- 1 | I would first like to thank the reviewers for their fair, thoughtful 2 | and encouraging reviews. 3 | 4 | 5 | # Response to review A: 6 | 7 | > One aspect I really enjoyed about this paper is the introduction of 8 | > the three principles that make a pretty printer. They are informally 9 | > introduced on p1, which is good, but I would also like to have a 10 | > formally explained version somewhere; this is brushed on in S4.3, 11 | > where it's clear the functions `mostFrugal` and `filter visible` play 12 | > a part, but I think that this should be elaborated and turned into a 13 | > more formal part of the story. 14 | 15 | Even though the draft paper does not emphasize it, I believe that 16 | `mostFrugal` is a crisp formalization of the frugality principle, and 17 | that `filter visible` is a crisp formalization of visibility. 18 | 19 | It would be helpful if the reviewer could point to an intermediate 20 | formalization if they have one in mind. 21 | 22 | > There are some gaps in the literature review that need to be plugged: 23 | > this is a very minor point that can easily be cleared up, but there 24 | > should probably be a reference to the chapter on pretty printing 25 | > in Bird's "Thinking Functionally with Haskell" book, and also to the 26 | > more recent work by Olaf Chitil "Pretty Printing with lazy deques" 2005. 27 | 28 | I agree that this gap should be adressed in the final version of the paper. 29 | 30 | 31 | 32 | # Response to reviews B and C: 33 | 34 | I agree that the evaluation section can be improved and I'll be happy 35 | to do so. If I understood correctly the demands of the reviewers (see 36 | below), I believe that they can be met before the (planned) second round of 37 | reviews. To sum up: 38 | 39 | - benchmark the text generation (not only the measurement of the space taken) 40 | 41 | - test on random s-exprs 42 | 43 | - test on real-world data (eg. 1K-10K lines xml and json) 44 | 45 | - test without domination pruning 46 | 47 | - as far as possible (due to differing behaviour) perform the above 48 | tests with hughes and wadler libraries 49 | -------------------------------------------------------------------------------- /paper/Paper.hs: -------------------------------------------------------------------------------- 1 | import qualified PM 2 | 3 | main = PM.main 4 | -------------------------------------------------------------------------------- /paper/benchdata/cds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Empire Burlesque 5 | Bob Dylan 6 | USA 7 | Columbia 8 | 10.90 9 | 1985 10 | 11 | 12 | Hide your heart 13 | Bonnie Tyler 14 | UK 15 | CBS Records 16 | 9.90 17 | 1988 18 | 19 | 20 | Greatest Hits 21 | Dolly Parton 22 | USA 23 | RCA 24 | 9.90 25 | 1982 26 | 27 | 28 | Still got the blues 29 | Gary Moore 30 | UK 31 | Virgin records 32 | 10.20 33 | 1990 34 | 35 | 36 | Eros 37 | Eros Ramazzotti 38 | EU 39 | BMG 40 | 9.90 41 | 1997 42 | 43 | 44 | One night only 45 | Bee Gees 46 | UK 47 | Polydor 48 | 10.90 49 | 1998 50 | 51 | 52 | Sylvias Mother 53 | Dr.Hook 54 | UK 55 | CBS 56 | 8.10 57 | 1973 58 | 59 | 60 | Maggie May 61 | Rod Stewart 62 | UK 63 | Pickwick 64 | 8.50 65 | 1990 66 | 67 | 68 | Romanza 69 | Andrea Bocelli 70 | EU 71 | Polydor 72 | 10.80 73 | 1996 74 | 75 | 76 | When a man loves a woman 77 | Percy Sledge 78 | USA 79 | Atlantic 80 | 8.70 81 | 1987 82 | 83 | 84 | Black angel 85 | Savage Rose 86 | EU 87 | Mega 88 | 10.90 89 | 1995 90 | 91 | 92 | 1999 Grammy Nominees 93 | Many 94 | USA 95 | Grammy 96 | 10.20 97 | 1999 98 | 99 | 100 | For the good times 101 | Kenny Rogers 102 | UK 103 | Mucik Master 104 | 8.70 105 | 1995 106 | 107 | 108 | Big Willie style 109 | Will Smith 110 | USA 111 | Columbia 112 | 9.90 113 | 1997 114 | 115 | 116 | Tupelo Honey 117 | Van Morrison 118 | UK 119 | Polydor 120 | 8.20 121 | 1971 122 | 123 | 124 | Soulsville 125 | Jorn Hoel 126 | Norway 127 | WEA 128 | 7.90 129 | 1996 130 | 131 | 132 | The very best of 133 | Cat Stevens 134 | UK 135 | Island 136 | 8.90 137 | 1990 138 | 139 | 140 | Stop 141 | Sam Brown 142 | UK 143 | A and M 144 | 8.90 145 | 1988 146 | 147 | 148 | Bridge of Spies 149 | T'Pau 150 | UK 151 | Siren 152 | 7.90 153 | 1987 154 | 155 | 156 | Private Dancer 157 | Tina Turner 158 | UK 159 | Capitol 160 | 8.90 161 | 1983 162 | 163 | 164 | Midt om natten 165 | Kim Larsen 166 | EU 167 | Medley 168 | 7.80 169 | 1983 170 | 171 | 172 | Pavarotti Gala Concert 173 | Luciano Pavarotti 174 | UK 175 | DECCA 176 | 9.90 177 | 1991 178 | 179 | 180 | The dock of the bay 181 | Otis Redding 182 | USA 183 | Stax Records 184 | 7.90 185 | 1968 186 | 187 | 188 | Picture book 189 | Simply Red 190 | EU 191 | Elektra 192 | 7.20 193 | 1985 194 | 195 | 196 | Red 197 | The Communards 198 | UK 199 | London 200 | 7.80 201 | 1987 202 | 203 | 204 | Unchain my heart 205 | Joe Cocker 206 | USA 207 | EMI 208 | 8.20 209 | 1987 210 | 211 | 212 | 213 | 214 | Empire Burlesque 215 | Bob Dylan 216 | USA 217 | Columbia 218 | 10.90 219 | 1985 220 | 221 | 222 | Hide your heart 223 | Bonnie Tyler 224 | UK 225 | CBS Records 226 | 9.90 227 | 1988 228 | 229 | 230 | Greatest Hits 231 | Dolly Parton 232 | USA 233 | RCA 234 | 9.90 235 | 1982 236 | 237 | 238 | Still got the blues 239 | Gary Moore 240 | UK 241 | Virgin records 242 | 10.20 243 | 1990 244 | 245 | 246 | Eros 247 | Eros Ramazzotti 248 | EU 249 | BMG 250 | 9.90 251 | 1997 252 | 253 | 254 | One night only 255 | Bee Gees 256 | UK 257 | Polydor 258 | 10.90 259 | 1998 260 | 261 | 262 | Sylvias Mother 263 | Dr.Hook 264 | UK 265 | CBS 266 | 8.10 267 | 1973 268 | 269 | 270 | Maggie May 271 | Rod Stewart 272 | UK 273 | Pickwick 274 | 8.50 275 | 1990 276 | 277 | 278 | Romanza 279 | Andrea Bocelli 280 | EU 281 | Polydor 282 | 10.80 283 | 1996 284 | 285 | 286 | When a man loves a woman 287 | Percy Sledge 288 | USA 289 | Atlantic 290 | 8.70 291 | 1987 292 | 293 | 294 | Black angel 295 | Savage Rose 296 | EU 297 | Mega 298 | 10.90 299 | 1995 300 | 301 | 302 | 1999 Grammy Nominees 303 | Many 304 | USA 305 | Grammy 306 | 10.20 307 | 1999 308 | 309 | 310 | For the good times 311 | Kenny Rogers 312 | UK 313 | Mucik Master 314 | 8.70 315 | 1995 316 | 317 | 318 | Big Willie style 319 | Will Smith 320 | USA 321 | Columbia 322 | 9.90 323 | 1997 324 | 325 | 326 | Tupelo Honey 327 | Van Morrison 328 | UK 329 | Polydor 330 | 8.20 331 | 1971 332 | 333 | 334 | Soulsville 335 | Jorn Hoel 336 | Norway 337 | WEA 338 | 7.90 339 | 1996 340 | 341 | 342 | The very best of 343 | Cat Stevens 344 | UK 345 | Island 346 | 8.90 347 | 1990 348 | 349 | 350 | Stop 351 | Sam Brown 352 | UK 353 | A and M 354 | 8.90 355 | 1988 356 | 357 | 358 | Bridge of Spies 359 | T'Pau 360 | UK 361 | Siren 362 | 7.90 363 | 1987 364 | 365 | 366 | Private Dancer 367 | Tina Turner 368 | UK 369 | Capitol 370 | 8.90 371 | 1983 372 | 373 | 374 | Midt om natten 375 | Kim Larsen 376 | EU 377 | Medley 378 | 7.80 379 | 1983 380 | 381 | 382 | Pavarotti Gala Concert 383 | Luciano Pavarotti 384 | UK 385 | DECCA 386 | 9.90 387 | 1991 388 | 389 | 390 | The dock of the bay 391 | Otis Redding 392 | USA 393 | Stax Records 394 | 7.90 395 | 1968 396 | 397 | 398 | Picture book 399 | Simply Red 400 | EU 401 | Elektra 402 | 7.20 403 | 1985 404 | 405 | 406 | Red 407 | The Communards 408 | UK 409 | London 410 | 7.80 411 | 1987 412 | 413 | 414 | Unchain my heart 415 | Joe Cocker 416 | USA 417 | EMI 418 | 8.20 419 | 1987 420 | 421 | 422 | 423 | 424 | Empire Burlesque 425 | Bob Dylan 426 | USA 427 | Columbia 428 | 10.90 429 | 1985 430 | 431 | 432 | Hide your heart 433 | Bonnie Tyler 434 | UK 435 | CBS Records 436 | 9.90 437 | 1988 438 | 439 | 440 | Greatest Hits 441 | Dolly Parton 442 | USA 443 | RCA 444 | 9.90 445 | 1982 446 | 447 | 448 | Still got the blues 449 | Gary Moore 450 | UK 451 | Virgin records 452 | 10.20 453 | 1990 454 | 455 | 456 | Eros 457 | Eros Ramazzotti 458 | EU 459 | BMG 460 | 9.90 461 | 1997 462 | 463 | 464 | One night only 465 | Bee Gees 466 | UK 467 | Polydor 468 | 10.90 469 | 1998 470 | 471 | 472 | Sylvias Mother 473 | Dr.Hook 474 | UK 475 | CBS 476 | 8.10 477 | 1973 478 | 479 | 480 | Maggie May 481 | Rod Stewart 482 | UK 483 | Pickwick 484 | 8.50 485 | 1990 486 | 487 | 488 | Romanza 489 | Andrea Bocelli 490 | EU 491 | Polydor 492 | 10.80 493 | 1996 494 | 495 | 496 | When a man loves a woman 497 | Percy Sledge 498 | USA 499 | Atlantic 500 | 8.70 501 | 1987 502 | 503 | 504 | Black angel 505 | Savage Rose 506 | EU 507 | Mega 508 | 10.90 509 | 1995 510 | 511 | 512 | 1999 Grammy Nominees 513 | Many 514 | USA 515 | Grammy 516 | 10.20 517 | 1999 518 | 519 | 520 | For the good times 521 | Kenny Rogers 522 | UK 523 | Mucik Master 524 | 8.70 525 | 1995 526 | 527 | 528 | Big Willie style 529 | Will Smith 530 | USA 531 | Columbia 532 | 9.90 533 | 1997 534 | 535 | 536 | Tupelo Honey 537 | Van Morrison 538 | UK 539 | Polydor 540 | 8.20 541 | 1971 542 | 543 | 544 | Soulsville 545 | Jorn Hoel 546 | Norway 547 | WEA 548 | 7.90 549 | 1996 550 | 551 | 552 | The very best of 553 | Cat Stevens 554 | UK 555 | Island 556 | 8.90 557 | 1990 558 | 559 | 560 | Stop 561 | Sam Brown 562 | UK 563 | A and M 564 | 8.90 565 | 1988 566 | 567 | 568 | Bridge of Spies 569 | T'Pau 570 | UK 571 | Siren 572 | 7.90 573 | 1987 574 | 575 | 576 | Private Dancer 577 | Tina Turner 578 | UK 579 | Capitol 580 | 8.90 581 | 1983 582 | 583 | 584 | Midt om natten 585 | Kim Larsen 586 | EU 587 | Medley 588 | 7.80 589 | 1983 590 | 591 | 592 | Pavarotti Gala Concert 593 | Luciano Pavarotti 594 | UK 595 | DECCA 596 | 9.90 597 | 1991 598 | 599 | 600 | The dock of the bay 601 | Otis Redding 602 | USA 603 | Stax Records 604 | 7.90 605 | 1968 606 | 607 | 608 | Picture book 609 | Simply Red 610 | EU 611 | Elektra 612 | 7.20 613 | 1985 614 | 615 | 616 | Red 617 | The Communards 618 | UK 619 | London 620 | 7.80 621 | 1987 622 | 623 | 624 | Unchain my heart 625 | Joe Cocker 626 | USA 627 | EMI 628 | 8.20 629 | 1987 630 | 631 | 632 | 633 | 634 | Empire Burlesque 635 | Bob Dylan 636 | USA 637 | Columbia 638 | 10.90 639 | 1985 640 | 641 | 642 | Hide your heart 643 | Bonnie Tyler 644 | UK 645 | CBS Records 646 | 9.90 647 | 1988 648 | 649 | 650 | Greatest Hits 651 | Dolly Parton 652 | USA 653 | RCA 654 | 9.90 655 | 1982 656 | 657 | 658 | Still got the blues 659 | Gary Moore 660 | UK 661 | Virgin records 662 | 10.20 663 | 1990 664 | 665 | 666 | Eros 667 | Eros Ramazzotti 668 | EU 669 | BMG 670 | 9.90 671 | 1997 672 | 673 | 674 | One night only 675 | Bee Gees 676 | UK 677 | Polydor 678 | 10.90 679 | 1998 680 | 681 | 682 | Sylvias Mother 683 | Dr.Hook 684 | UK 685 | CBS 686 | 8.10 687 | 1973 688 | 689 | 690 | Maggie May 691 | Rod Stewart 692 | UK 693 | Pickwick 694 | 8.50 695 | 1990 696 | 697 | 698 | Romanza 699 | Andrea Bocelli 700 | EU 701 | Polydor 702 | 10.80 703 | 1996 704 | 705 | 706 | When a man loves a woman 707 | Percy Sledge 708 | USA 709 | Atlantic 710 | 8.70 711 | 1987 712 | 713 | 714 | Black angel 715 | Savage Rose 716 | EU 717 | Mega 718 | 10.90 719 | 1995 720 | 721 | 722 | 1999 Grammy Nominees 723 | Many 724 | USA 725 | Grammy 726 | 10.20 727 | 1999 728 | 729 | 730 | For the good times 731 | Kenny Rogers 732 | UK 733 | Mucik Master 734 | 8.70 735 | 1995 736 | 737 | 738 | Big Willie style 739 | Will Smith 740 | USA 741 | Columbia 742 | 9.90 743 | 1997 744 | 745 | 746 | Tupelo Honey 747 | Van Morrison 748 | UK 749 | Polydor 750 | 8.20 751 | 1971 752 | 753 | 754 | Soulsville 755 | Jorn Hoel 756 | Norway 757 | WEA 758 | 7.90 759 | 1996 760 | 761 | 762 | The very best of 763 | Cat Stevens 764 | UK 765 | Island 766 | 8.90 767 | 1990 768 | 769 | 770 | Stop 771 | Sam Brown 772 | UK 773 | A and M 774 | 8.90 775 | 1988 776 | 777 | 778 | Bridge of Spies 779 | T'Pau 780 | UK 781 | Siren 782 | 7.90 783 | 1987 784 | 785 | 786 | Private Dancer 787 | Tina Turner 788 | UK 789 | Capitol 790 | 8.90 791 | 1983 792 | 793 | 794 | Midt om natten 795 | Kim Larsen 796 | EU 797 | Medley 798 | 7.80 799 | 1983 800 | 801 | 802 | Pavarotti Gala Concert 803 | Luciano Pavarotti 804 | UK 805 | DECCA 806 | 9.90 807 | 1991 808 | 809 | 810 | The dock of the bay 811 | Otis Redding 812 | USA 813 | Stax Records 814 | 7.90 815 | 1968 816 | 817 | 818 | Picture book 819 | Simply Red 820 | EU 821 | Elektra 822 | 7.20 823 | 1985 824 | 825 | 826 | Red 827 | The Communards 828 | UK 829 | London 830 | 7.80 831 | 1987 832 | 833 | 834 | Unchain my heart 835 | Joe Cocker 836 | USA 837 | EMI 838 | 8.20 839 | 1987 840 | 841 | 842 | 843 | 844 | Empire Burlesque 845 | Bob Dylan 846 | USA 847 | Columbia 848 | 10.90 849 | 1985 850 | 851 | 852 | Hide your heart 853 | Bonnie Tyler 854 | UK 855 | CBS Records 856 | 9.90 857 | 1988 858 | 859 | 860 | Greatest Hits 861 | Dolly Parton 862 | USA 863 | RCA 864 | 9.90 865 | 1982 866 | 867 | 868 | Still got the blues 869 | Gary Moore 870 | UK 871 | Virgin records 872 | 10.20 873 | 1990 874 | 875 | 876 | Eros 877 | Eros Ramazzotti 878 | EU 879 | BMG 880 | 9.90 881 | 1997 882 | 883 | 884 | One night only 885 | Bee Gees 886 | UK 887 | Polydor 888 | 10.90 889 | 1998 890 | 891 | 892 | Sylvias Mother 893 | Dr.Hook 894 | UK 895 | CBS 896 | 8.10 897 | 1973 898 | 899 | 900 | Maggie May 901 | Rod Stewart 902 | UK 903 | Pickwick 904 | 8.50 905 | 1990 906 | 907 | 908 | Romanza 909 | Andrea Bocelli 910 | EU 911 | Polydor 912 | 10.80 913 | 1996 914 | 915 | 916 | When a man loves a woman 917 | Percy Sledge 918 | USA 919 | Atlantic 920 | 8.70 921 | 1987 922 | 923 | 924 | Black angel 925 | Savage Rose 926 | EU 927 | Mega 928 | 10.90 929 | 1995 930 | 931 | 932 | 1999 Grammy Nominees 933 | Many 934 | USA 935 | Grammy 936 | 10.20 937 | 1999 938 | 939 | 940 | For the good times 941 | Kenny Rogers 942 | UK 943 | Mucik Master 944 | 8.70 945 | 1995 946 | 947 | 948 | Big Willie style 949 | Will Smith 950 | USA 951 | Columbia 952 | 9.90 953 | 1997 954 | 955 | 956 | Tupelo Honey 957 | Van Morrison 958 | UK 959 | Polydor 960 | 8.20 961 | 1971 962 | 963 | 964 | Soulsville 965 | Jorn Hoel 966 | Norway 967 | WEA 968 | 7.90 969 | 1996 970 | 971 | 972 | The very best of 973 | Cat Stevens 974 | UK 975 | Island 976 | 8.90 977 | 1990 978 | 979 | 980 | Stop 981 | Sam Brown 982 | UK 983 | A and M 984 | 8.90 985 | 1988 986 | 987 | 988 | Bridge of Spies 989 | T'Pau 990 | UK 991 | Siren 992 | 7.90 993 | 1987 994 | 995 | 996 | Private Dancer 997 | Tina Turner 998 | UK 999 | Capitol 1000 | 8.90 1001 | 1983 1002 | 1003 | 1004 | Midt om natten 1005 | Kim Larsen 1006 | EU 1007 | Medley 1008 | 7.80 1009 | 1983 1010 | 1011 | 1012 | Pavarotti Gala Concert 1013 | Luciano Pavarotti 1014 | UK 1015 | DECCA 1016 | 9.90 1017 | 1991 1018 | 1019 | 1020 | The dock of the bay 1021 | Otis Redding 1022 | USA 1023 | Stax Records 1024 | 7.90 1025 | 1968 1026 | 1027 | 1028 | Picture book 1029 | Simply Red 1030 | EU 1031 | Elektra 1032 | 7.20 1033 | 1985 1034 | 1035 | 1036 | Red 1037 | The Communards 1038 | UK 1039 | London 1040 | 7.80 1041 | 1987 1042 | 1043 | 1044 | Unchain my heart 1045 | Joe Cocker 1046 | USA 1047 | EMI 1048 | 8.20 1049 | 1987 1050 | 1051 | 1052 | 1053 | -------------------------------------------------------------------------------- /paper/blog.md: -------------------------------------------------------------------------------- 1 | % The Prettiest Printer 2 | % Jean-Philippe Bernardy 3 | 4 | Popular Haskell pretty printers have given me less-than-optimal 5 | results. This is especially disappointing, as they seem to be the 6 | epitome of functional programs, blessed with the 7 | correct-by-construction methodology of program development. In this 8 | note I review why I find the current solutions sub-optimal, and propose 9 | a satisfactory alternative. 10 | 11 | The state of the art. 12 | ===================== 13 | 14 | Even today, pretty printing in Haskell is mostly backed by two classic 15 | libraries, either: 16 | 17 | 1. The Hughes-Peyton Jones library. The design is [described by 18 | Hughes](http://belle.sourceforge.net/doc/hughes95design.pdf) in 19 | *The Design of a Pretty-printing Library*. It has then been 20 | adopted (and modified) by Peyton Jones, and was distributed with GHC 21 | for a long time, making it the *de-facto* standard pretty printer. 22 | It is now available on Hackage in the 23 | [pretty](https://hackage.haskell.org/package/pretty) package. I believe that 24 | this remains the dominant design, perhaps disputed by... 25 | 26 | 2. The Wadler-Leijen library. In the penultimate chapter of *The Fun 27 | of Programming*, Wadler re-constructs a pretty printing library 28 | from scratch. Keeping true to Hughes in particular and the general 29 | functional programming tradition in general, Wadler starts by 30 | specifying his library using equational laws, and derives an 31 | implementation. Leijen took Wadler's implementation and modified it 32 | to increase its expressivity (but more on that later). The result is 33 | available in the eponymous 34 | [wl-pprint](https://hackage.haskell.org/package/wl-pprint) 35 | package. 36 | 37 | Not. Pretty. Enough. 38 | -------------------- 39 | 40 | As it happens, I am dissatisfied with the outputs produced by either 41 | libraries. At the risk of appearing ungrateful to the masters, I'll 42 | spend some effort to back this claim. 43 | 44 | ### Hughes 45 | 46 | Let us assume we want to pretty print S-Expressions: 47 | 48 | ``` {.example} 49 | data SExpr where 50 | SExpr :: [SExpr] -> SExpr 51 | Atom :: String -> SExpr 52 | ``` 53 | 54 | We'd like to allow to pretty print an S-Expr either horizontally, like 55 | so: 56 | 57 | ``` {.example} 58 | (a b c d) 59 | ``` 60 | 61 | or vertically, like so: 62 | 63 | ``` {.example} 64 | (a 65 | b 66 | c 67 | d) 68 | ``` 69 | 70 | (I'll refrain the urge to be more specific at this point). 71 | 72 | For the sake of the argument, let's pretend we want to pretty print the 73 | following s-expr: 74 | 75 | ``` {.example} 76 | abcd = SExpr $ map (Atom . (:[])) "abcd" 77 | abcd4 = SExpr [abcd,abcd,abcd,abcd] 78 | testData = SExpr [Atom "axbxcxd", abcd4] 79 | ``` 80 | 81 | Printed on a wide page, we'd get: 82 | 83 | ``` {.example} 84 | (axbxcxd ((a b c d) (a b c d) (a b c d) (a b c d) (a b c d))) 85 | ``` 86 | 87 | In a narrow page (whose width is indicated by the row of hashes), what 88 | we'd get from Hughes' library is the following output: 89 | 90 | ``` {.example} 91 | ############### 92 | (axbxcxd ((a 93 | b 94 | c 95 | d) 96 | (a 97 | b 98 | c 99 | d) 100 | (a 101 | b 102 | c 103 | d) 104 | (a 105 | b 106 | c 107 | d) 108 | (a 109 | b 110 | c 111 | d))) 112 | ############### 113 | ``` 114 | 115 | This does not quite cut it. Our senses of aesthetics are tingling... 116 | this is not pretty enough. Why can't we get the following? 117 | 118 | ``` {.example} 119 | ############### 120 | (axbxcxd 121 | ((a b c d) 122 | (a b c d) 123 | (a b c d) 124 | (a b c d) 125 | (a b c d)) 126 | ``` 127 | 128 | The thing is, Hughes believes that "it would be unreasonably inefficient 129 | for a pretty-printer do decide whether or not to split the first line of 130 | a document on the basis of the content of the last." (sec. 7.4 of his 131 | paper). Therefore, he chooses a greedy algorithm, which tries to fit as 132 | much as possible on a single line, without regard for what comes next. 133 | In our example, the algorithm fits `(axbxcxd ((a`, but then it has 134 | committed to a very deep indentation level, which forces a 135 | less-than-pretty outcome for the remainder of the document. 136 | 137 | ### Wadler 138 | 139 | Wadler's design fares somewhat better. It does not suffer from the above 140 | problem... *by default*. That is, it lacks the capability to express 141 | that sub-documents should be vertically aligned --- compositionally. 142 | 143 | Let us illustrate. Using Wadler's library, one might specify pretty 144 | printing of s-exprs as follows (see Wadler's paper is there is any doubt 145 | on the meaning of the following): 146 | 147 | ``` {.example} 148 | x y = x <> line <> y 149 | sep = foldr empty () 150 | ``` 151 | 152 | ``` {.example} 153 | pretty (SExpr xs) = group $ "(" <> nest 1 (sep $ map pretty xs) <> ")" 154 | pretty (Atom x) = text x 155 | ``` 156 | 157 | which appears to do the trick. Indeed, we get: 158 | 159 | ``` {.example} 160 | ############### 161 | (axbxcxd 162 | ((a b c d) 163 | (a b c d) 164 | (a b c d) 165 | (a b c d) 166 | (a b c d)) 167 | ``` 168 | 169 | However, the `group` combinator does not quite behave as I'd like. 170 | What `group` does is to allow its argument to be laid out on a single 171 | line, instead of multiple ones. Hence, we can put two elements next to 172 | each other *only if* they are flattened. This means that if we typeset 173 | the same s-expr, but in a slightly wider page, we get the same output: 174 | 175 | ``` {.example} 176 | ##################### 177 | (axbxcxd 178 | ((a b c d) 179 | (a b c d) 180 | (a b c d) 181 | (a b c d) 182 | (a b c d)) 183 | ``` 184 | 185 | whereas I crave something more pleasing to the eye: 186 | 187 | ``` {.example} 188 | ##################### 189 | (axbxcxd ((a b c d) 190 | (a b c d) 191 | (a b c d) 192 | (a b c d) 193 | (a b c d)) 194 | ``` 195 | 196 | At this point, the reader may raise two objections: 197 | 198 | - Objection 1: *All this fuss for ONE LINE?* 199 | - Reply 1: Every computer-science academic has once in their lifetime 200 | been in a killing mood due to a one-line-too-long printout causing a 201 | paper to spill over the page limit. So that extra line saved *is* 202 | worth something. Plus, we can construct examples where more space 203 | can be saved. 204 | 205 | - Objection 2: *Leijen's extension of Wadler's design solves the 206 | issue: it provides an `align` combinator.* 207 | - Reply 2: Yes, but: it exhibits the same symptoms as Hughes' library. 208 | 209 | Aside: In his paper, Wadler proves that his library produces the 210 | shortest output. But, Leijen's extension breaks this invariant. This 211 | makes me suspect that the extension was done on the implementation 212 | directly rather than on the design. 213 | 214 | The search for the prettiest output 215 | =================================== 216 | 217 | API 218 | --- 219 | 220 | Before discussing possible algorithms, we need to chose wisely the the 221 | document-description language that we accept. Daringly standing on 222 | Phil's strong shoulders, I propose the following set of combinators: 223 | 224 | - `empty`: The empty document 225 | - `(<>)`: concatenation 226 | - `line`: insert a new line (unconditionally) 227 | - `text`: insert a meaningful piece of text 228 | - `nest`: nest the argument 229 | - `align`: align the documents in the argument 230 | - `(<|>)`: disjunction of layouts 231 | - `spacing`: non-meaningful text (spaces or typographical marks) 232 | 233 | We can represent the above API in a data type, as follows: 234 | 235 | ``` {.example} 236 | data Doc where 237 | Line :: Doc 238 | Nil :: Doc 239 | (:<>) :: Doc -> Doc -> Doc 240 | Text :: String -> Doc 241 | Nest :: Int -> Doc -> Doc 242 | Align :: Doc -> Doc 243 | (:<|>) :: Doc -> Doc -> Doc -- ^ Attn: INVARIANT 244 | Spacing :: String -> Doc 245 | ``` 246 | 247 | Compared to Wadler (and *a-fortiori* Hughes) the above API is very 248 | liberal in the typesetting strategies that it can express. Indeed, the 249 | user can use a fully-general disjunction operator `(<|>)`, which 250 | accepts arbitrary documents as arguments. A downside is that it leaves 251 | the user responsible to give two documents that differ only in layout: 252 | they must have the same `contents`. 253 | 254 | 255 | ``` {.example} 256 | contents :: Doc -> [String] 257 | contents (Spacing _) = [] 258 | contents Nil = [] 259 | contents Line = [] 260 | contents (d1 :<> d2) = contents d1 <> contents d2 261 | contents (Text x) = [x] 262 | contents (Align x) = contents x 263 | contents (x :<|> y) = contents x 264 | ``` 265 | (Note that the `contents` function relies on the invariant being 266 | verified.) 267 | 268 | Other invariants include that text and spacing may not contain any 269 | newline, and nesting may not be negative. 270 | 271 | ### Example 272 | 273 | Using the above combinators, we can pretty print s-exprs as follows: 274 | 275 | ``` {.example} 276 | x <+> y = x <> Spacing " " <> y 277 | x y = x <> Line <> y 278 | ``` 279 | 280 | ``` {.example} 281 | sep [] = mempty 282 | sep xs = foldr1 (<+>) xs :<|> Align (foldr1 () xs) 283 | pretty (Atom s) = Text s 284 | pretty (SExpr xs) = Text "(" <> (sep $ map pretty xs) <> Text ")" 285 | ``` 286 | 287 | The `sep` combinator now precisely expresses what I was after at the 288 | beginning: either all the elements are glued horizontally, or they are 289 | aligned vertically. 290 | 291 | Semantics 292 | --------- 293 | 294 | We have our API and an intuition of what it means. Let us make the 295 | intuition formal, by specifying how to render documents. I could do as 296 | Hughes or Wadler and start by stating a few laws on the API (in 297 | particular all laws stated by Wadler should hold). Instead I'll give 298 | the semantics directly, using a compositional interpretation. I will 299 | interpret documents as a non-deterministic function from the current 300 | indentation level (1st argument) and current column (2nd argument) to 301 | a text and a final column. 302 | 303 | Using lists for non-determinism, we have: 304 | 305 | ``` {.example} 306 | type Semantics = Int -> Int -> [(String,Int)] 307 | ``` 308 | 309 | The interpretation function is then the following. 310 | 311 | ``` {.example} 312 | eval :: Doc -> Semantics 313 | eval (Text s) i c = return (s, c + length s) 314 | eval (Spacing s) i c = return (s, c + length s) 315 | eval Nil i c = return ("",c) 316 | eval (Align d) i c = eval d c c 317 | eval (Nest j d) i c = eval d (i+j) c 318 | eval Line i c = return ('\n' : replicate i ' ', i) 319 | eval (d1 :<> d2) i c = do 320 | (t1,c1) <- eval d1 i c 321 | (t2,c2) <- eval d2 i c1 322 | return (t1 ++ t2, c2) 323 | eval (d1 :<|> d2) i c = eval d1 i c ++ eval d2 i c 324 | ``` 325 | 326 | Given the use of monadic syntax to handle list-non-determinism, the 327 | interpretation of `text`, `spacing`, `empty`, `<>`, and even `<|>` 328 | reserve no particular surprise. The interesting bit is the interplay 329 | between `line`, `nest` and `align`. 330 | 331 | The indentation level is implemented by inserting a certain number of 332 | spaces after moving to the next `Line` (which also resets the current 333 | column). `Nest`-ing is defined by increasing the indentation 334 | level. `Align`-ing means setting the indentation level to the current 335 | column. (Exercise: verify that, at all times, *c >= i*.) 336 | 337 | Finally, we can define the prettiest rendering as that which 338 | 339 | - fits the page and 340 | - uses the smallest amount of lines 341 | 342 | (This is not quite the ideal definition: sometimes no layout fits the 343 | page, and we want to pick that with the least overflow. But we'll 344 | leave such details to the implementer and stick to the simpler 345 | definition given above.) 346 | 347 | Fitting the page means that the line width is less than the page width: 348 | 349 | ``` {.example} 350 | maxWidth = maximum . map length . lines 351 | fits w s = maxWidth s <= w 352 | ``` 353 | 354 | The final renderer is thus: 355 | 356 | ``` {.example} 357 | height = length . lines 358 | render w d = minimumBy (compare `on` height) $ filter (fits w) $ map fst $ eval d 0 0 359 | ``` 360 | 361 | The above renderer satisfies our needs: it finds the prettiest layout. 362 | Yet, we should not expect to get results quickly. A document may contain 363 | hundreds of disjunctions, and if we exhaustively search a space that 364 | big, even the legendary long-lasting batteries of our iPads(tm) will die 365 | before anything can be printed. 366 | 367 | Implementation 368 | -------------- 369 | 370 | Fortunately, there is a way out of this tar-pit. The trick is to explore 371 | the search space *line by line*. That is, every time we find the 372 | `Line` combinator, we stash the current partial result for later 373 | examination. Eventually, all pending states will be stashed. We can then 374 | *prune out* useless, dominated states, and resume the search. There 375 | remains to define when a state is dominated: 376 | 377 | For each state *t*, we define: 378 | 379 | - *i(t)*: the indentation of the next line (remember that we stopped 380 | at a given newline) 381 | - *p(t)*: the progress inside the document, defined as the number of 382 | tokens printed so far. Remember that disjuncted documents must have 383 | the same contents, so it is meaningful to compare *p(t)* and *p(u)* 384 | for every pair of states *(t,u)*. 385 | 386 | Definition: *t* dominates *u* iff. *i(t) < i(u)* and *p(t) >= p(u)*. 387 | 388 | Heuristic: If *t* dominates *u*, and *t* is a valid state, then *u* 389 | does not generate the prettiest output. The idea is the following: if 390 | *u* is at a higher indentation level, it has less space to print the 391 | rest of the document (remember that indentation is always 392 | positive). Therefore, if it is also late in the production of tokens, 393 | there is no hope for *u* to catch up with *t*. There are some 394 | pathological cases where things do not go as I outline above, however 395 | they are pretty rare: they never occured in the examples that I've 396 | tried[^1]. 397 | 398 | Consequently, if there is a finite number *l* of indentation levels 399 | (traditionally *l=79*), then we have only to consider at worst *l* 400 | solutions after each line break. There is no exponential blow up. 401 | 402 | For completeness, here is the code implementing the above idea. 403 | 404 | ``` {.example} 405 | type Docs = [(Int,Doc)] 406 | data Process = Process {curIndent :: Int -- current indentation 407 | ,progress :: Int 408 | ,tokens :: [String] -- tokens produced, in reverse order 409 | ,rest :: Docs -- rest of the input document to process 410 | } 411 | measure :: Process -> (Int, Int) 412 | measure Process{..} = (curIndent, negate progress) 413 | 414 | filtering :: [Process] -> [Process] 415 | filtering (x:y:xs) | progress x >= progress y = filtering (x:xs) 416 | | otherwise = x:filtering (y:xs) 417 | filtering xs = xs 418 | 419 | renderFast :: Int -> Doc -> String 420 | renderFast w doc = concat $ reverse $ loop [Process 0 0 [] $ [(0,doc)]] 421 | where 422 | loop ps = case dones of 423 | (done:_) -> done 424 | [] -> case conts of 425 | (_:_) -> loop $ filtering $ sortBy (compare `on` measure) $ conts 426 | [] -> error "overflow" 427 | where 428 | ps' = concatMap (\Process{..} -> rall progress tokens curIndent rest) ps 429 | (dones,conts) = partitionEithers ps' 430 | 431 | rall :: Int -> [String] -> Int -> Docs -> [Either [String] Process] 432 | rall p ts k ds0 | k > w = [] 433 | rall p ts k ds0 = case ds0 of 434 | [] -> [Left ts] -- Done! 435 | (i,d):ds -> case d of 436 | Nil -> rall p ts k ds 437 | Text s -> rall (p+1) (s:ts) (k+length s) ds 438 | Spacing s -> rall (p ) (s:ts) (k+length s) ds 439 | Line -> [Right $ Process i p (('\n':replicate i ' '):ts) ds] 440 | x :<> y -> rall p ts k ((i,x):(i,y):ds) 441 | Nest j x -> rall p ts k ((i+j,x):ds) 442 | x :<|> y -> rall p ts k ((i,x):ds) ++ rall p ts k ((i,y):ds) 443 | Align x -> rall p ts k ((k,x):ds) 444 | ``` 445 | 446 | Coda 447 | ==== 448 | 449 | The above has been inspired by two implementations of pretty printers 450 | that I've made. The first one is a regular pretty printing library, 451 | [available on hackage](https://hackage.haskell.org/package/pretty-compact) 452 | which is a drop-in replacement for the `print-wl` package, except for 453 | a few unsafe functions which I've hidden. It implements exaclty the above idea, 454 | but handles overflow satisfactorily. 455 | 456 | The second one is part of the 457 | [marxup](https://hackage.haskell.org/package/marxup) package, which is 458 | a Haskell layer on top of the Latex document-preparation system. To 459 | see an example of a paper typeset with this technology, follow 460 | [this link](http://www.cse.chalmers.se/~bernardy/controlled-array-fusion.pdf). 461 | 462 | Happy pretty printing! 463 | 464 | [^1]: I originally thought this heurisitic to be a theorem, as shown 465 | in earlier versions of this document. 466 | 467 | 469 | -------------------------------------------------------------------------------- /paper/pretty-paper.cabal: -------------------------------------------------------------------------------- 1 | name: pretty-paper 2 | version: 1.0 3 | synopsis: Pretty-printing library 4 | description: 5 | This package contains a pretty-printing library, a set of API's 6 | that provides a way to easily print out text in a consistent 7 | format of your choosing. This is useful for compilers and related 8 | tools. 9 | . 10 | This library produces more compact outputs than both 11 | Wadler-Leijen or Hughes-PJ algorithms, at the expense of computational ressources. 12 | The core API is based on Hughes-PJ, but some combinators of the Leijen API are implemented as well. 13 | category: Text 14 | maintainer: Jean-Philippe Bernardy 15 | build-type: Simple 16 | Cabal-Version: >= 1.8 17 | 18 | Library 19 | exposed-modules: PM 20 | build-depends: 21 | base, 22 | marxup, 23 | lens, 24 | criterion, 25 | lp-diagrams, 26 | gasp, 27 | statistics, 28 | random 29 | hs-source-dirs: lib 30 | 31 | executable paper 32 | ghc-options: -O0 33 | build-depends: 34 | pretty-paper, 35 | base, 36 | marxup, 37 | lens, 38 | criterion, 39 | lp-diagrams, 40 | gasp, 41 | statistics, 42 | random 43 | main-is: 44 | Paper.hs 45 | 46 | executable bench 47 | build-depends: 48 | pretty-paper, 49 | base, 50 | lens, 51 | criterion, 52 | gasp, 53 | statistics, 54 | random, 55 | aeson, 56 | pretty, 57 | wl-pprint, 58 | pretty-compact, 59 | bytestring, 60 | attoparsec, 61 | unordered-containers, 62 | text, 63 | xml 64 | -- other-modules: 65 | -- BenchmarkLibs BenchmarksXML 66 | main-is: 67 | Benchmarks.hs 68 | 69 | 70 | -------------------------------------------------------------------------------- /pretty-compact.cabal: -------------------------------------------------------------------------------- 1 | Cabal-Version: 3.4 2 | name: pretty-compact 3 | version: 3.1 4 | synopsis: Pretty-printing library 5 | description: 6 | This package contains a pretty-printing library, a set of API's 7 | that provides a way to easily print out text in a consistent 8 | format of your choosing. This is useful for compilers and related 9 | tools. 10 | . 11 | This library produces more compact outputs than both 12 | Wadler-Leijen or Hughes-PJ algorithms, at the expense of computational ressources. 13 | The core API is based on Hughes-PJ, but some combinators of the Leijen API are implemented as well. 14 | license: LGPL-3.0-only 15 | license-file: LICENSE 16 | category: Text 17 | maintainer: Jean-Philippe Bernardy 18 | build-type: Simple 19 | 20 | source-repository head 21 | type: git 22 | location: http://github.com/jyp/prettiest.git 23 | 24 | 25 | Library 26 | exposed-modules: 27 | Text.PrettyPrint.Compact 28 | Text.PrettyPrint.Compact.Core 29 | build-depends: 30 | base >= 4.6 && < 5, 31 | base-compat >= 0.9.3 && <666, 32 | containers >= 0 && <666 33 | 34 | if !impl(ghc >= 8.0) 35 | build-depends: semigroups >= 0 && <666 36 | 37 | other-extensions: 38 | LambdaCase 39 | 40 | benchmark pretty-comparison 41 | type: exitcode-stdio-1.0 42 | hs-source-dirs: bench 43 | main-is: Benchmark.hs 44 | build-depends: 45 | aeson, 46 | base, 47 | base-compat, 48 | bytestring, 49 | criterion, 50 | deepseq, 51 | pretty, 52 | pretty-compact, 53 | text, 54 | unordered-containers, 55 | wl-pprint 56 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md 2 | 3 | # Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) 4 | resolver: lts-7.1 5 | 6 | # Local packages, usually specified by relative directory name 7 | packages: 8 | - '.' 9 | - paper/ 10 | - ../MarXup 11 | - ../lp-diagrams 12 | - ../gasp 13 | 14 | # Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) 15 | extra-deps: 16 | - labeled-tree-1.0.0.0 17 | - parsek-1.0.1.3 18 | 19 | # Override default flag values for local packages and extra-deps 20 | flags: {} 21 | 22 | # Extra package databases containing global packages 23 | extra-package-dbs: [] 24 | 25 | # Control whether we use the GHC we find on the path 26 | # system-ghc: true 27 | 28 | # Require a specific version of stack, using version ranges 29 | # require-stack-version: -any # Default 30 | # require-stack-version: >= 1.0.0 31 | 32 | # Override the architecture used by stack, especially useful on Windows 33 | # arch: i386 34 | # arch: x86_64 35 | 36 | # Extra directories used by stack for building 37 | # extra-include-dirs: [/path/to/dir] 38 | # extra-lib-dirs: [/path/to/dir] 39 | -------------------------------------------------------------------------------- /styx.yaml: -------------------------------------------------------------------------------- 1 | local-packages: 2 | pretty-compact: 3 | location: . 4 | pretty-paper: 5 | location: ./paper 6 | # marxup: 7 | # location: ../MarXup 8 | # lp-diagrams: 9 | # location: ../lp-diagrams 10 | 11 | nixpkgs: 12 | commit: ec9a23332f06eca2996b15dfd83abfd54a27437a 13 | sha256: 09d225y0a4ldx08b5rfhy7jk4qp0nj4q7xsjb49hvb5an79xmgdl 14 | 15 | source-deps: 16 | lp-diagrams: 17 | location: https://github.com/jyp/lp-diagrams.git 18 | revision: cc9a31305421f6bc72cc1f107f1270bd178c78a1 19 | gasp: 20 | location: https://github.com/jyp/gasp.git 21 | marxup: 22 | location: https://github.com/jyp/MarXup 23 | revision: 3a4ece072123a3e98a6e9178522c53c24422b0df 24 | 25 | nix-deps: 26 | - criterion 27 | - gasp 28 | - lens 29 | - statistics 30 | 31 | non-haskell-deps: 32 | - z3 33 | - texlive.combine { 34 | inherit (texlive) 35 | algorithm2e 36 | biblatex 37 | boondox 38 | cmll 39 | collection-fontsrecommended 40 | comment 41 | environ 42 | fontaxes 43 | inconsolata 44 | kastrup 45 | libertine 46 | listings 47 | lm 48 | logreq 49 | mweights 50 | ncclatex 51 | ncctools 52 | newtx 53 | newtxsf 54 | newtxtt 55 | newunicodechar 56 | prftree 57 | relsize 58 | scheme-small wrapfig marvosym wasysym 59 | stmaryrd lazylist polytable 60 | todonotes 61 | totpages 62 | trimspaces 63 | wasy cm-super unicode-math filehook lm-math capt-of 64 | xargs 65 | xstring ucharcat; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /talk/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | *.tex -------------------------------------------------------------------------------- /talk/Balanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyp/prettiest/71fe5b4a88154b9d70e7053a330ad29f03b51d94/talk/Balanced.png -------------------------------------------------------------------------------- /talk/HCat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyp/prettiest/71fe5b4a88154b9d70e7053a330ad29f03b51d94/talk/HCat.png -------------------------------------------------------------------------------- /talk/Outline.org: -------------------------------------------------------------------------------- 1 | #+TITLE: A pretty but not greedy printer (pearl) 2 | #+DATE: September 4th 2017 3 | #+AUTHOR: Jean-Philippe Bernardy, University of Gothenburg 4 | 5 | * Intro 6 | 7 | - Pretty printing (and typesetting in general) is hard! 8 | 9 | - chaotic: if you change something towards the beginning it may have 10 | non-linear effects way down the document 11 | 12 | - critically sensitive to small changes: every saved line counts 13 | 14 | * Running Example: SExpr 15 | Assume that an s-expr can be pretty-printed either horizontally or 16 | vertically: 17 | 18 | #+BEGIN_EXAMPLE 19 | (a b c d) 20 | #+END_EXAMPLE 21 | or 22 | 23 | #+BEGIN_EXAMPLE 24 | (a 25 | b 26 | c 27 | d) 28 | #+END_EXAMPLE 29 | Recall 30 | #+BEGIN_SRC haskell 31 | data SExpr = SExpr [SExpr] | Atom String 32 | 33 | #+END_SRC 34 | 35 | * State of the pop-art: Hughes 36 | 37 | - Greedily fit stuff on the line 38 | 39 | #+BEGIN_EXAMPLE 40 | 12345678901234567890 41 | 42 | ((abcde ((a b c d) 43 | (a b c d) 44 | (a b c d) 45 | (a b c d) 46 | (a b c d))) 47 | (abcdefgh ((a 48 | b 49 | c 50 | d) 51 | (a 52 | b 53 | c 54 | d) 55 | (a 56 | b 57 | c 58 | d) 59 | (a 60 | b 61 | c 62 | d) 63 | (a 64 | b 65 | c 66 | d)))) 67 | #+END_EXAMPLE 68 | 69 | * State of the pop-art: Wadler 70 | 71 | #+BEGIN_EXAMPLE 72 | 12345678901234567890 73 | 74 | ((abcde 75 | ((a b c d) 76 | (a b c d) 77 | (a b c d) 78 | (a b c d) 79 | (a b c d))) 80 | (abcdefgh 81 | ((a b c d) 82 | (a b c d) 83 | (a b c d) 84 | (a b c d) 85 | (a b c d)))) 86 | #+END_EXAMPLE 87 | - will waste a line here and there 88 | - lack of "hang" feature 89 | 90 | * I am not satisfied 91 | What I want: 92 | 93 | #+BEGIN_EXAMPLE 94 | ((abcde ((a b c d) 95 | (a b c d) 96 | (a b c d) 97 | (a b c d) 98 | (a b c d))) 99 | (abcdefgh 100 | ((a b c d) 101 | (a b c d) 102 | (a b c d) 103 | (a b c d) 104 | (a b c d)))) 105 | #+END_EXAMPLE 106 | 107 | * Laws of Pretty-Printing (paraphrased) 108 | 109 | In general, this is what I want: 110 | 111 | 1. A pretty-printer shall not print beyond the right margin 112 | 2. A pretty-printer shall reveal the structure of the input 113 | 3. A pretty-printer shall use as few lines as possible 114 | 115 | - Hughes breaks 3 116 | - Wadler breaks 2 (cannot specify "to the right of" in general) 117 | - I am trading performance for abiding to the laws. 118 | (Greedy cannot be pretty.) 119 | 120 | * What does rule 2 really mean? 121 | 122 | - Recall rule 2: "A pretty-printer shall reveal the structure of the 123 | input" 124 | 125 | - The user must specify a mapping from input to 126 | layout which reveals the structure 127 | 128 | - The API to describe pretty layouts: 129 | #+BEGIN_SRC haskell 130 | class Layout l where 131 | text :: String -> l 132 | (<>) :: l -> l -> l 133 | flush :: l -> l 134 | (<|>) :: l -> l -> l 135 | #+END_SRC 136 | 137 | * Example: 138 | #+BEGIN_SRC haskell 139 | pretty :: Doc d => SExpr -> d 140 | pretty (Atom s) = text "(" <> 141 | (sep $ map pretty xs) <> 142 | text ")" 143 | 144 | sep :: Doc d => [d] -> d 145 | sep [] = empty 146 | sep xs = hsep xs <|> vcat xs 147 | 148 | hsep,vcat :: Doc d => [d] -> d 149 | vcat = foldDoc ($$) 150 | hsep = foldDoc (<+>) 151 | 152 | foldDoc :: Doc d => (d -> d -> d) -> [d] -> d 153 | foldDoc _ [] = empty 154 | foldDoc _ [x] = x 155 | foldDoc f (x:xs) = f x (foldDoc f xs) 156 | #+END_SRC 157 | 158 | * Semantics (1a): Roughly following Hughes' 159 | #+BEGIN_SRC haskell 160 | type L = [String] -- non empty. 161 | 162 | instance Layout L where 163 | render :: L -> String 164 | render = intercalate "\n" 165 | text :: String -> L 166 | text s = [s] 167 | (<>) :: L -> L -> L 168 | xs <> (y:ys) = xs0 ++ [x ++ y] ++ map (indent ++) ys 169 | where xs0 = init xs 170 | x :: String 171 | x = last xs 172 | n = length x 173 | indent = replicate n ' ' 174 | flush :: L -> L 175 | flush xs = xs ++ [""] 176 | 177 | xs $$ ys = flush xs <> ys 178 | #+END_SRC 179 | 180 | * Semantics (1b): Horizontal composition 181 | 182 | #+BEGIN_EXAMPLE 183 | xxxxxxxxxxxxxxxxxxxx 184 | xxxxxxxxxxxxx 185 | xxxxxxxxxxxxxxxxxxxxxx 186 | xxxxxxyyyyyyyyyyyyyy 187 | yyyyyyyyyy 188 | yyyyyyyyyyyyyyyyyy 189 | yyyyyyyyy 190 | #+END_EXAMPLE 191 | 192 | 193 | * Semantics (2): Disjunction 194 | 195 | By idiomatic distribution over sets 196 | 197 | #+BEGIN_SRC haskell 198 | instance Layout [L] where 199 | text = pure . text 200 | flush = fmap flush 201 | xs <> ys = (<>) <$> xs <*> ys 202 | xs <|> ys = (xs ++ ys) 203 | #+END_SRC 204 | 205 | - The above is an executable specification. 206 | 207 | - It has unacceptable performance: every choice induces two layouts. 208 | Printing any document with >20 disjunctions is unrealistic. 209 | 210 | * Making it fast (1b): consider the useful parts only 211 | 212 | The choice of layout depends only on 3 parameters: width, height the 213 | width of the last line. 214 | 215 | #+BEGIN_EXAMPLE 216 | width 217 | <----------------> 218 | xxxxxxxxxxxxxxxxxx ^ 219 | xxxxxxxxxxxxxxxxxx | height 220 | xxxxxxxxxxxxxxxxxx v 221 | xxxxxxx 222 | <-----> 223 | last width 224 | #+END_EXAMPLE 225 | 226 | * Making it fast (1b): consider the useful parts only 227 | #+NAME: fig:cat 228 | #+CAPTION: Concatenation 229 | #+ATTR_ORG: :width 600 230 | [[file:HCat.png]] 231 | 232 | * Making it fast (2): discard dominated results 233 | 234 | /a/ dominates /b/ iff. /a/ is smaller than /b/ in all three dimensions 235 | 236 | If /a/ dominates /b/, then for any context /ctx/, 237 | 238 | /ctx a/ dominates /ctx b/ 239 | 240 | So, at any point we can discard all dominated layouts from the set of 241 | possible layouts. 242 | 243 | (proof in the paper) 244 | 245 | * Experimental Results (asymptotic, balanced sexprs) 246 | #+NAME: fig:balanced 247 | #+CAPTION: Balanced tree 248 | #+ATTR_ORG: :width 600 249 | [[file:Balanced.png]] 250 | 251 | * Experimental Results (asymptotic, random sexprs) 252 | #+NAME: fig:random 253 | #+CAPTION: Balanced tree 254 | #+ATTR_ORG: :width 600 255 | [[file:Random.png]] 256 | 257 | * Experimental Results (absolute) 258 | 259 | Render time in seconds: 260 | 261 | | Input | Mine | Wadler-Leijen | Hughes-PJ | 262 | |------------+-------+---------------+-----------| 263 | | JSON 1k | 9.7 | 1.5 | 3.0 | 264 | | JSON 10k | 145.5 | 14.8 | 30.0 | 265 | | XML 1k | 20.0 | 3.2 | 11.9 | 266 | | XML 10k | 245.0 | 36.1 | 192.0 | 267 | 268 | * Conclusion 269 | 270 | - We have defined pretty printing (3 principles) 271 | - They are not compatible with a greedy approach 272 | - But we can still make a fast printer 273 | - And even write it in pearl style! 274 | 275 | - This is an attempt to do a more realistic FP pearl: bridge the gap 276 | between what actually goes on in programming (informal spec, 277 | graphical models, worry about difficult parts only) and program 278 | calculation style found in classical pearls 279 | 280 | - In the paper: sound reasoning steps and lovely typesetting. 281 | 282 | * LocalWords 283 | 284 | # LocalWords: SExpr expr SRC abcde abcdefgh sep xs hsep vcat ys iff 285 | # LocalWords: foldDoc intercalate init xxxxxxxxxxxxxxxxxxxx fmap PJ 286 | # LocalWords: xxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyy ATTR 287 | # LocalWords: xxxxxxyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyy yyyyyyyyy ctx 288 | # LocalWords: disjunctions xxxxxxxxxxxxxxxxxx xxxxxxx sexprs Leijen 289 | # LocalWords: JSON FP 290 | -------------------------------------------------------------------------------- /talk/Random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyp/prettiest/71fe5b4a88154b9d70e7053a330ad29f03b51d94/talk/Random.png --------------------------------------------------------------------------------