Checked Type and Effect Casts
The Flix type and effect system – by design – does not support sub-typing nor sub-effecting. To work around these limitations, which are rare in practice, Flix has two safe upcast constructs:
- A checked type cast:
checked_cast(exp)
, and - A checked effect cast
checked_ecast(exp)
.
Note: The
checked_cast
andchecked_ecast
expressions are guaranteed to be safe. The Flix compiler will check at compile-time that every checked cast cannot go wrong.
Checked Type Casts
The following program:
def main(): Unit =
let s = "Hello World";
let o: ##java.lang.Object = s;
()
does not compile:
❌ -- Type Error --------------------------------------------------
>> Expected type: 'Object' but found type: 'String'.
4 | let o: ##java.lang.Object = s;
^
expression has unexpected type.
because in Flix the String
type is not a subtype of Object
.
We can use a checked type cast to safely upcast from String
to Object
:
def main(): Unit =
let s = "Hello World";
let o: ##java.lang.Object = checked_cast(s);
()
We can use the checked_cast
construct to safely upcast any Java type to one of
its super-types:
let _: ##java.lang.Object = checked_cast("Hello World");
let _: ##java.lang.CharSequence = checked_cast("Hello World");
let _: ##java.io.Serializable = checked_cast("Hello World");
let _: ##java.lang.Object = checked_cast(null);
let _: ##java.lang.String = checked_cast(null);
Checked Effect Casts
The following program:
def hof(f: Int32 -> Int32 \ IO): Int32 \ IO = f(42)
def main(): Int32 \ IO =
hof(x -> x + 1)
does not compile:
❌ -- Type Error --------------------------------------------------
>> Expected argument of type 'Int32 -> Int32 \ IO', but got 'Int32 -> Int32'.
4 | hof(x -> x + 1)
^^^^^^^^^^
expected: 'Int32 -> Int32 & Impure \ IO'
The function 'hof' expects its 1st argument to be of type 'Int32 -> Int32 \ IO'.
Expected: Int32 -> Int32 & Impure \ IO
Actual: Int32 -> Int32
because in Flix a pure function is not a subtype of an impure function.
Specifically, the hof
requires a function with the IO
effect, but we are
passing in a pure function.
We can use a checked effect cast to safely upcast upcast a pure expression to an impure expression:
def main(): Int32 \ IO =
hof(x -> checked_ecast(x + 1))
The checked_ecast
construct allows us to pretend that x + 1
has the IO
effect.
Note: In Flix – as a general rule – higher-order functions should not require their function arguments to have a specific effect. Instead they should be effect polymorphic.
Function Types
Neither the checked_cast
nor the checked_ecast
constructs work on function types.
For example, the following does not work:
let f: Unit -> ##java.lang.Object = checked_cast(() -> "Hello World")
because it tries to cast the function type Unit -> String
to String -> Object
.
Instead, we should write:
let f: Unit -> ##java.lang.Object = (() -> checked_cast("Hello World"))
because it directly casts String
to Object
.