FregeFX IntelliJ setup
After a weekend of unsuccessfully trying to get Haskell using an HTTP package, I decided to finally give Frege a try. And while being at it, why not try FregeFX as well. Another weekend of moments of confusion, frustration and euphoria got me to the initial setup of creating projects with FregeFX in IntelliJ. I put this setup in a template for whomever might need it. Here is the link to the template: FregeFX IntelliJ project template. The following article describes my odyssey into the world of Frege(FX) ...
Introduction
I'm a fan of functional programming and Haskell in particular, since the moment I understood the idea at university. I've used it quite a bit on codingame for implementing algorithms. Then I wanted to move forward and use it for a real project, with accessing the network and the filesystem. I failed miserably, but, while searching the internet, I found that at least I wasn't the only one. I'm also a fan of the JVM and I've known of the existence of Frege for a while too. I wanted to give it a try for a while and I guess this was the moment, that I've been looking for.
First of all, there is lots of helpful content about Frege and FregeFX on the internet, that is worth mentioning. While setting up the template, I worked through most of the following sources:
- Frege Goodness (a short introductory online book)
- Frege (the sources on GitHub)
- FregeFX (the sources on GitHub)
- fregeTutorial (Example project on GitHub)
- Frege API documentation
- Calling Frege from Java
- FregeChat (Example project on GitHub)
- frege-gradle-plugin (the sources on GitHub)
Big thanks to the authors of all those projects!
What are the project properties/requirements?
The technical basics of the template project are as follows:
- It's gradle project.
- It supports a GUI based on FregeFX (JavaFX for Frege).
- The window design should be defined in FXML and CSS.
- User actions are handled by a controller, which is referenced in the FXML file.
- All logic should be implemented in Frege.
Setting up the gradle project
A basic gradle file can be found in the build gradle of the fregeTutorial. It covers the following features:
- Addition of Frege to Java source set
- Gradle tasks for Frege and FregeFX
- Compile tasks
- Test tasks
- Frege REPL task
Frege code will first be compiled to Java (.java) and then into Java class files (.class). The files of both types will be generated into the build folder. This setup allows using Java from Frege, since Frege can access manually created Java files from the source directory. This includes IDE support.
!!! Hint: The IDE support in Eclipse using the Frege Eclipse plugin seems to be more advanced then the support in IntelliJ at the time of the creation of this article. This is a guess though, since I only worked with Frege in IntelliJ and the knowledge for working with Frege in Eclipse comes from reading a few articles and comments.
It is also possible to use Frege code from Java with this setup, but there is no IDE support. At least IntelliJ is not searching the build folder for Java sources (why should it) and setting up the build folder as src directory will break the compile process. Without the word completion, it is necessary to check the generated code. When implementing the calls to the generated code, the IDE will mark the calls as faulty, because it can't find the code. It will compile though, since the compilation will use the compiled Frege code in the build directory.
This turns out to be a problem. The fourth project property requires a Controller, which is written in Java, because an object instance with non-static methods (handler methods) and fields (element references) is required. Frege code doesn't compile into "simple POJOs". Annotations can't be set either in Frege, but the interface fields and methods towards the FXML need to be annotated with the @FXML
tag. So, the controller needs to be a POJO with annotations. If the controller is written in Java and the logic is written in Frege, then Java to Frege calls are necessary, in which the controller acts as interface from the GUI to the actual controller logic. Of course, IDE support for this would be great.
So, how can the tooling support of the IDE be achieved, with FXML and controllers, which forces Java to Frege calls? The solution is a gradle feature called source sets. By defining a seperate feature set for Frege, it is also possible to change the build path for compilation. The Frege compile path can direct to a new Java source directory, which also needs to be added to the Java source set. The compile dependencies need to be adjusted as well - Frege needs to be compiled before compiling Java. When Java is compiled to class files, it will also use the compiled Frege code. And the IDE will use the new Java source directory for tooling support.
This is the additional/changed code in the build.gradle
:
sourceSets {
main {
java {
// src/frege/java will contain the java files generated from Frege
// by adding it to the srcDirs, we get IDE support for accessing
// the Frege content from Java.
srcDirs = ['src/main/java', 'src/frege/java']
}
}
// Add a special source set for Frege and set the output directory for the
// Frege compiler to src/frege/java. It's using the Java output dir, so we
// need to change that one for this source set *rolling eyes*
frege {
java {
outputDir = file("src/frege/java")
}
}
}
dependencies {
fregeCompile 'org.frege-lang:frege:3+'
fregeCompile 'org.frege-lang:fregefx:0.8'
compile 'org.frege-lang:frege:3+'
compile 'org.frege-lang:fregefx:0.8'
// we want to call Frege from Java, so it needs to be compiled to Java first
compileOnly sourceSets.frege.output
testCompile 'junit:junit:4+'
testCompile 'org.mockito:mockito-all:1+'
// Just in case we have an implicit reference from Java to Frege during the tests
testCompile sourceSets.frege.output
}
compileFrege {
target = javaTarget
compileGeneratedJava = false // i'd like to NOT have class files generated by Frege, but it doesn't work :-\
}
compileTestFrege {
target = javaTarget
}
fregeDoc {
verbose = true
module = 'src/frege/java'
}
fregeQuickCheck {
moduleDir = "$project.buildDir/classes/java/test"
classpathDirectories = ["$project.buildDir/classes/java/main", "$project.buildDir/classes/java/test"]
}
The Frege code goes into src/frege/frege
. The compiled Frege code goes into src/frege/java
. Non-generated Java code goes as usual into src/main/java
and resources into src/main/resources
. Tests go into src/test/frege
and src/test/java
with test resources being in src/test/resources
.
This will compile Java twice: once before Frege (this is part of the Frege tool chain) and once after Frege, which is the result of the above shown gradle setup. The achievement of this is the IDE tooling supporting two way communication between Frege and Java.
There are at least two alternatives worth mentioning:
- Don't use FXML. All elements and handlers can be created in Frege during the GUI setup. This is similar to the way everything can be created as Java objects. This is the way of all examples, that I've found so far.
- Use FXML, but don't use a controller. FregeFX, just like Java, allows creating/accessing
Scene
objects. They can be used to gain access to elements defined in FXML using the elements IDs. Then handlers can be attached to the elements. This can also be done during the GUI setup.
What is the reason to go through all the problems with the FXML and the controller? There are good arguments to use the alternatives. First and foremost, it's less complicated then the setup of this article. Well, I prefer FXML to take the UI design out of the code to "clean it up" and I simply like to link the UI to the logic using references to controllers.
Setting up the UI
This step is quite short in code, because the GUI is defined in FXML. Here is the code:
module de.atennert.fregefx.Hello where
import fregefx.JavaFxType
import fregefx.JavaFxAll
import fregefx.JavaFxUtils
{-- Main function - starting point of the program, initialize UI --}
main _ = do
FregeFX.launch $ withStage buildUI
{-- Build the UI using FXML --}
buildUI :: Family a => a -> Stage -> JFX a
buildUI root stage = do
stage.setTitle "Hello world"
view <- FregeFX.fxml "de.atennert.fregefx.Hello" "/main_screen.fxml"
scene <- Scene.new view
stage.setScene scene
-- you can get UI elements with scene.lookup "#id" -> JFX (Maybe Node) and register handling
return root
This code includes the main function, which will be called at the start of the program. That function launches FregeFX, which builds the UI using the buildUI function. This function gets the root
element and the stage
. It sets the window title and, by utilising the Scene
, the FXML UI description and it returns root
element. As mentioned earlier, it's possible to use the Scene
for returning UI elements and registering handlers on them.
Communication from the UI to Frege
How can user actions be evaluated? At this point, the concept of JFX and IO actions becomes relevant. In short, UI actions should run in a different thread, then the rest of the program, to not block while data is processed in the background. This is partially enforced by the framework, in which the UI actions need to be in the JFX
monad, while IO actions need to be in the IO
monad. It is possible to switch between the to worlds by a view functions, provided by FregeFX. I'll name three of them here:
inIO
- allows to execute IO actions coming from the UIwithUI
- allows to execute UI (JFX) actions coming from IOwithStage
- allows to execute UI (JFX) actions with the JavaFX stage (will be provided as parameter)
!!! Note: In Frege, the interaction with mutable Java objects is done with the ST
monad. As far as I have seen it can be used with functions, which make use of the IO
monad, like inIO
.
So, when IO actions, like writing to stdout
, need to be executed on a button press of a user, inIO
can be used to achieve this.
There is another thing. With the given setup, the user actions will first be executed in the Java conttroller. Then the controller calls Frege code. Calling Frege from Java means, that the actions are run inside the ST
monad. It is kind of equal to the IO
monad so it is not necessary to use a conversion method like inIO
.
However, the call from Java to Frege needs to be done in a certian way.
Given a function doIO
in the Hello module in Frege, it can be called like this:
...
doIO :: IO ()
doIO = println "hello again"
...
public class HelloController {
@FXML
public void doIO( ActionEvent e ) {
PreludeBase.TST.performUnsafe( Hello.doIO.call() ).call();
}
...
}
After the conversion to Java, doIO
will be wrapped in a so called Func
(a special kind of function). Executing call
on it, the doIO
function (also a Func
object) is returned. This Frege function needs to be executed from Java inside the performUnsafe
method, which will return a thrunk
. That is a lazy execution wrapper container. Executing call
on this container will then execute the function.
The Frege functions, that can be called from Java need to use IO
or ST
monads. In case of UI (JFX
) actions, it's necessary to use a conversion function like withUI
to wrap the UI call. Here is an example from a project (not the template).
...
setLabeledText :: Labeled -> String -> IO ()
setLabeledText labeled text = (withUI $ labeled.setText text) `inIO` (\_ -> return ())
...
public class UIController {
@FXML
private Button myButton;
@FXML
public void () {
PreludeBase.TST.performUnsafe( Hello.setLabeledText(myButton, "new text").call() ).call();
}
}
It's similar to the IO scenario, with the addition of providing the button and the text to the function. Here withUI
and inIO
are used to execute the UI action (setting the text of the button) in the JFX
context and then returning back to the IO
context, which is required as the return type, because the function is called in the Java code.
To round up this section, here is the FXML UI definition from the template:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.String?>
<VBox styleClass="container" xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="de.atennert.fregefx.HelloController">
<stylesheets>
<String fx:value="hello.css"/>
</stylesheets>
<Label fx:id="label1" text="Hello" />
<HBox styleClass="container">
<Button fx:id="button1" text="Do IO" onAction="#doIO"/>
<Button fx:id="button2" text="Do UI" onAction="#doUI"/>
</HBox>
</VBox>
How about file and network IO?
This is not part of the template, but since it is mentioned in the introduction, let's have a peek.
File output
It is suggested to use a BufferedWriter
for efficient writing to the storage. A FileWriter
is required for writing files and both are wrapped in a PrintWriter
for convenience.
Frege already contains definitions for different readers and writers, which can be looked up in the API and the Frege sources. Unfortunately, the PrintWriter
can't take a BufferedWriter
as parameter at the time of writing this article. It can take a Writer
and BufferedWriter
extends Writer
in Java, but Frege has a strict type system, which doesn't know about this. In Frege BufferedWriter
and Writer
are completely different things. The PrintWriter
is already part of Frege, so it only needs to be extended. The FileWriter
as well as the BufferedWriter
are not yet part of the Frege definitions. So, I decided to write my own definitions for accessing the Java classes:
private data BufferedWriter = mutable native java.io.BufferedWriter where
native new :: FileWriter -> IO BufferedWriter
private data FileWriter = mutable native java.io.FileWriter where
native new :: String -> Bool -> IO FileWriter throws IOException
-- extending the PrintWriter
native newPW new :: BufferedWriter -> IO PrintWriter
native close close :: PrintWriter -> IO ()
native flush flush :: PrintWriter -> IO ()
Those definitions contain the minimal set, that was needed for my project, but it can be extended as necessary.
Now the functions for writing to a file can be created:
open :: String -> IO PrintWriter
open path = newPw =<< BufferedWriter.new =<< FileWriter.new path
write :: String -> PrintWriter -> IO ()
write text writer = writer.print text
close :: PrintWriter -> IO ()
close writer = writer.close
=<<
(or >>=
respectively) is explained well in Frege Goodness, chapter "List and Path".
HTTP requests
The goal of the HTTP request is, to download a website, which is defined by it's URL, and return the content in form of a String.
The corresponding Java function may look like this (source taken from stackoverflow):
public static String getHTML(String urlToRead) throws Exception {
StringBuilder result = new StringBuilder();
URL url = new URL(urlToRead);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader rd = new BufferedReader( new InputStreamReader( conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
return result.toString();
}
This code contains a cast to HttpURLConnection
. Again, extends / implements doesn't exist in Freges strict type system, neither do casts. Similarly to the BufferedWriter
, the BufferedReader
requires a Reader
as argument and not an InputStreamReader
. Fortunately, it's possible to define explicit casts from one class to another. For the HTTP request handling, I choose to define the casts to profit from the existing URL
type and the BufferedReader
features from Frege:
import frege.java.Net
private data ClassCastException = native java.lang.ClassCastException
private data ProtocolException = native java.net.ProtocolException
derive Exceptional ProtocolException
private class URLCastTarget a where
private downcastUrl :: URLConnection -> IO (ClassCastException | a)
instance URLCastTarget (MutableIO HttpURLConnection) where
native downcastUrl "(java.net.HttpURLConnection)" :: URLConnection -> IO (ClassCastException | MutableIO HttpURLConnection)
private class ReaderCastTarget a where
private downcastReader :: a -> IO (ClassCastException | Reader)
instance ReaderCastTarget InputStreamReader where
native downcastReader "(java.io.Reader)" :: InputStreamReader -> IO (ClassCastException | Reader)
private data HttpURLConnection = native java.net.HttpURLConnection where
native setRequestMethod :: Mutable RealWorld HttpURLConnection -> String -> IO () throws ProtocolException
native getInputStream :: Mutable RealWorld HttpURLConnection -> IO InputStream throws IOException
private castConnection :: URLConnection -> IO (MutableIO HttpURLConnection)
private castConnection urlcon = downcastUrl urlcon >>= either
(\cce -> error $ "cannot cast connection. Reason: " ++ show cce.getMessage)
return
private castReader :: ReaderCastTarget a => a -> IO Reader
private castReader reader = downcastReader reader >>= either
(\cce -> error $ "cannot cast reader. Reason: " ++ show cce.getMessage)
return
This code contains the definitions of ClassCastException
and ProtocolException
. Below are the necessary class and instance definitions for the casts. Then follows the definition for HttpURLConnection, because that is not part of the Frege definitions. Finally, there are two functions for handling the casts and throwing errors, in case an Exception should be thrown. Frege, just like Haskell, doesn't have the concept of Exceptions. There are good ways to handle corresponding use cases, for instance with the Maybe
monad. Throwing an error is a little bit of a dirty short cut, as it avoids the explicit error handling at the cost of ending the application in case of an error.
What is left, is the actual implementation of the download function, which only needs a few lines to write and can be read quite well, thanks to the preparatory work:
downloadPage :: String -> IO String
downloadPage urlString = do
httpConnection <- castConnection =<< URL.openConnection =<< URL.new urlString
httpConnection.setRequestMethod "GET"
reader <- BufferedReader.new =<< castReader =<< flip InputStreamReader.new "UTF-8" =<< httpConnection.getInputStream
webpage <- reader.getLines
return $ unlines webpage
Final thoughts
Here and there it's still a little cumbersome to use Frege, compared to using Java. I say this, because of the missing definitions, that need to be defined for certain use cases. Taking a look into the Frege and FregeFX sources, there are a lot prepared definitions, which are still put in comments and therefor not yet available. I imagine this to be the result of a lot of special cases, due to the strict type system.
On the other hand, the strict type system helps preventing type errors. Writing stateless functional code also has it's charm, because it removes the necessity to keep track of the state of variables. Data in Frege is immutable, so one part of the code cannot manipulate data, which is processed in another part of the code.
Comparing Frege to Haskell, the most important thing for me is: I don't have the issues with Frege, that I had with Haskell in terms of using packages/libraries. Haskell has a lot of packages though and I guess it's nice, when it works. Frege builds on the JVM and Java libraries can be used in Frege. The downside is, it may be necessary to write the definitions yourself, because at the time of writing this article, Frege(FX) covers definitions of many parts of Java/JavaFX, but not all of it. To my current knowledge, there is also no definition source for, let's say, libraries from the Maven or Bintray repositories.
Overall, I suggest to give it a try. For me it is a challenging, great experience, that gives food for thought about software development.
Edit
I updated the code for the IO writing, above. In my project, I also refactored and extended the HTTP handling heavily. The current version is shown by the following code. It includes some basic handling for HTTP errors and exceptions during opening of the connection.
Careful: The HTTP status handling treats all codes >= 300 the same way ... with a simple retry after 5 seconds. That worked for my tests, but is very wrong! I display it here, to show the general approach on how to handle exceptions and different return codes.
import frege.java.Net
private data ClassCastException = pure native java.lang.ClassCastException
private data ProtocolException = pure native java.net.ProtocolException
private class URLCastTarget a where
private downcastUrl :: URLConnection -> IO (ClassCastException | a)
instance URLCastTarget HttpURLConnection where
native downcastUrl "(java.net.HttpURLConnection)" :: URLConnection -> IO (ClassCastException | HttpURLConnection)
private class ReaderCastTarget a where
private downcastReader :: a -> IO (ClassCastException | Reader)
instance ReaderCastTarget InputStreamReader where
native downcastReader "(java.io.Reader)" :: InputStreamReader -> IO (ClassCastException | Reader)
private data HttpURLConnection = mutable native java.net.HttpURLConnection where
native setRequestMethod :: HttpURLConnection -> String -> IO () throws ProtocolException
native getInputStream :: HttpURLConnection -> IO InputStream throws IOException
native getResponseCode :: HttpURLConnection -> IO Int throws IOException
private castConnection :: URLConnection -> IO HttpURLConnection
private castConnection urlcon = downcastUrl urlcon >>= either
(\cce -> error $ "cannot cast connection. Reason: " ++ show cce.getMessage)
return
private castReader :: ReaderCastTarget a => a -> IO Reader
private castReader reader = downcastReader reader >>= either
(\cce -> error $ "cannot cast reader. Reason: " ++ show cce.getMessage)
return
getUrlString :: String -> String
getUrlString word = baseUrl ++ word
downloadPage :: String -> IO String
downloadPage urlString = do
httpConnection <- castConnection =<< getConnection
httpConnection.setRequestMethod "GET"
responseCode <- httpConnection.getResponseCode
if responseCode >= 200 && responseCode < 300
then do
reader <- BufferedReader.new =<< castReader =<< flip InputStreamReader.new "UTF-8" =<< httpConnection.getInputStream
webpage <- reader.getLines
return $ unlines webpage
else do
Thread.sleep 5000
stderr.println $ "Error connection to " ++ urlString ++ " ... retrying"
return =<< downloadPage urlString
where getConnection :: IO URLConnection
getConnection = (URL.new urlString >>= URL.openConnection)
`catch` (\(ioe::IOException) -> (stderr.println $ ioe.getMessage) >> Thread.sleep 2000 >> getConnection)