Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lifted Pretty classes #40

Open
wants to merge 57 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
5595ce0
Copy in the Data.Functor.Classes.Pretty module.
robrix Aug 22, 2017
d3a44cf
Define generically-derivable Pretty1 instances.
robrix Aug 22, 2017
00f7bb3
Give a default implementation of liftPretty for Generic1 types.
robrix Aug 22, 2017
c51c52b
Define a Pretty1 instance for NonEmpty.
robrix Aug 22, 2017
8082325
Define a Pretty1 instance for Maybe.
robrix Aug 22, 2017
2247b13
:fire: Pretty1Of & Pretty2Of.
robrix Aug 23, 2017
96e21f9
Define Pretty1 & Pretty2 in Data.Text.PrettyPrint.Doc.Internal.
robrix Aug 23, 2017
680d6e2
:memo: Pretty1 & Pretty2.
robrix Aug 23, 2017
f8e3343
:fire: liftPrettyList/liftPrettyList2.
robrix Aug 23, 2017
3f65702
:fire: redundant spacing.
robrix Aug 23, 2017
73dd98c
Reformat liftPretty/liftPretty2.
robrix Aug 23, 2017
6d8d0af
Add newlines after class declarations.
robrix Aug 23, 2017
7cafb33
:memo: liftPretty.
robrix Aug 23, 2017
9579404
:fire: Data.Functor.Classes.Pretty.
robrix Aug 23, 2017
add8798
Define a bunch of Pretty1 instances.
robrix Aug 23, 2017
ba7ba56
:memo: Pretty1 [].
robrix Aug 23, 2017
146ab16
:memo: Pretty1 Maybe.
robrix Aug 23, 2017
12ebec7
Better :memo: for liftPretty.
robrix Aug 23, 2017
8f3c306
Define a Pretty2 instance for pairs.
robrix Aug 23, 2017
223b7af
:memo: Pretty1 ((,) a).
robrix Aug 23, 2017
6280232
Include the list parameter in the :memo:s.
robrix Aug 23, 2017
00612b7
:memo: liftPretty2.
robrix Aug 23, 2017
5acdc82
Define a Pretty instance for Either.
robrix Aug 23, 2017
df98809
:memo: Pretty (Either a b).
robrix Aug 23, 2017
4c42bd0
Mirror the Pretty instance for Maybe.
robrix Aug 23, 2017
fce219b
:memo: why Pretty1.
robrix Aug 23, 2017
85d57e4
:memo: why Pretty2.
robrix Aug 23, 2017
9d2f6a8
Better :memo: for Pretty1 Maybe.
robrix Aug 23, 2017
943163a
Define Pretty1 & Pretty2 instances for Either.
robrix Aug 23, 2017
03bd952
:memo: Pretty2 Either.
robrix Aug 23, 2017
566b0dc
:memo: Pretty1 (Either a).
robrix Aug 23, 2017
2fa9adc
Better docs for Pretty1/Pretty2.
robrix Aug 23, 2017
21a3c33
Correct the examples for Pretty2 Either.
robrix Aug 23, 2017
605f59a
Match the formatting style of e.g. 'nest'.
robrix Aug 23, 2017
ad6f171
:memo: the arguments to liftPretty.
robrix Aug 23, 2017
07986b7
Indent, don’t align.
robrix Aug 23, 2017
1da038b
:memo: the arguments to liftPretty2.
robrix Aug 23, 2017
f91663a
Disambiguate the types for the doctests.
robrix Aug 23, 2017
ee66301
Fix the doctests for Pretty (Either a b).
robrix Aug 23, 2017
ec0e482
Export Pretty1 & Pretty2.
robrix Aug 23, 2017
fc5efc1
Reformat the doctests for the Pretty2 instance for Either.
robrix Aug 26, 2017
a60ab84
Give laws relating Pretty2, Pretty1, & Pretty.
robrix Aug 26, 2017
d6c2166
Better formatting of the laws.
robrix Aug 26, 2017
2441649
Fix the doctests.
robrix Aug 26, 2017
2284f72
:fire: the list parameters to liftPretty & liftPretty2.
robrix Oct 11, 2017
d9425eb
Briefer docs for liftPretty2.
robrix Oct 11, 2017
5b9f66b
Correct the law for Pretty1.
robrix Oct 11, 2017
4dced20
Note the difference in behaviour between Pretty1 and Pretty for [].
robrix Oct 11, 2017
31cf99b
Cast for the doctest.
robrix Oct 11, 2017
03a1ae2
Clarify the relationship between liftPretty2, liftPretty, and pretty.
robrix Oct 11, 2017
a1d6c13
Dedent the param docs.
robrix Oct 11, 2017
234154c
Better docs for liftPretty.
robrix Oct 11, 2017
d80a5e5
Docs for liftPretty2.
robrix Oct 11, 2017
821a8f5
Use a longer example to illustrate Pretty1/Pretty for recursive types.
robrix Oct 11, 2017
12457a8
Spacing.
robrix Oct 11, 2017
dba8a03
Split up the instances over multiple lines.
robrix Oct 11, 2017
b36e306
:fire: an exclamation point.
robrix Oct 11, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions prettyprinter/src/Data/Text/Prettyprint/Doc.hs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ module Data.Text.Prettyprint.Doc (
PageWidth(..), LayoutOptions(..), defaultLayoutOptions,
layoutPretty, layoutCompact, layoutSmart,

-- * Lifted classes.
Pretty1(..),
Pretty2(..),

-- * Migration guide
--
-- $migration
Expand Down
97 changes: 97 additions & 0 deletions prettyprinter/src/Data/Text/Prettyprint/Doc/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,15 @@ instance Pretty a => Pretty (Maybe a) where
pretty = maybe mempty pretty
prettyList = prettyList . catMaybes

-- | Print 'Left' and 'Right' contents.
--
-- >>> pretty (Left True :: Either Bool Bool)
-- True
-- >>> pretty (Right True :: Either Bool Bool)
-- True
instance (Pretty a, Pretty b) => Pretty (Either a b) where
pretty = either pretty pretty

-- | Automatically converts all newlines to @'line'@.
--
-- >>> pretty ("hello\nworld" :: Text)
Expand All @@ -352,7 +361,95 @@ instance Pretty Lazy.Text where pretty = pretty . Lazy.toStrict
-- []
instance Pretty Void where pretty = absurd

-- | Overloaded conversion to 'Doc', lifted to unary type constructors.
--
-- This is most useful for:
-- 1. defining 'Pretty' instances for recursive types,
-- 2. defining 'Pretty' instances for type constructors without 'Functor'
-- instances, and
-- 3. efficiently pretty-printing type constructors with 'Functor' instances
-- whose 'fmap' traverses the whole structure.
--
-- Laws:
--
-- 1. output should be pretty. :-)
Copy link
Owner

Choose a reason for hiding this comment

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

I think there should be an example for an instance definition here, for example the [] one. And the law joke doesn’t need to be repeated again ;-)

I think the key to the Pretty1 documentation should be showing how it is useful without going into too much detail. Like »here we define Pretty without using Pretty1, see how Pretty1 makes our lives much easier?«

class Pretty1 f where

-- | >>> liftPretty (parens . pretty) (list . map (parens . pretty)) (Just "hello")
-- (hello)
liftPretty
:: (a -> Doc ann) -- ^ A function to print a single value.
-> ([a] -> Doc ann) -- ^ A function to print a list. Used for [].
Copy link
Owner

Choose a reason for hiding this comment

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

Is this really necessary? It clutters the definitions quite a bit, but it’s usually just list . map pretty.

Copy link
Author

Choose a reason for hiding this comment

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

It’s necessary for the [] definition to work as expected for Char, unfortunately.

Copy link
Owner

Choose a reason for hiding this comment

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

Arrr, I see.

Actually, this brings up a new law: Pretty1 f and Pretty (f a) should result in identical behavior! :-)

-> f a
-> Doc ann
Copy link
Author

Choose a reason for hiding this comment

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

It turns out that there’s nothing gained by providing liftPrettyList; the list-printing parameter suffices to pretty-print [Char] correctly using liftPretty:

λ: liftPretty pretty prettyList "hello"
hello

liftPrettyList & liftPrettyList2 can make some situations involving nested Pretty1 instances a little more convenient, but as they’re essentially always given the default definitions (as follows), I have omitted them to keep surface area down.

liftPrettyList pretty' prettyList' = list . map (liftPretty pretty' prettyList')


-- | >>> liftPretty (parens . pretty) (list . map (parens . pretty)) [1,2,3]
-- [(1), (2), (3)]
instance Pretty1 [] where
liftPretty _ prettyList' = prettyList'

instance Pretty1 NonEmpty where
liftPretty _ prettyList' (x:|xs) = prettyList' (x:xs)

-- | Ignore 'Nothing's, print 'Just' contents with the supplied function.
--
-- >>> liftPretty (parens . pretty) (list . map (parens . pretty)) (Just True)
-- (True)
-- >>> braces (liftPretty (parens . pretty) (list . map (parens . pretty)) (Nothing :: Maybe Bool))
-- {}
instance Pretty1 Maybe where
liftPretty prettyJust _ = maybe mempty prettyJust

-- | Print 'Left' contents with 'pretty', and 'Right' contents with the supplied
-- function.
--
-- >>> liftPretty (parens . pretty) (list . map (parens . pretty)) (Left True :: Either Bool Bool)
-- True
-- >>> liftPretty (parens . pretty) (list . map (parens . pretty)) (Right True :: Either Bool Bool)
-- (True)
instance Pretty a => Pretty1 (Either a) where
liftPretty prettyRight _ = either pretty prettyRight

-- | >>> liftPretty (parens . pretty) (list . map (parens . pretty)) (123, "hello")
-- (123, (hello))
instance Pretty a => Pretty1 ((,) a) where
liftPretty pretty2 _ (x1, x2) = tupled [pretty x1, pretty2 x2]

-- | Overloaded conversion to 'Doc', lifted to binary type constructors.
--
-- This is most useful for:
-- 1. defining 'Pretty' instances for recursive types,
-- 2. defining 'Pretty' instances for type constructors without 'Functor'
-- instances, and
-- 3. efficiently pretty-printing type constructors with 'Functor' instances
-- whose 'fmap' traverses the whole structure.
--
-- Laws:
--
-- 1. output should be pretty. :-)
class Pretty2 f where

liftPretty2
:: (a -> Doc ann) -- ^ A function to print a single value of the first parameter.
-> ([a] -> Doc ann) -- ^ A function to print a list of the first parameter.
-> (b -> Doc ann) -- ^ A function to print a single value of the second parameter.
-> ([b] -> Doc ann) -- ^ A function to print a list of the second parameter.
-> f a b
-> Doc ann

-- | Print 'Left' and 'Right' contents with the supplied functions.
--
-- >>> liftPretty2 (parens . pretty) (list . map (parens . pretty)) (parens . pretty) (list . map (parens . pretty)) (Left True :: Either Bool Bool)
-- (True)
-- >>> liftPretty2 (parens . pretty) (list . map (parens . pretty)) (parens . pretty) (list . map (parens . pretty)) (Right True :: Either Bool Bool)
-- (True)
Copy link
Author

Choose a reason for hiding this comment

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

These doctests are unfortunately quite lengthy, but it’s a little difficult providing good concise examples for nonrecursive types.

Nevertheless, I’ve found these instances to be quite useful in practice, so I felt it was worth providing them, lengthy doctests and all.

Copy link
Owner

Choose a reason for hiding this comment

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

I think these tests would be nicer if they weren’t on a single line, but with let helper definitions with good names.

instance Pretty2 Either where
liftPretty2 prettyLeft _ prettyRight _ = either prettyLeft prettyRight

-- | >>> liftPretty2 (parens . pretty) (list . map (parens . pretty)) (parens . pretty) (list . map (parens . pretty)) (123, "hello")
-- ((123), (hello))
instance Pretty2 (,) where
liftPretty2 pretty1 _ pretty2 _ (x1, x2) = tupled [pretty1 x1, pretty2 x2]

-- | @(unsafeTextWithoutNewlines s)@ contains the literal string @s@.
--
Expand Down