|
| 1 | +# Eval plugin for the [Haskell Language Server](https://github.com/haskell/haskell-language-server#readme) |
| 2 | + |
| 3 | +The Eval plugin evaluates code inserted in comments. |
| 4 | + |
| 5 | +This is mainly useful to test and document functions and to quickly evaluate small expressions. |
| 6 | + |
| 7 | +Every line of code to be evaluated is introduced by __>>>__ |
| 8 | + |
| 9 | +A quick calculation: |
| 10 | + |
| 11 | +``` |
| 12 | +-- >>> 2**4.5/pi |
| 13 | +-- 7.202530529256849 |
| 14 | +``` |
| 15 | + |
| 16 | +A little test for the `double` function: |
| 17 | + |
| 18 | +``` |
| 19 | +{- | |
| 20 | +A doubling function. |
| 21 | +
|
| 22 | +>>> double 11 |
| 23 | +22 |
| 24 | +-} |
| 25 | +double = (2*) |
| 26 | +``` |
| 27 | + |
| 28 | +# Demo |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +# Test Structure |
| 33 | + |
| 34 | +A test is composed by a sequence of contiguous lines, the result of their evaluation is inserted after the test body: |
| 35 | + |
| 36 | +``` |
| 37 | +>>> "AB" ++ "CD" |
| 38 | +>>> "CD" ++ "AB" |
| 39 | +"ABCD" |
| 40 | +"CDAB" |
| 41 | +``` |
| 42 | + |
| 43 | +You execute a test by clicking on the _Evaluate_ code lens that appears above it (or _Refresh_, if the test has been run previously). |
| 44 | + |
| 45 | +All tests in the same comment block are executed together. |
| 46 | + |
| 47 | + |
| 48 | +Tests can appear in all kind of comments: |
| 49 | +* plain comments (both single and multi line) |
| 50 | +``` |
| 51 | +{- |
| 52 | +>>> "ab" ++ "c" |
| 53 | +"abc" |
| 54 | +-} |
| 55 | +
|
| 56 | +-- >>> "ab" ++ "c" |
| 57 | +-- "abc" |
| 58 | +``` |
| 59 | +* Haddock commands (both single and multi line, forward and backward) |
| 60 | +``` |
| 61 | +{- |
| 62 | +>>> "ab" ++ "c" |
| 63 | +"abc" |
| 64 | +-} |
| 65 | +
|
| 66 | +-- >>> "ab" ++ "c" |
| 67 | +-- "abc" |
| 68 | +
|
| 69 | +double a = a + a |
| 70 | +-- ^ A doubling function |
| 71 | +-- >>> double 11 |
| 72 | +-- 22 |
| 73 | +``` |
| 74 | + |
| 75 | +Both plain Haskell and Literate Haskell (Bird-style only) source files are supported. |
| 76 | + |
| 77 | +# Test Components |
| 78 | + |
| 79 | +In general, a test is a sequence of: |
| 80 | +* imports |
| 81 | +* directives |
| 82 | +* statements |
| 83 | +* expressions |
| 84 | +* properties |
| 85 | + |
| 86 | +in no particular order, with every line introduced by __>>>__ (or __prop>__ in the case of properties). |
| 87 | + |
| 88 | +### Imports |
| 89 | + |
| 90 | +``` |
| 91 | +>>> import Data.List |
| 92 | +>>> import GHC.TypeNats |
| 93 | +``` |
| 94 | + |
| 95 | +From any package in scope but currently NOT from modules in the same source directory. |
| 96 | + |
| 97 | +### Language Extensions |
| 98 | + |
| 99 | +``` |
| 100 | +>>> :set -XScopedTypeVariables -XStandaloneDeriving -XDataKinds -XTypeOperators -XExplicitNamespaces |
| 101 | +``` |
| 102 | + |
| 103 | +### Statements and Declarations |
| 104 | + |
| 105 | +Function declarations (optionally introduced by __let__): |
| 106 | + |
| 107 | +``` |
| 108 | +>>> let tuple x = (x,x) |
| 109 | +>>> let one=1;two=2 |
| 110 | +>>> triple x = (x,x,x) |
| 111 | +``` |
| 112 | + |
| 113 | +Any other declaration: |
| 114 | + |
| 115 | +``` |
| 116 | +>>> data TertiumDatur = Truly | Falsely | Other deriving Show |
| 117 | +>>> class Display a where display :: a -> String |
| 118 | +>>> instance Display TertiumDatur where display = show |
| 119 | +``` |
| 120 | + |
| 121 | +Definitions are available to following tests in the __same__ comment: |
| 122 | + |
| 123 | +``` |
| 124 | +{- |
| 125 | +>>> two = 2 |
| 126 | +
|
| 127 | +>>> two |
| 128 | +2 |
| 129 | +-} |
| 130 | +
|
| 131 | +-- >>> two |
| 132 | +-- Variable not in scope: two |
| 133 | +``` |
| 134 | + |
| 135 | +If you want definitions to be available to all tests in the module, define a setup section: |
| 136 | + |
| 137 | +``` |
| 138 | +-- $setup |
| 139 | +-- >>> eleven = 11 |
| 140 | +
|
| 141 | +{- |
| 142 | +eleven is now available to any test: |
| 143 | +
|
| 144 | +>>> eleven*2 |
| 145 | +22 |
| 146 | +-} |
| 147 | +``` |
| 148 | + |
| 149 | + |
| 150 | +### Type and Kind directives |
| 151 | + |
| 152 | +``` |
| 153 | +>>> :type Truly |
| 154 | +Truly :: TertiumDatur |
| 155 | +
|
| 156 | +>>> :kind TertiumDatur |
| 157 | +TertiumDatur :: * |
| 158 | +
|
| 159 | +>>> :type 3 |
| 160 | +3 :: forall p. Num p => p |
| 161 | +
|
| 162 | +>>> :type +d 3 |
| 163 | +3 :: Integer |
| 164 | +
|
| 165 | +>>> type N = 1 |
| 166 | +>>> type M = 40 |
| 167 | +>>> :kind! N + M + 1 |
| 168 | +N + M + 1 :: Nat |
| 169 | += 42 |
| 170 | +``` |
| 171 | + |
| 172 | +### Expressions |
| 173 | + |
| 174 | +``` |
| 175 | +>>> tuple 2 |
| 176 | +>>> triple 3 |
| 177 | +>>> display Other |
| 178 | +(2,2) |
| 179 | +(3,3,3) |
| 180 | +"Other" |
| 181 | +``` |
| 182 | + |
| 183 | +IO expressions can also be evaluated but their output to stdout/stderr is NOT captured: |
| 184 | + |
| 185 | +``` |
| 186 | +>>> print "foo" |
| 187 | +() |
| 188 | +``` |
| 189 | + |
| 190 | +### Properties |
| 191 | + |
| 192 | +``` |
| 193 | +prop> \(l::[Int]) -> reverse (reverse l) == l |
| 194 | ++++ OK, passed 100 tests. |
| 195 | +``` |
| 196 | + |
| 197 | +# Haddock vs Plain Comments |
| 198 | + |
| 199 | +There is a conceptual difference between Haddock and plain comments: |
| 200 | +* Haddock comments constitute the external module's documentation, they state the contract between the implementor and the module users (API) |
| 201 | +* Plain comments are internal documentation meant to explain how the code works (implementation). |
| 202 | + |
| 203 | +This conceptual difference is reflected in the way tests results are refreshed by the Eval plugin. |
| 204 | + |
| 205 | +Say that we have defined a `double` function as: |
| 206 | + |
| 207 | +``` |
| 208 | +double = (*2) |
| 209 | +``` |
| 210 | + |
| 211 | +And, in an Haddock comment, we run the test: |
| 212 | + |
| 213 | +``` |
| 214 | +{- | |
| 215 | +>>> double 11 |
| 216 | +22 |
| 217 | +-} |
| 218 | +``` |
| 219 | + |
| 220 | +We then change the definition to: |
| 221 | + |
| 222 | +``` |
| 223 | +double = (*3) |
| 224 | +``` |
| 225 | + |
| 226 | +When we refresh the test, its current result is compared with the previous one and differences are displayed (as they change the API): |
| 227 | + |
| 228 | +``` |
| 229 | +{- | |
| 230 | +>>> double 11 |
| 231 | +WAS 22 |
| 232 | +NOW 33 |
| 233 | +-} |
| 234 | +``` |
| 235 | + |
| 236 | +On the contrary, if the test were into a plain comment, the result would simply be replaced: |
| 237 | + |
| 238 | +``` |
| 239 | +{- |
| 240 | +>>> double 11 |
| 241 | +33 |
| 242 | +-} |
| 243 | +``` |
| 244 | + |
| 245 | +# Multiline Output |
| 246 | + |
| 247 | +By default, the output of every expression is returned as a single line. |
| 248 | + |
| 249 | +This is a problem if you want, for example, to pretty print a value (in this case using the [pretty-simple](https://hackage.haskell.org/package/pretty-simple) package): |
| 250 | + |
| 251 | +``` |
| 252 | +>>> import Text.Pretty.Simple |
| 253 | +>>> pShowNoColor [1..3] |
| 254 | +"[ 1\n, 2\n, 3\n]" |
| 255 | +``` |
| 256 | + |
| 257 | +We could try to print the pretty-print output, but stdout is not captured so we get just a (): |
| 258 | + |
| 259 | +``` |
| 260 | +>>> print $ pShowNoColor [1..7] |
| 261 | +() |
| 262 | +``` |
| 263 | + |
| 264 | +To display it properly, we can exploit the fact that the output of an error is displayed as a multi-line text: |
| 265 | + |
| 266 | +``` |
| 267 | +>>> import qualified Data.Text.Lazy as TL |
| 268 | +>>> import Text.Pretty.Simple |
| 269 | +>>> prettyPrint v = error (TL.unpack $ pShowNoColor v) :: IO String |
| 270 | +>>> prettyPrint [1..3] |
| 271 | +[ 1 |
| 272 | +, 2 |
| 273 | +, 3 |
| 274 | +] |
| 275 | +``` |
| 276 | + |
| 277 | +# Differences with doctest |
| 278 | + |
| 279 | +Though the Eval plugin functionality is quite similar to that of [doctest](https://hackage.haskell.org/package/doctest), some doctest's features are not supported. |
| 280 | + |
| 281 | +### Capturing Stdout |
| 282 | + |
| 283 | +Only the value of an IO expression is spliced in, not its output: |
| 284 | + |
| 285 | +``` |
| 286 | +>>> print "foo" |
| 287 | +() |
| 288 | +``` |
| 289 | + |
| 290 | +### Pattern Matching |
| 291 | + |
| 292 | +The arbitrary content matcher __...__ is unsupported. |
| 293 | + |
| 294 | +### Missing lambda abstractions in property tests |
| 295 | + |
| 296 | +Variables are not automatically introduced: |
| 297 | + |
| 298 | +``` |
| 299 | +prop> reverse (reverse l) == (l::[Int]) |
| 300 | +Variable not in scope: l :: [Int] |
| 301 | +``` |
| 302 | + |
| 303 | +This works: |
| 304 | + |
| 305 | +``` |
| 306 | +prop> \(l::[Int]) -> reverse (reverse l) == l |
| 307 | ++++ OK, passed 100 tests. |
| 308 | +``` |
| 309 | + |
| 310 | +### Multiline Expressions |
| 311 | + |
| 312 | +``` |
| 313 | + >>> :{ |
| 314 | + let |
| 315 | + x = 1 |
| 316 | + y = 2 |
| 317 | + in x + y + multiline |
| 318 | + :} |
| 319 | +``` |
| 320 | + |
| 321 | +# Acknowledgments |
| 322 | + |
| 323 | +Design/features derived from: |
| 324 | + |
| 325 | +* [GHCi](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ghci.html) |
| 326 | + |
| 327 | +* [Haddock's](https://www.haskell.org/haddock/doc/html/ch03s08.html#idm140354810775744) Examples and Properties |
| 328 | + |
| 329 | +* [Doctest](https://hackage.haskell.org/package/doctest) |
| 330 | + |
| 331 | +* the REPLoid feature of [Dante](https://github.com/jyp/dante) |
| 332 | + |
0 commit comments