packages feed

sayable (empty) → 1.0.1.0

raw patch · 4 files changed

+703/−0 lines, 4 filesdep +basedep +bytestringdep +exceptions

Dependencies added: base, bytestring, exceptions, prettyprinter, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,9 @@+# Revision history for sayable++## 1.0.1.0 -- 2022-12-01++* Added Sayable instance for Char.++## 1.0.0.0 -- 2022-06-30++* First independent version.
+ LICENSE view
@@ -0,0 +1,15 @@+Copyright 2022, Galois Inc++Permission to use, copy, modify, and/or distribute this software for+any purpose with or without fee is hereby granted, provided that the+above copyright notice and this permission notice appear in all+copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE+AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER+TORTIOUS ACTION ARISING OUT OF OR IN CONNECTION WITH THE USE OR+PERFORMANCE OF THIS SOFTWARE.
+ Text/Sayable.hs view
@@ -0,0 +1,630 @@+{- |+Module: Text.Sayable++This module provides a set of data structures, classes, and operators that facilitate the construction of a Prettyprinter Doc object.++= Motivation++Standard prettyprinting is a monotonic conversion that does not allow for+customization for different uses or environments.  For example, when debugging,+full and explicit information about a structure should be generated, but for+checkpoint logging, a simple overview is usually more appropriate.++This library provides for an additional type parameter that can be used to+control the conversion to a suitably verbose Prettyprinter Doc representation.++This is also highly useful in conjunction with logging to generate successively+more verbose information as the logging verbosity increases.++= Usage++Typical usage is to create a sayable message using the operators defined here and+then extract Prettyprinter Doc from the saying and convert it to a printable+format (here, simply using @show@ for the default Prettyprinter rendering).++@+import qualified Prettyprinter as PP++foo :: Members '[ Logging SayMessage, Config ] r -> a -> b -> Eff r [b]+foo arg1 arg2 =+   do putStrLn $ show $ saying $ sayable @info "Entering foo with" &- arg1 &- "and" &- arg2+      rslt <- something arg1 arg2+      case rslt of+        Right vals ->+          do putStrLn $ show $ saying $ sayable @"verbose"+                 $ "Foo successfully returning" &% length vals &- "results:" &- vals+             return vals+        Left err ->+          do putStrLn $ show $ saying $ sayable @"error"+                 $ "Foo error (" &- arg1 &- PP.comma &- arg2 &- ") is" &- err+             throwError err+@++[Note: if viewing via Haddock HTML, the '@' in front of @"info"@,+@"verbose"@, and @"error"@ on the putStrLn lines above may not be+visible.]++There are three messages printed: one on entry and one on either the success or+failure paths.  Each message may have different levels of information reported for the various arguments.++== The 'saytag' type parameter++Each sayable message uses a 'TypeApplication' to specify a "saytag" which should+be used for controlling the rendering of that message (e.g. "info", "verbose", "error", etc.).++As a developer, it is encouraged to use whatever saytag makes sense relative to the current context and type of information being processed.++== Individual Arguments++The arguments passed to the sayable should be instances of the 'Sayable' class.+There are a number of standard instances of 'Sayable', but an instance can be+declared for any object that might be output.  The 'Sayable' class has two class+parameters: the second is object to be converted, and the first is the "saytag".+This allows different Sayable instances for an object to be used in different+saytag scenarios.  For example:++@+import Network.URL++instance Sayable "verbose" URL where+  sayable url =+    let newline = PP.line :: PP.Doc SayableAnn+        prettyShow x = PP.viaShow x :: PP.Doc SayableAnn+    in "URL {"+        &- "url_type=" &- prettyShow (url_type url) &- newline+        &- "url_path=" &- url_path url &- newline+        &- "url_params=" &* url_params url+        &- "}"+instance Sayable saytag URL where+  sayable = Sayable . PP.viaShow . exportURL+@++The above would cause a url emitted via a "verbose" saytag to be+expanded into a report on each individual field, whereas all other+saytags would simply output the 'exportURL' representation of the 'URL'.++>>> let host = Host (HTTP True) "github.com" Nothing+>>> url' = URL (Absolute host) "by/one"+>>> saying $ sayable @"verbose" url'+URL { url_type= Absolute (Host {protocol = HTTP True, host= "github.com", port= Nothing})+ url_path= by/one+ url_params= }+>>> saying @"info" $ sayable url'+https://github.com:442/by/one++There are some tricky elements to the above however; see "Unfortunate+Details" below.++Note that there are several pre-declared Sayable instances for common+datatypes for convenience.++== Operators++In the logging lines above, there are three operators used, each of+which starts with the '&' character:++  ['&-'] This is the standard operator that takes two Sayable+         arguments and converts them to their Sayable form, then+         combining them (with an intervening space).  This is the+         standard argument to use for building the output message from+         distict parts.++  ['&+'] This is a variation of the standard '&-' operator that has no+         intervening space between the two arguments that are+         converted to a Sayable form.++  ['&%'] This is a variation of the standard '&-' operator that only+         requires the second argument to be an instances of+         Prettyprinter.Pretty instead of an instance of 'Sayable',+         which can be convenient and avoids the need to define large+         numbers of 'Sayable' instances.++  ['&*'] This is a helper operator whose second argument is a+         'Foldable' series of 'Sayable' elements.  This will fold over+         the series, adding the 'Sayable' instance value for each+         element separated by commas.++  ['&+*'] This is similar to the '&*' helper, but it uses the first+          argument as the separator between the elements of the+          'Foldable' second argument (instead of the ", " default used+          by the '&*' helper).++  ['&?'] This is a helper operator whose second argument is a 'Maybe+         a' (where 'a' is a 'Showable').  This will emit the+         'Showable' of 'a' if the argument is a 'Just' value, or+         nothing (an empty Text Showable) if the argument is a+         'Nothing' value.++  ['&!'] This is a helper operator to apply a Prettyprinter+         transformation function (the first argument) to a 'Sayable'+         message (the second argument).++  ['&!*'] This helper operator is a combination of the '&!' operator+          and the '&*' operator: it applies the first argument (a+          @[PrettyPrinter.Doc ann] -> PrettyPrinter.Doc ann@ function)+          to the foldable collection represented by the second+          argument.++  ['&!+*'] This helper operator is a combination of the '&!' operator and the+           '&+*' operator (and is a trinary rather than a binary operator): it+           applies the first argument (a @[PrettyPrinter.Doc ann] ->+           PrettyPrinter.Doc ann@ function) to the foldable collection+           represented by the third argument, using the second argument to+           specify the separators between the elements.++== Convenience/other++  * This module also provides an instance to convert a Sayable back+    into a Prettyprinter.Pretty.++  * This module provides a helper function: 't'' which can be useful+    when 'OverloadedStrings' is active to designate its argument as+    being a Text string.++    If the following:++@+saying @"error" $ "This is an error:" &- err+@++    results in an error @Could not deduce (Data.String.IsString m0)+    arising from the literal '"This is an error:"'@ then this helper+    can fix that:++@+__ @"error" $ t'"This is an error:" &- err+@++  * This module provides a helper function: 'd'' which can be useful+    when creating a PP.Doc SayableAnn for inclusion into a 'Sayable'+    by fixing the 'ann' of 'PP.Doc ann' to 'SayableAnn'.++    Fixes the error:+@+    • Overlapping instances for Sayable saytag (PP.Doc ann1)+        arising from a use of ‘&-’+      Matching instances:+        instance [overlappable] Sayable tag (PP.Doc ann)+          -- Defined in ‘Taphos.Say’+        instance Sayable tag (PP.Doc SayableAnn)+          -- Defined in ‘Taphos.Say’+      (The choice depends on the instantiation of ‘saytag, ann1’+       To pick the first instance above, use IncoherentInstances+       when compiling the other instance declarations)+@++    This is similar to the '&%' operator except it takes a single+    argument rather than the two arguments passed to the operator.++  * The pattern of converting a saying into a String (e.g. for passing to+    putStrLn) is fairly common, so the simplistic operations of that is provided+    by the 'sez' function.+++== Generating final output++  The 'sayable' method of the 'Sayable' class generates instances of+  the 'Saying' data object.  The 'saying' function can be used to+  extract the 'Prettyprinter.Doc' from the 'Saying' object.  This Doc+  can then be converted to a 'Lumberjack.LogMessage' or to a plain+  Text format for display.++== Unfortunate Details++  The use of the Sayable class to translate individual objects is+  fairly straightforward, but the management of the phantom 'saytag'+  type parameter is a bit tricky.  As described above (with the+  Network.URL example), it's possible to provide different output+  generation by providing specialized instances for specific saytags.+  The determination of which instance GHC will use has some+  idiosyncrasies that make lead to unexpected instance selection when+  used transitively (viz.+  https://ghc.gitlab.haskell.org/ghc/doc/users_guide/extsinstances.html).++  For instance:++@+import Network.URL ( URL )+newtype Foo = Foo URL+data Bar a  = Bar String a++-- [previous instances for Sayable URL here...]++instance Sayable "loud" Foo where sayable (Foo url) = t'"{!" &- url &- t'"!}"+instance Sayable saytag Foo where sayable (Foo url) = sayable url++instance (Sayable saytag a) => Sayable saytag (Bar a) where+  sayable (Bar b a) = b &- t'"is" &- a++let host = Host (HTTP True) "github.com" Nothing+let url' = URL (Absolute host) "by/one"+let foo = Foo url'+let bar = Bar "bar" foo+@++  will generate:++>>> putStrLn $ sez @"info" $ t'"INFO:" &- bar &- "via" &- foo+INFO: bar is "https://github.com/by/one" via "https://github.com/by/one"+>>> putStrLn $ sez @"loud" $ t'"LOUD:" &- bar &- "via" &- foo+LOUD: bar is {! "https://github.com/by/one" !} via {! "https://github.com/by/one" !}++  which is expected.  However, if the calls to 'sez' are moved+  to a separate file from the instance declarations, the compilation+  error will be:++  @Overlapping instances for Sayable "loud" Foo arising from a use of &-@++  for the last (loud) line.  To resolve this, use OVERLAPPING and/or+  OVERLAPPABLE specifications on the instance declarations.  Usually+  it's sufficient (and easiest) to add the OVERLAPPABLE to the generic+  instance:++> instance Sayable "loud" Foo where sayable (Foo s) = t'"{!" &- s &- t'"!}"+> instance {-# OVERLAPPABLE #-} Sayable saytag Foo where sayable (Foo s) = sayable s++  [Note: if you are viewing the above via Haddock HTML, the second+  line has the @instance@ keyword, followed by an open comment+  directive (open curly brace, dash, hash) , @OVERLAPPABLE@ and a+  closing comment directive (hash, dash, close curly brace), followed+  by the @Sayable@ keyword, but that doesn't render under HTML Haddock+  (circa 2022).]++  There's another twist to this story though.  To observe this new+  twist, add a 'Baz' datastructure and its generic 'Sayable' instance:++  > data Baz = Baz Foo+  > instance Sayable saytag Baz where sayable (Baz a) = t'"BAZ :=" &- foo++  Now the following calls and corresponding output can be observed:++>>> putStrLn $ sez @"info" $ t'"INFO:" &- bar &- t'"and" &- baz+INFO: bar is "https://github.com/by/one" and BAZ := "https://github.com/by/one"+>>> putStrLn $ sez @"loud" $ t'"LOUD:" &- bar &- t'"and" &- baz+LOUD: bar is {! "https://github.com/by/one" !} and BAZ := "https://github.com/by/one"++  Notice how the @foo@ value in @bar@ changes when the '@"loud"' saytag is+  used, but the same @foo@ value in @baz@ does not change!++  The difference here is in the mechanism GHC uses to select instances+  (as described on the referenced link above).  In short, for @bar@, the+  generic 'Sayable' instance has a constraint for the inner element,+  which causes GHC to wait until the final use case to determine what+  the specific type parameters are; it sees the @"loud"@ @saytag@ value+  and selects the @"loud"@ 'Foo' 'Sayable' instance as the most specific.+  However, the @baz@ 'Sayable' instance does not have a constraint, so GHC+  takes the conservative approach and uses the most general instance,+  which means that it transitively selects the generic 'Foo' 'Sayable'+  instance instead of the @"loud"@ instance.++  There are two ways to fix this:++  1. Provide explicit @"loud"@ 'Sayable' instance for 'Baz'.  This is+     problematic, because this must be done for *each* saytag for+     which there is a variation and it must be done for *each* upper+     level 'Sayable' instance.++  2. Provide 'Sayable' constraints for each sub-element.  This generates+     larger type signatures, but is preferrable to solution 1 because+     it makes no assumptions about current or future saytags and+     variations.++     This 'Sayable' constraint was already present on the 'Bar'+     'Sayable' instance because of the parameterized type for 'Bar';+     the 'Baz' type has no type parameter, but a constraint can still+     be added for each interior type:++> instance Sayable saytag Foo => Sayable saytag Baz where+>   sayable (Baz a) = t'"BAZ :=" &- foo++  Using either of the above solutions, the new output is fully+  specialized as desired:++>>> putStrLn $ sez @"info" $ t'"INFO:" &- bar &- t'"and" &- baz+INFO: bar is "https://github.com/by/one" and BAZ := "https://github.com/by/one"+>>> putStrLn $ sez @"loud" $ t'"LOUD:" &- bar &- t'"and" &- baz+LOUD: bar is {! "https://github.com/by/one" !} and BAZ := {! "https://github.com/by/one" !}+++  The good news here is that the complexity is all handled at the+  Sayable instance definition and the client usage calls are all+  unaffected, regardless of which solution is chosen.++-}++{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Text.Sayable+  (+    Sayable(sayable)+  , Saying(Saying, saying)+  , SayMessage(SayMessage, sayMsg)+  , t'+  , d'+  , (&-)+  , (&+)+  , (&%)+  , (&*)+  , (&+*)+  , (&?)+  , (&!)+  , (&!*)+  , (&!+*)+  , SayableAnn(SayableAnn)+  , sez+  )+where++import qualified Control.Monad.Catch as X+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import qualified Data.Int as I+import           Data.Text ( Text, pack )+import qualified Data.Text.Encoding as TE+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TEL+import qualified Data.Word as W+import           GHC.Exts ( Proxy#, proxy# )+import           GHC.OverloadedLabels+import           GHC.TypeLits ( Symbol, KnownSymbol, symbolVal' )+import           Numeric.Natural ( Natural )+import           Prettyprinter ( (<+>) )+import qualified Prettyprinter as PP+++-- | The main class of things that can be passed to 'say'.  Arguments+-- provided to 'sayable' or 'sez' will be converted to the sayable form by+-- automatically applying the appropriate instance of this class.  The+-- default implementation is:+--+-- > sayable = Saying . Prettyprinter.pretty++class Sayable (tag :: Symbol) v where+  sayable :: v -> Saying tag++  default sayable :: PP.Pretty v => v -> Saying tag+  sayable = Saying . PP.pretty+++-- | The result of applying the sayable method of the Sayable class is+-- the Saying object.  This object is internal to the Say module and+-- is mostly used for subsequently combining with additional Saying+-- objects to produce the final Saying object that is converted to a+-- SayMessage for actual logging.  A Sayable supports a Semigroup+-- combinator to allow composition of messages.++newtype Saying (tag :: Symbol) = Saying { saying :: PP.Doc SayableAnn }++instance Semigroup (Saying tag) where+  Saying sm1 <> Saying sm2 = Saying $ sm1 <+> sm2++-- | This is the principle data type that carries 'say' messages to the output+-- conversion point.  This is generated internally by the 'saying' functions and+-- is usually not directly needed by client code.++newtype SayMessage = SayMessage { sayMsg :: PP.Doc SayableAnn }++-- | Inputs that are 'Sayable', i.e. that can be converted to a Saying++instance {-# OVERLAPPING #-} (tagA ~ tagB) => Sayable tagA (Saying tagB) where sayable = id+instance Sayable tag Text+instance Sayable tag String+instance Sayable tag Char+instance Sayable tag Bool+instance Sayable tag Int+instance Sayable tag Integer+instance Sayable tag I.Int32+instance Sayable tag I.Int64+instance Sayable tag W.Word8+instance Sayable tag W.Word16+instance Sayable tag W.Word32+instance Sayable tag W.Word64+instance Sayable tag Natural+instance Sayable tag TL.Text+instance Sayable tag BS.ByteString where sayable = sayable . TE.decodeUtf8+instance Sayable tag BSL.ByteString where sayable = sayable . TEL.decodeUtf8+instance Sayable tag X.SomeException where sayable = sayable . X.displayException+instance Sayable tag (PP.Doc SayableAnn) where sayable = Saying+instance {-# OVERLAPPABLE #-} Sayable tag (PP.Doc ann) where sayable = Saying . PP.unAnnotate++-- | A Saying can be converted back into a PP.Pretty instance+-- representation.  Just saying... :-)+--+-- This can be convenient to apply Prettyprinter formatting elements.+-- For example:+--+-- > instance Sayable saytag Foo where+-- >   sayable foo = sayable @saytag $ PP.group $ PP.pretty+-- >                 $ field1 foo &- sayable @saytag PP.line &- field2 foo+--+-- This uses Prettyprinter's 'group' and 'line' formatters to show the+-- two fields on the same line if they will fit, otherwise stacked on+-- top of each other.  Note that the second portion needs an explicit+-- 'TypeApplication' (applied here to the 'PP.line') because the+-- 'PP.group' and 'PP.pretty' functions do not propagate that outer+-- 'saytag' to the inner portion.++instance PP.Pretty (Saying tag) where pretty = PP.unAnnotate . saying+++-- | A helper operator allowing two Sayable items to be composed into+-- a Saying.  This is the most common operator used to construct+-- composite Sayable messages.  The two Sayable items are separated by+-- a space.+(&-) :: forall saytag m n . (Sayable saytag m, Sayable saytag n)+     => m -> n -> Saying saytag+m &- n = sayable m <> sayable n+infixl 1 &-++-- | A helper operator allowing two Sayable items to be composed into+-- a Saying by placing the two Sayable items immediately adjacent with+-- no intervening spaces.  This is the high-density version of the+-- more common '&-' operator.+(&+) :: forall saytag m n . (Sayable saytag m, Sayable saytag n)+     => m -> n -> Saying saytag+m &+ n = Saying $ (saying $ sayable @saytag m) <> (saying $ sayable @saytag n)+infixl 1 &+++-- | A helper operator allowing a Sayable item to be composed with a+-- Pretty item into a Saying.  This is infrequently used and primarily+-- allows the composition of a data object which has a "Prettyprinter"+-- instance but no 'Sayable' instance.+(&%) :: (Sayable tag m, PP.Pretty n) => m -> n -> Saying tag+m &% n = sayable m <> sayable (PP.pretty n :: PP.Doc SayableAnn)+infixl 1 &%++-- | A helper operator to /apply/ a "Prettyprinter" (@Doc ann -> Doc+-- ann@) function (the first argument) to the Sayable in the second+-- argument.  This is different from the '&%' operator in that the+-- former uses 'Prettyprinter.hsep' to join two independent+-- 'Prettyprinter.Doc' 'Saying' values, whereas this operator applies+-- a transformation (e.g. @Prettyprinter.annotate AnnValue@ or+-- @Prettyprinter.align . Prettyprinter.group@) to the+-- 'Prettyprinter.Doc' in the second 'Saying' argument.+(&!) :: forall tag m . Sayable tag m+     => (PP.Doc SayableAnn -> PP.Doc SayableAnn) -> m -> Saying tag+pf &! m = Saying $ pf $ saying $ sayable @tag m+infixl 2 &!++-- | A helper operator allowing a Sayable item to be composed with a+-- Foldable series of Sayable items.  This can be used when the second+-- argument is a List, Sequence, Set, etc. to add all elements of the+-- set (comma-separated).+--+-- Note: this instance makes it easy to output lists, Sequence,+-- NonEmpty.List, etc., but it can have undesireable effects for data+-- tructures whose Foldable (Functor) is irregular... for example,+-- folding over a tuple only returns the 'snd' value of a tuple.+-- Consider wrapping tuples in a newtype with an explicit Sayable to+-- avoid this.+(&*) :: forall tag m e t+        . (Sayable tag m, Sayable tag e, Foldable t) => m -> t e -> Saying tag+m &* l = let addElem e (s, Saying p) =+               (", ", Saying $ saying (sayable @tag e) <> s <> p)+         in sayable m <> (snd $ foldr addElem ("", Saying PP.emptyDoc) l)+infixl 1 &*+++-- | A helper operator that generates a sayable from a list of sayable+-- items, separated by the first sayable+(&+*) :: forall tag m e t+         . (Sayable tag m, Sayable tag e, Foldable t) => m -> t e -> Saying tag+m &+* l = let addElem e (s, Saying p) = (Just m,+                                         case s of+                                           Nothing -> sayable @tag e &+ p+                                           Just s' -> sayable @tag e &+ s' &+ p+                                        )+          in snd $ foldr addElem (Nothing, Saying PP.emptyDoc) l+infixl 2 &+*+++-- | A helper operator that applies the first argument which converts+-- an array of 'Prettyprinter.Doc ann' elements to a single+-- 'PrettyPrinter.Doc ann' element to the second argument, which is a+-- Foldable collection of 'Sayable' items.  This is essentially a+-- combination of the '&!' and '&*' operators.+(&!*) :: forall tag m t+         . (Sayable tag m, Foldable t)+      => ([PP.Doc SayableAnn] -> PP.Doc SayableAnn) -> t m -> Saying tag+pf &!* l = let addElem e (s, p) = (", ", saying (sayable @tag e) <> s : p)+           in Saying $ pf $ snd $ foldr addElem ("", []) l+infixl 2 &!*+++-- | A helper operator that applies the first argument which converts+-- an array of 'Prettyprinter.Doc ann' elements to a single+-- 'PrettyPrinter.Doc ann' element to the second argument, which is a+-- Foldable collection of 'Sayable' items.  This is essentially a+-- combination of the '&!' and '&+*' operators.+--+-- Unlike the other operators defined in this package, this is a trinary operator+-- rather than a binary operator.  Because function application (whitespace) is+-- the highest precedence, the last argument will typically need a preceeding $+-- to prevent applying the second argument to the third argument before applying+-- this operator.+--+-- > import qualified Prettyprinter as PP+-- >+-- > putStrLn $ sez @"info" $ PP.fillSep &!+* t'" and " $ ["one", "two", "three"]+-- one and two and three+--+(&!+*) :: forall tag m t b . (Sayable tag b, Sayable tag m, Foldable t)+       => ([PP.Doc SayableAnn] -> PP.Doc SayableAnn) -> b -> t m -> Saying tag+pf &!+* b = let addElem e (s, p) =+                  (Just b, (case s of+                               Nothing -> saying (sayable @tag e)+                               Just x -> saying (sayable @tag e &+ x)+                           ) : p)+            in Saying . pf . snd . foldr addElem (Nothing, [])+infixl 2 &!+*+++-- | A helper operator allowing a Sayable item to be wrapped in a+-- 'Maybe'.  This adds the 'Sayable' of the first argument to the+-- 'Sayable' of the second argument in the 'Just' case, or just emits+-- the 'Sayable' of the first argument if the second argument is+-- 'Nothing'.+(&?) :: forall tag m e+        . (Sayable tag m, Sayable tag e) => m -> Maybe e -> Saying tag+m &? Nothing = sayable m+m &? (Just a) = sayable m <> sayable a+infixl 1 &?++-- | A helper function to use when 'OverloadedStrings' is active to+-- identify the following quoted literal as a "Data.Text" object.+t' :: Text -> Text+t' = id+{-# INLINE t' #-}+++-- | A helper function to use when creating a PP.Doc SayableAnn data+-- object (i.e. fixing the 'ann' of 'Doc ann' to 'SayableAnn')+d' :: PP.Pretty n => n -> PP.Doc SayableAnn+d' = PP.pretty+++----------------------------------------------------------------------++-- | This is the default annotation type for the Saying module.  The+-- Prettyprinter reannotate operation can be used to change this annotation into+-- any other annotation type the client desires.+--+-- The SayableAnn is an instance of IsLabel, so if OverloadedLabels is enabled,+-- this can easily be specified:+--+-- @+-- import qualified Prettyprinter as PP+-- import Text.Sayable+--+-- putStrLn $ sez @"info" $ PP.annotate #myann $ "Hello" &- "world!"+-- @+--+-- Note however that labels cannot start with a capital letter.++data SayableAnn = SayableAnn Text++instance KnownSymbol ann => IsLabel (ann :: Symbol) SayableAnn where+  fromLabel = SayableAnn $ pack $ symbolVal' (proxy# :: Proxy# ann)+++----------------------------------------------------------------------++-- | This is a convenience function that can be used for simple conversions of a+-- Sayable to a String.++sez :: forall saytag a . Sayable saytag a => a -> String+sez = show . saying . sayable @saytag
+ sayable.cabal view
@@ -0,0 +1,49 @@+cabal-version:      2.4+name:               sayable+version:            1.0.1.0+synopsis: Data structures, classes and operators for constructing context-adjusted pretty output+description:+   .+   This package provides a set of data structures, classes and operators that+   facilitate the construction of a Prettyprinter Doc object.  The difference+   between this an Prettyprinter is:+   .+   * Additional "saytag" parameter that can be used to control the Doc rendering.+   .+   * Brevity of syntax (using operators) designed to enhance conversion of+     arguments and readability of messages and conversion+   +license:            ISC+license-file:       LICENSE+author:             Kevin Quick+maintainer:         kquick@galois.com+copyright:          Galois Inc., 2022+category:           Text+build-type:         Simple+extra-source-files: CHANGELOG.md++source-repository head+  type: git+  location: https://github.com/kquick/sayable++common bldspec+  ghc-options:  -Wall+                -Wcompat+                -Wincomplete-uni-patterns+                -Wsimplifiable-class-constraints+                -Wpartial-fields+                -fhide-source-paths+                -O2+                -flate-specialise+                -fspecialise-aggressively++library+    import:           bldspec+    hs-source-dirs:   .+    default-language: Haskell2010+    exposed-modules:  Text.Sayable+    build-depends:    base >= 4.10 && < 4.15+                    , exceptions+                    , bytestring+                    , text+                    , prettyprinter