Calling Object Methods

In Flix, we can call methods on Java objects using syntax similar to Java.

For example:

import java.io.File

def main(): Unit \ IO = 
    let f = new File("foo.txt");
    println(f.getName())

Here we import the java.io.File class, instantiate a File object, and then call the getName method on that object.

Like with constructors, Flix resolves the method based on the number of arguments and their types.

Here is another example:

import java.io.File

def main(): Unit \ IO = 
    let f = new File("foo.txt");
    if (f.exists())
        println("The file ${f.getName()} exists!")
    else
        println("The file ${f.getName()} does not exist!")

And here is a larger example:

import java.io.File
import java.io.FileWriter

def main(): Unit \ IO = 
    let f = new File("foo.txt");
    let w = new FileWriter(f);
    w.append("Hello World\n");
    w.close()

In the above example, we may want to catch the IOException that can be raised:

import java.io.File
import java.io.FileWriter
import java.io.IOException

def main(): Unit \ IO = 
    let f = new File("foo.txt");
    try {
        let w = new FileWriter(f);
        w.append("Hello World\n");
        w.close()
    } catch {
        case ex: IOException => 
            println("Unable to write to file: ${f.getName()}");
            println("The error message was: ${ex.getMessage()}")
    }

Calling Static Methods

In Flix, we can call static methods (i.e. class methods) using syntax similar to Java:

For example:

import java.lang.Math

def main(): Unit \ IO = 
    let n = Math.sin(3.14);
    println(n)

Like with constructors and methods, Flix resolves the static method based on the number of arguments and their types.

Here is another example:

import java.lang.Math

def main(): Unit \ IO = 
    println(Math.abs(-123i32));
    println(Math.abs(-123i64));
    println(Math.abs(-123.456f32));
    println(Math.abs(-123.456f64))

Calling Constructors or Methods with VarArgs

We can call a constructor or method that takes variable arguments using the the special syntax ...{ value1, value2, ...}. For example:

import java.nio.file.Path

def getMyPhoto(): Path \ IO =
    Path.of("Documents", ...{"Images", "me.jpg"})

Here we call the Path.of Java method which requires a single string and then a varargs array of strings.

In the special case where we want to call a constructor or method without any varargs we have to explicitly pass an empty Vector[t]. Moreover, we have to specify the type of the elements. For example:

import java.nio.file.Path

def getDocuments(): Path \ IO =
    Path.of("Documents", (Vector.empty(): Vector[String]))

When Constructor or Method Resolution Fails

In some cases the Flix compiler is unable to determine what Java constructor or method is called.

For example, in the program:

import java.lang.{String => JString}

def f(): String \ IO = 
    let o = ???;
    JString.valueOf(o)

The type of o is unknown, hence Flix cannot know if we want to call String.valueOf(boolean), String.valueOf(char), String.valueOf(double), or one of the other overloaded versions.

The solution is to put a type ascription on the relevant argument:

import java.lang.{String => JString}

def f(): String \ IO = 
    let o = ???;
    JString.valueOf((o: Bool))

The type ascription specifies that o has type Bool which allows method resolution to complete successfully. Note that the extra pair of parenthesis is required.

Calling Java Methods Known to be Pure

Any Flix expression that creates a Java object, calls a Java method, or calls a Java static method has the IO effect. This is to be expected: Java constructors and methods may have arbitrary side-effects.

If we know for certain that a Java constructor or method invocation has no side-effects, we can use an unsafe block to tell Flix to treat that expression as pure.

For example:

import java.lang.Math

def pythagoras(x: Float64, y: Float64): Float64 = // Pure, no IO effect
    unsafe Math.sqrt((Math.pow(x, 2.0) + Math.pow(y, 2.0)))

def main(): Unit \ IO = 
    println(pythagoras(3.0, 4.0))

Here we know for certain that Math.pow and Math.sqrt are pure functions, hence we can put them inside an unsafe block. Thus we are able to type check the Flix pythagoras function as pure, i.e. without the IO effect.

Warning: Do not, under any circumstances, use unsafe on expressions that have side-effects. Doing so breaks the type and effect system which can lead to incorrect compiler optimizations which can change the meaning of your program in subtle or catastrophic ways!

Partial Application of Java Constructors and Methods

Flix supports partial application of Flix functions. However, Java constructors and methods can never be partially applied. This limitation can be overcome introducing an explicit lambda.

For example:

import java.lang.{String => JString}

def main(): Unit \ IO = 
    def replaceAll(s, src, dst) = s.replaceAll(src, dst);
    let f = replaceAll("Hello World");
    let s1 = f("World")("Galaxy");
    let s2 = f("World")("Universe");
    println(s1);
    println(s2)

Here we introduce a Flix function replaceAll which calls String.replaceAll. Since replaceAll is a Flix function, we can partially apply it as shown in the example.