Skip to content

Commit a4627e5

Browse files
Documentation for the Eval Plugin (#1206)
* moved tutorial to README * demo gif * Added reference to Tutorial and new demo in README Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 58cd76a commit a4627e5

File tree

6 files changed

+338
-258
lines changed

6 files changed

+338
-258
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ background](https://neilmitchell.blogspot.com/2020/01/one-haskell-ide-to-rule-th
4949

5050
## Features
5151

52-
- Code evaluation codelens (inspired by [Dante](https://github.com/jyp/dante#-reploid)). You can evaluate code by writing it in a comment of the form -- >>>
52+
- Code evaluation codelens ([Tutorial](plugins/hls-eval-plugin/README.md)):
5353

54-
![Eval](https://i.imgur.com/bh992sT.gif)
54+
![Eval Demo](plugins/hls-eval-plugin/demo.gif)
5555

5656
- Type information and documentation on hover. Note that currently, in order for docs to be displayed for dependencies, they must have been built with GHC's `-haddock` flag:
5757

plugins/hls-eval-plugin/README.md

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
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+
![Eval](demo.gif)
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+

plugins/hls-eval-plugin/demo.gif

186 KB
Loading

plugins/hls-eval-plugin/hls-eval-plugin.cabal

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ license-file: LICENSE
1010
author: https://github.com/haskell/haskell-language-server/contributors
1111
maintainer: https://github.com/haskell/haskell-language-server/contributors
1212
build-type: Simple
13-
extra-source-files: LICENSE
13+
extra-source-files:
14+
LICENSE
15+
README.md
1416

1517
flag pedantic
1618
description: Enable -Werror

plugins/hls-eval-plugin/src/Ide/Plugin/Eval.hs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,7 @@
33
{-# OPTIONS_GHC -Wwarn #-}
44

55
{- |
6-
A plugin inspired by:
7-
8-
* the REPLoid feature of <https://github.com/jyp/dante Dante>
9-
10-
* <https://www.haskell.org/haddock/doc/html/ch03s08.html#idm140354810775744 Haddock>'s Examples and Properties
11-
12-
* <https://hackage.haskell.org/package/doctest Doctest>
13-
14-
See the "Ide.Plugin.Eval.Tutorial" module for a full introduction to the plugin functionality.
6+
Eval Plugin entry point.
157
-}
168
module Ide.Plugin.Eval (
179
descriptor,

0 commit comments

Comments
 (0)