Constraints
The following constraints are imposed upon the Frege IDE.
Language Server Protocol (LSP)
Interoperability is the Frege IDE’s highest priority quality attribute and it can only be satisfied if the communication between the text editor and the Frege IDE is standardized. The LSP emerged as the communication standard. As a result, the Frege IDE must implement the LSP.
The LSP uses a client-server model with a remote procedure call JSON (JSON-RPC) message format and supports standard input output, pipes and sockets as communication channels. The text editor takes the role of the client while the Frege IDE acts as the server. The protocol consists of a header and a content part and specifies three types of messsages:
- Notification
-
which does not require a response.
- Request
-
which requires a response.
- Response
-
which matches a request.
Hence, every supported interaction is either started by a notification or request message.
Figure 1 shows the exchanged messages when the user opens a Frege file:
Listing 1 shows the JSON-RPC message of the textDocument/publishDiagnostics
notification from Figure 1.
textDocument/publishDiagnostics
notification from Figure 1.Content-Length: ...\r\n
\r\n
{
"jsonrpc": "2.0",
"id": 4,
"method": "textDocument/publishDiagnostics",
"params": {
"uri": "file:///Users/.../Hello.fr",
"diagnostics": [
{
"range": {
"start": {
"line": 5,
"character": 8
},
"end": {
"line": 5,
"character": 19
}
},
"severity": 3,
"source": "frege compiler",
"message": "value `result` is not used anywhere."
}]
}
}
The LSP specification divides the supported messages into the following groups which are summarized for brevity:
- Lifecycle Messages
-
signal the start and exit of the client-server interaction. Examples are
intialize
andshutdown
. - Document Synchronization
-
signals document specific messages such as
didOpen
,didChange
anddidSave
. Documents with a corresponding URI are the basic units for files and folders. - Language Features
-
are language specific code smarts visualised by the text editor which help developers to write code faster. Examples are
Goto Declaration
,Hover
andPublishDiagnostics
. - Workspace Features
-
A workspace is an opened project in a text editor with one or more root folders. Example messages are
didChangeWatchedFiles
,workspaceSymbol
anddidChangeConfiguration
. - Window Features
-
are messages related to the user interface such as
ShowMessage
andCreate Work Done Progress
.
Document Ownership and its Single Source of Truth
The |
Language Server Protocol 4 Java (LSP4J)
There are existing software development kits (SDKs) which provide language bindings for the LSP types, the JSON-RPC format and its asychronous message exchange. Implementing the LSP in Frege was out of scope and therefore we use the LSP4J library for the Frege IDE.
After setting up LSP4J, we only need to implement the org.eclipse.lsp4j.services.LanguageServer
, org.eclipse.lsp4j.services.LanguageServer.TextDocumentService
and org.eclipse.lsp4j.services.WorkspaceService
interfaces. Listing 2 shows and explains how LSP4J works and helps with an example of the hover language feature.
class FregeTextDocumentService implements TextDocumentService
{
...
@Override
public CompletableFuture<Hover> hover(HoverParams params) (1)
{
return HoverService.hover(params); (2)
}
...
}
1 | LSP4J provides the CompletableFuture type for the asynchronous message exchange and the HoverParams and Hover types which correspond to the LSP types of the hover specification. |
2 | The actual work is performed in the HoverService class. |
As a consequence, we do not need to worry about correctly creating a JSON-RPC message and the asynchronous complexity of the message exchange. Instead, we just use the types provided by LSP4J.
Frege Compiler
.fr
files to .java
files and then runs the java compiler to create .class
files.The Frege compiler transpiles .fr
files to .java
files. By default, it then calls the Java compiler to compile the .java
files to .class
files. The Java compilation step can be turned off with the -j
flag so that the Frege compiler only acts as a transpiler. The compiler manpage lists all supported flags and configurations for more information.
The Frege compiler contains all possible information for the Frege language features. Extracting the needed language feature and transforming it to an language sever protocol type is the core task of the Frege IDE. Hence the integration boundary between the Frege IDE and the Frege compiler is the most crucial relationship. Since the Frege compiler is written in Frege, we want to write the Frege IDE’s core part in Frege as well to reduce the complexity of the Frege IDE - Frege compiler boundary.
Frege and Java
We use Java with the Language Server Protocol 4 Java (LSP4J) library only and Frege for everything else. See Principles for more information.
Why Frege?
IO
, otherwise it is pure. Therefore the function average
is pure, while the main
function is impure.module Compute where
average :: Real a => [a] -> a
average [] = error "no average for empty lists"
average xs = sum xs / fromIntegral (length xs)
main :: IO ()
main = do
println $ average [1, 2, 3] -- prints 2.0
Frege is a purely functional language for the Java Virtual Machine (JVM). The strong and static type system with global type inference combined with the purity characteristic lead to the following powerful guarantee: The type system not only distinguishes if an expression is pure or impure (causing a side-effect) at compile time, as shown in Listing 3, but it also enforces that a pure function cannot call an impure action.
As a result, the Frege language has builtin support for the separation of concerns (SoC) design principle by separating pure code from side-effect causing actions. Pure code is desirable because it can be cached, tested and evaluated lazily, in advance or concurrently. Applying the SoC to a program thus benefits not only its modularity and testability but also allows the compiler to optimize the pure code part. The functional core, outer shell design pattern aims at exactly these quality attributes for its pure core.
The abovementioned guarantee and its advantages do not uniquely apply to Frege but to other purely functional languages such as Haskell, Purescript and F# too. However, Frege is the only language that guarantees them for the JVM. Herein lies its main contribution: It allows developers to adopt the functional paradigm with its design patterns and benefits to build a functional architecture on the vast ecosystem of the JVM. Even more information on Frege can be found on its official Github repository.
Build Tool
Language features can be divided into three levels, depending on their capabilities as shown in Figure 3.
- Basic
-
Provides syntax highlighting and language snippets. These features can be provided with a language extension only.
- Single-File
-
Provides language features, such as completions, hovers, signature help and document symbols for a single-file only. These features are usually powered by a separete program, called the language server.
- Project/Workspace Aware
-
Provides language features across files. This is especially important for programming languages since a project almost always consists of multiple dependent files. Capturing the project structure and its dependency model is usually delegated to a specialised build tool.
The Frege IDE needs to be project/workspace aware to satisfy the Usability attribute. In order to achieve this, the Frege IDE adds Frege project support to a build tool and extracts project specific configuration from the build tool. See Context for the big picture and Software Architecture for more details.