Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

REPL Support #477

Closed
alanz opened this issue Oct 7, 2020 · 26 comments
Closed

REPL Support #477

alanz opened this issue Oct 7, 2020 · 26 comments
Labels
status: in discussion Not actionable, because discussion is still ongoing or there's no decision yet type: enhancement New feature or request

Comments

@alanz
Copy link
Collaborator

alanz commented Oct 7, 2020

Haskell is legendary as a REPL driven language, via ghci. This is the last major piece missing from our IDE offering.

How will we do it?

This is my personal brain dump, specifically to seed a discussion on the topic. Nothing cast in concrete.
Note: I use "the server" to refer to the conglomerate of HLS, ghcide, hie-bios, etc.

  • The REPL has to be part of the running server.
    • Given the immense memory usage cost we already incur, running a separate ghci session does not make sense
    • Also keeping things in sync becomes problematic
  • It has to have unfettered access to its own IO
    • A user needs to be able to run code, which they have just written. This mostly does IO
    • A corollary of this is that running user code should not cause a lack of responsiveness in the server
  • It has to have a well-defined, well-integrated way of integrating with an IDE.
    • We have come this far using LSP, ideally we should leverage existing protocols implemented in most IDEs

My high level view of a way of achieving all these goals is the following

  • Use the Debug Adapter Protocol (DAP)
    • This already has first class support in VsCode and emacs, and coming to others as it picks up usage
  • Expose a DAP TCP endpoint in the running server.
  • DAP is just a message passing protocol, intending to talk to a target manager. This role is played by the server.
  • Use the RunInTerminal reverse request to initiate the session. This opens up an inferior terminal in the client, which exposes the REPL
  • Run something in this terminal which talks back to another endpoint in the server. This endpoint is used to run the read-eval-print loop for a ghci session
  • The ghci session runs via remote-interpreter, against hooked stdio pipes, which are routed via the third endpoint to the terminal in the client.

Voila. The rest is detail.

Back of envelope sketch of the concept

block-diagram

See also

@alanz alanz added the status: in discussion Not actionable, because discussion is still ongoing or there's no decision yet label Oct 7, 2020
@Anrock
Copy link
Contributor

Anrock commented Oct 7, 2020

Sounds nice, but I don't get what benefits this will provide vs. just running ghci in terminal? One I see is a proper debugging support which is awesome thing, but the issue here is called REPL not Debugger.

@alanz
Copy link
Collaborator Author

alanz commented Oct 7, 2020

I don't get what benefits this will provide vs. just running ghci in terminal?

Doing this means you need a whole extra GHC session, with a potentially massive extra memory usage. For a small project this is fine, but it can be a problem.

And for me that fact that is has debugger support is a happy accident, I am not a strong believer in that stuff.

however, it does unlock cool things like https://github.com/hediet/vscode-debug-visualizer

@jneira jneira added the type: enhancement New feature or request label Oct 7, 2020
@jneira jneira pinned this issue Oct 8, 2020
@jneira
Copy link
Member

jneira commented Oct 8, 2020

I think a debugger could be attractive for developers from other languages which has it from the beginning.
But i've been an emacs user and to have the repl connected with the edited file and reload changes in one click was very handy.

@alanz
Copy link
Collaborator Author

alanz commented Oct 8, 2020

to have the repl connected with the edited file and reload changes in one click was very handy
Does this mean you no longer do this? Or is it still good enough for you?

For me, the haskell inferior repl stopped working some time ago, so nowadays I run M-x shell, and launch a repl there, with cabal new-repl or whatever.

@1Computer1
Copy link

One thing I've always wanted was something like a scratch file, e.g. DrRacket but better. So you would type some valid GHCi lines in the scratch file, and it would watch the scratch file and your code for changes and run the scratch file in GHCi and you would see the output of each command. I don't really know how feasible this would be though.

@ndmitchell
Copy link
Collaborator

I think once we have basic REPL functionality, we can then go crazy with lots of more advanced forms. But getting a basic REPL enables a lot of other things.

@pepeiborra is the only person I've ever seen use the GHCi debugger effectively. But when he uses it, it is effective, and I think it's mostly a question of UI rather than anything fundamental.

@tittoassini
Copy link
Contributor

tittoassini commented Oct 8, 2020

The Eval plugin already comes rather close, you can write down a bunch of expressions and tests and get all of them executed with a single click.

One thing I've always wanted was something like a scratch file, e.g. DrRacket but better. So you would type some valid GHCi lines in the scratch file, and it would watch the scratch file and your code for changes and run the scratch file in GHCi and you would see the output of each command. I don't really know how feasible this would be though.

@tittoassini
Copy link
Contributor

tittoassini commented Oct 8, 2020

In fact, I am not sure that the traditional REPL interface is particularly effective.

When implementing a function, I find it way more useful to write a bunch of test in the functions' docs to explore and specify its behaviour and execute them in parallel after any change.

It's faster than keep retyping expressions in the REPL and has the huge advantage of leaving behind a set of permanent tests that are embedded in the function documentation. It's effectively a "docs for free" system.

The REPL model, based on the ephemeral evaluation of expressions that end up discarded and unrecorded, it's a massive waste of time.

What is really missing is the possibility of looking "into" complex functions to observe their behaviour, i.e. some intuitive form of debugging/logging.

Breaking down function in smaller functions can reduce but not completely substitute for an explicit and intuitive visualisation of the execution trail (at least for non-pure functions whose behaviour can only be properly observed "in vivo").

To conclude, I see way more potential in embedding evaluation in the source code/documentation itself than in having a separate evaluation environment. It's not a chance if 'notebook' environments that integrate textual documentation and 'persistent evaluation' like jupyter or mathematica are increasingly popular. They are not just a better way to develop code but also to use it and share its results.

@alanz
Copy link
Collaborator Author

alanz commented Oct 8, 2020

@tittoassini I fully agree that having in vivo live code exploration is good, and leaves permanent artifacts at the point they are relevant.

But people use REPLs for other things too. And to some extent, having a first-class REPL solution makes the living doc model more tractable, in terms of expectations around edge cases.

@tittoassini
Copy link
Contributor

tittoassini commented Oct 8, 2020

@alanz No doubts REPL has its uses and it would be welcome.

Just saying that for most use cases the persistent-evaluation-in-place model, that systems like HLS make easily accessible, is a more efficient solution.

But there is no conflict.

In fact, the code required to implement both is pretty much the same. Just two different user interfaces to the same underlying evaluation engine.

@alanz
Copy link
Collaborator Author

alanz commented Oct 8, 2020

A related GHC issue GHCi should support interpeting multiple packages/units with separate DynFlags
And there is another one I can never find, will keep looking.

@alanz
Copy link
Collaborator Author

alanz commented Oct 9, 2020

I see vscode is proposing a Notebook extension too.

Although that might be a better fit for the eval plugin

pepeiborra pushed a commit that referenced this issue Dec 27, 2020
* Working on Plugin support for hls

Fix PluginCommand reply type for executeCommand needs

* Remove PluginCommand

It will move to haskell-language-server instead

* Make azure CI hlint happy

By removing explicit OverloadedStrings pragma, in favour of the one already
enabled in the cabal file.

* Remove unneeded 'do'

* Fix more nits from review
@alanz alanz unpinned this issue Dec 29, 2020
@jneira
Copy link
Member

jneira commented Nov 9, 2021

It worths to note there is valuable info about the alternatives to implement the features here: haskell/vscode-haskell#450

@yaxu
Copy link

yaxu commented Jan 11, 2022

The REPL model, based on the ephemeral evaluation of expressions that end up discarded and unrecorded, it's a massive waste of time.

This isn't at all true. The REPL can be a way of interacting with the world. This interaction has its own value and trying to record it can even cheapen the experience. With a REPL, the programmer can be part of the program, and the main result can be a change in the programmer and/or other people present. Making live music is one common example.

@tittoassini
Copy link
Contributor

tittoassini commented Jan 11, 2022

The REPL model, based on the ephemeral evaluation of expressions that end up discarded and unrecorded, it's a massive waste of time.

This isn't at all true. The REPL can be a way of interacting with the world. This interaction has its own value and trying to record it can even cheapen the experience. With a REPL, the programmer can be part of the program, and the main result can be a change in the programmer and/or other people present. Making live music is one common example.

I guess we are talking about two different things.

While programming, we use the REPL mainly to verify that code works as intended and this is much more efficiently done by defining tests and properties directly in the code documentation where they can be easily replayed after every change rather than in a REPL where they end up being discarded.

While using code, on the contrary, the REPL can be an excellent interface. In fact, at least for an expert, is way more productive to write code as a set of functions that can be composed on the spot, using the full power of a programming language, rather than as a traditional monolithic program with their usually limited and clunky UIs.

In the context of a development tool like HLS, the first case is what usually matters but you are absolutely right in stressing the flexibility of the REPL as the ultimate power user tool.

@yaxu
Copy link

yaxu commented Jan 13, 2022

@tittoassini That's well put, but I think you underestimate the need to support using code as well as test-driven development of it. Exploratory use of a language is an excellent way to decide what and how you want to make something with it, and once you've developed a new library, you will often want to use it. These are different modes but it is nice to be able to jump between them from one minute to the next. So I think it makes a great deal of sense for HLS to support both.

@konn
Copy link
Collaborator

konn commented Jan 13, 2022

If I understand correctly, the REPL support for HLS is definitely on the future roadmap and not abandoned and no one says that "HLS won't support REPL at all" (hence this issue remains open for more than a year). The situation is that we are waiting for some good design/proposal.

@bradrn
Copy link
Contributor

bradrn commented Jan 17, 2022

Having discussed this issue with @jneira over at the FP Discord server, I thought I might have a go at writing an implementation. However I recently discovered that @phoityne has already written a full-featured debug adaptor for the GHCi debugger (https://github.com/phoityne/phoityne-vscode and associated repos). I fully expect that this could be modified to bring up a full REPL as desired here.

Given this, how should we proceed with this issue? Reimplementing DAP integration with GHCi seems a bit wasteful seeing that it already exists; on the other hand, I’m not quite sure what other options there are.

@jneira
Copy link
Member

jneira commented Jan 17, 2022

Given this, how should we proceed with this issue? Reimplementing DAP integration with GHCi seems a bit wasteful seeing that it already exists; on the other hand, I’m not quite sure what other options there are.

Not sure if the analysis @alanz did in the issue description continue being valid, i hope it does.
@alanz mentioned phoityne here: haskell/vscode-haskell#450 (comment)

And as I pointed out before, there is already DAP support for GHC, using the Phoityne VSCode extension. Which means the protocol implementation in haskell for the GHC side already exists. The pieces should be to adapt that to run under HLS using the external interpreter, and orchestrating things so that both the LSP and DAP sessions talk to the same executable.

@bradrn
Copy link
Contributor

bradrn commented Jan 17, 2022

@alanz mentioned phoityne here: haskell/vscode-haskell#450 (comment)

Ah, I missed that comment! What @alanz describes sounds like a good plan. So the architecture would look something like this:

@phoityne
Copy link

Hi.

The haskell-debug-adapter has a public I/F for running debugger.
So before reimplementing DAP handling in HLS, easily you can try it.
Just call it from a thread in HLS process.

DAP launch architecture has two mode, starting new debug adapter each time or attaching to existing debug adapter.
I think HLS would take the second one. (phoityne is the first one.)

And REPL, although DAP has repl I/F, but you have to start debugging to use it, not suitable for repl functionality while coding,
After start debugging, I think a language server functionality does not work. Because events from VScode GUI will send to a debug adapter.

Regards.

@bradrn
Copy link
Contributor

bradrn commented Jan 17, 2022

Thanks for the reply @phoityne!

The haskell-debug-adapter has a public I/F for running debugger.
So before reimplementing DAP handling in HLS, easily you can try it.
Just call it from a thread in HLS process.

I actually did see this. My main concern with directly using haskell-debug-adaptor in HLS is that this interface may not provide enough fine-grained control to be easily integrable. But I can certainly try it and see.

DAP launch architecture has two mode, starting new debug adapter each time or attaching to existing debug adapter.
I think HLS would take the second one. (phoityne is the first one.)

I came to this same conclusion: HLS would maintain one DAP server, which the client would attach/detach to as necessary.

And REPL, although DAP has repl I/F, but you have to start debugging to use it, not suitable for repl functionality while coding

This is the other reason I’m a bit uncomfortable with directly using haskell-debug-adaptor. An interactive debugger would be amazing, but the really vital component will be just normal GHCi integration, which haskell-debug-adaptor does not currently provide.

After start debugging, I think a language server functionality does not work. Because events from VScode GUI will send to a debug adapter.

Are you sure? I’d expect VSCode (and other editors) to be perfectly capable of communicating on two separate channels at once. On the client-side, DAP and LSP shouldn’t interact in any way, I think.

@bradrn
Copy link
Contributor

bradrn commented Jun 27, 2022

Having gotten some more free time recently, and having finally gotten HLS working in my editor, I’ve been poking around the code trying to figure out where everything should go. It seems like adding DAP support might necessitate deeper changes than I thought, given that DAP needs to communicate on a separate channel to LSP. (At least, I’m pretty sure it does… it would make our lives a lot easier if they could be mixed together on the same channel.) But I’m not sure exactly how to accomplish this: currently we communicate on stdin/stdout, and of course there’s only one of those each. Perhaps we may need to move to sockets— or perhaps there’s simply no way to implement this issue within a single executable in the first place, in which case development on this issue would need to more somewhere else (most probably @phoityne’s repos).

But anyway, assuming this can be kept within HLS itself, I’m thinking the following structure could work:

  1. Add a separate subsystem to respond to DAP requests, parallel to Development.IDE.LSP.LanguageServer
  2. Structure that one in terms of plugins too, by making Plugin and PluginDescription polymorphic over message type (which surely can be done without disturbing the existing types)
  3. Add a DAP plugin to launch GHCi on request, using the architecture detailed above
  4. (Possible next steps: add more DAP plugins for things like running in terminal, interactive debugging, variable watching etc.)

Does this seem reasonable? (Certainly it does to me, but given my unfamiliarity with the HLS codebase I’m wary of making such a big change without confirmation.)

@alanz
Copy link
Collaborator Author

alanz commented Jun 27, 2022

DAP and LSP are separate protocols, which both prefer to run on stdin/stdout.
So in terms of actually running this, you either need to be able to get one of the servers to work with an alternate communication mechanism (which is hard to do, across all supported clients), or run a separate process for the DAP server, and possibly communicate with the LSP server via IPC mechanism.

@bradrn
Copy link
Contributor

bradrn commented Jun 28, 2022

DAP and LSP are separate protocols, which both prefer to run on stdin/stdout.

Yes, this is what I was afraid of. But I quite like this idea:

… run a separate process for the DAP server, and possibly communicate with the LSP server via IPC mechanism.

So at this point, I see two options. Either we can try to do everything within HLS itself and use some IPC plumbing to let it communicate over two channels, or we can decide that DAP communication is outside the scope of HLS and move development elsewhere. I’m currently leaning towards the latter, but what does everyone else think?

@bradrn
Copy link
Contributor

bradrn commented Jul 5, 2022

Well, if no-one else has any ideas, I’ll just go with my previous comment and try to implement this issue alongside the rest of the DAP protocol for Haskell, which in this case I suppose would mean moving to @phoityne’s repos.

@haskell haskell locked and limited conversation to collaborators Jul 13, 2022
@hasufell hasufell converted this issue into discussion #3042 Jul 13, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
status: in discussion Not actionable, because discussion is still ongoing or there's no decision yet type: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests