Skip to content

Documentation for the Eval Plugin #1206

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

Merged
merged 5 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ background](https://neilmitchell.blogspot.com/2020/01/one-haskell-ide-to-rule-th

## Features

- 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 -- >>>
- Code evaluation codelens ([Tutorial](plugins/hls-eval-plugin/README.md)):

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

- 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:

Expand Down
332 changes: 332 additions & 0 deletions plugins/hls-eval-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
# Eval plugin for the [Haskell Language Server](https://github.com/haskell/haskell-language-server#readme)

The Eval plugin evaluates code inserted in comments.

This is mainly useful to test and document functions and to quickly evaluate small expressions.

Every line of code to be evaluated is introduced by __>>>__

A quick calculation:

```
-- >>> 2**4.5/pi
-- 7.202530529256849
```

A little test for the `double` function:

```
{- |
A doubling function.

>>> double 11
22
-}
double = (2*)
```

# Demo

![Eval](demo.gif)

# Test Structure

A test is composed by a sequence of contiguous lines, the result of their evaluation is inserted after the test body:

```
>>> "AB" ++ "CD"
>>> "CD" ++ "AB"
"ABCD"
"CDAB"
```

You execute a test by clicking on the _Evaluate_ code lens that appears above it (or _Refresh_, if the test has been run previously).

All tests in the same comment block are executed together.


Tests can appear in all kind of comments:
* plain comments (both single and multi line)
```
{-
>>> "ab" ++ "c"
"abc"
-}

-- >>> "ab" ++ "c"
-- "abc"
```
* Haddock commands (both single and multi line, forward and backward)
```
{-
>>> "ab" ++ "c"
"abc"
-}

-- >>> "ab" ++ "c"
-- "abc"

double a = a + a
-- ^ A doubling function
-- >>> double 11
-- 22
```

Both plain Haskell and Literate Haskell (Bird-style only) source files are supported.

# Test Components

In general, a test is a sequence of:
* imports
* directives
* statements
* expressions
* properties

in no particular order, with every line introduced by __>>>__ (or __prop>__ in the case of properties).

### Imports

```
>>> import Data.List
>>> import GHC.TypeNats
```

From any package in scope but currently NOT from modules in the same source directory.

### Language Extensions

```
>>> :set -XScopedTypeVariables -XStandaloneDeriving -XDataKinds -XTypeOperators -XExplicitNamespaces
```

### Statements and Declarations

Function declarations (optionally introduced by __let__):

```
>>> let tuple x = (x,x)
>>> let one=1;two=2
>>> triple x = (x,x,x)
```

Any other declaration:

```
>>> data TertiumDatur = Truly | Falsely | Other deriving Show
>>> class Display a where display :: a -> String
>>> instance Display TertiumDatur where display = show
```

Definitions are available to following tests in the __same__ comment:

```
{-
>>> two = 2

>>> two
2
-}

-- >>> two
-- Variable not in scope: two
```

If you want definitions to be available to all tests in the module, define a setup section:

```
-- $setup
-- >>> eleven = 11

{-
eleven is now available to any test:

>>> eleven*2
22
-}
```


### Type and Kind directives

```
>>> :type Truly
Truly :: TertiumDatur

>>> :kind TertiumDatur
TertiumDatur :: *

>>> :type 3
3 :: forall p. Num p => p

>>> :type +d 3
3 :: Integer

>>> type N = 1
>>> type M = 40
>>> :kind! N + M + 1
N + M + 1 :: Nat
= 42
```

### Expressions

```
>>> tuple 2
>>> triple 3
>>> display Other
(2,2)
(3,3,3)
"Other"
```

IO expressions can also be evaluated but their output to stdout/stderr is NOT captured:

```
>>> print "foo"
()
```

### Properties

```
prop> \(l::[Int]) -> reverse (reverse l) == l
+++ OK, passed 100 tests.
```

# Haddock vs Plain Comments

There is a conceptual difference between Haddock and plain comments:
* Haddock comments constitute the external module's documentation, they state the contract between the implementor and the module users (API)
* Plain comments are internal documentation meant to explain how the code works (implementation).

This conceptual difference is reflected in the way tests results are refreshed by the Eval plugin.

Say that we have defined a `double` function as:

```
double = (*2)
```

And, in an Haddock comment, we run the test:

```
{- |
>>> double 11
22
-}
```

We then change the definition to:

```
double = (*3)
```

When we refresh the test, its current result is compared with the previous one and differences are displayed (as they change the API):

```
{- |
>>> double 11
WAS 22
NOW 33
-}
```

On the contrary, if the test were into a plain comment, the result would simply be replaced:

```
{-
>>> double 11
33
-}
```

# Multiline Output

By default, the output of every expression is returned as a single line.

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):

```
>>> import Text.Pretty.Simple
>>> pShowNoColor [1..3]
"[ 1\n, 2\n, 3\n]"
```

We could try to print the pretty-print output, but stdout is not captured so we get just a ():

```
>>> print $ pShowNoColor [1..7]
()
```

To display it properly, we can exploit the fact that the output of an error is displayed as a multi-line text:

```
>>> import qualified Data.Text.Lazy as TL
>>> import Text.Pretty.Simple
>>> prettyPrint v = error (TL.unpack $ pShowNoColor v) :: IO String
>>> prettyPrint [1..3]
[ 1
, 2
, 3
]
```

# Differences with doctest

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.

### Capturing Stdout

Only the value of an IO expression is spliced in, not its output:

```
>>> print "foo"
()
```

### Pattern Matching

The arbitrary content matcher __...__ is unsupported.

### Missing lambda abstractions in property tests

Variables are not automatically introduced:

```
prop> reverse (reverse l) == (l::[Int])
Variable not in scope: l :: [Int]
```

This works:

```
prop> \(l::[Int]) -> reverse (reverse l) == l
+++ OK, passed 100 tests.
```

### Multiline Expressions

```
>>> :{
let
x = 1
y = 2
in x + y + multiline
:}
```

# Acknowledgments

Design/features derived from:

* [GHCi](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ghci.html)

* [Haddock's](https://www.haskell.org/haddock/doc/html/ch03s08.html#idm140354810775744) Examples and Properties

* [Doctest](https://hackage.haskell.org/package/doctest)

* the REPLoid feature of [Dante](https://github.com/jyp/dante)

Binary file added plugins/hls-eval-plugin/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion plugins/hls-eval-plugin/hls-eval-plugin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ license-file: LICENSE
author: https://github.com/haskell/haskell-language-server/contributors
maintainer: https://github.com/haskell/haskell-language-server/contributors
build-type: Simple
extra-source-files: LICENSE
extra-source-files:
LICENSE
README.md

flag pedantic
description: Enable -Werror
Expand Down
10 changes: 1 addition & 9 deletions plugins/hls-eval-plugin/src/Ide/Plugin/Eval.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,7 @@
{-# OPTIONS_GHC -Wwarn #-}

{- |
A plugin inspired by:

* the REPLoid feature of <https://github.com/jyp/dante Dante>

* <https://www.haskell.org/haddock/doc/html/ch03s08.html#idm140354810775744 Haddock>'s Examples and Properties

* <https://hackage.haskell.org/package/doctest Doctest>

See the "Ide.Plugin.Eval.Tutorial" module for a full introduction to the plugin functionality.
Eval Plugin entry point.
-}
module Ide.Plugin.Eval (
descriptor,
Expand Down
Loading