├── .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.<> 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.<> 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
--------------------------------------------------------------------------------