I have Frege code as follows (mostly, just pay attention to the type signature for getDatabase)
module fregeHelper.FregeCode where
--Java's String.split method
pure native split :: String -> String -> JArray String
--Java's ArrayList<t>
data ArrayList t =native java.util.ArrayList where
native new :: () -> STMutable s (ArrayList t)
native add::Mutable s (ArrayList t)-> t -> ST s ()
getDatabase::String->(IO (STMutable s (ArrayList (String, String))))
getDatabase s = do
fileContents <- readFile s
let processedData = map ((\x->(elemAt x 0, elemAt x 1)) . (flip split ";")) . lines $ fileContents
return $ fold foldAdd (ArrayList.new ()) processedData
where
foldAdd::ST s (Mutable s (ArrayList t)) -> t -> ST s (Mutable s (ArrayList t))
foldAdd list elem = list >>= \x->(ArrayList.add x elem >> return x)
Then from Java I want to define the following function thusly (where DATABASE is a string constant):
private void readDatabase() {
myList = Delayed.<ArrayList<TTuple2>>forced(
fregeHelper.FregeCode.getDatabase(DATABASE));
}
However, this gives me a java.lang.ClassCastException: frege.prelude.PreludeBase$TST$1 cannot be cast to java.util.ArrayList
Through experimentation, I had to change the code to be
private void readDatabase() {
fighters = Delayed.<ArrayList<TTuple2>>forced(
fregeHelper.FregeCode.getDatabase(DATABASE)
.apply(null)
.apply(null)
);
}
I've put null in the latter applies just to show that it doesn't matter what I pass in. I have no idea why I have to apply the function three times (I can't just immediately force evaluation). Is there any way I can either remove the applies or have some rationalization as to why they're necessary? (Note: using .result() doesn't help the case.)
The reason for this is that in this implementation, a ST action is represented as a "function object", where the method that implements the accompagnied function ignores it argument.
It may help the understanding to recall the definition of ST:
abstract data ST s a = ST (s -> a) where ...
First thing to note is that the data
would actually be written newtype
in Haskell. So ST
is just a type renaming, that is, an ST action is actually a function.
However, the abstract
makes sure you cannot look through the ST
data constructor and hence cannot run the function directly from Frege code.
This explains why, from Java, after applying the arguments to a function that returns a ST
action, one needs to apply that extra argument to the result, which is, as we've seen, nothing but another function.
So, why then, do you have to do this two times in your code? Because IO
is (from the top of my head):
type IO = ST RealWorld
and STMutable
is
type STMutable s x = ST s (Mutable s x)
So the problem lies in your getDatabase
function, which returns an IO
action, which, when executed, returns an ST action, which, when executed, returns a mutable ArrayList.
This is probably not what you wanted. And I guess you struggled a while with the last line in getDatabase
, which should probably read:
list <- ArrayList.new ()
foldM (\xs\x -> ArrayList.add xs x >> return xs) list processedData
the return type being then
IO (Mutable RealWorld ArraList)
or just
IOMutable ArrayList
Another point besides: you should not need to re-introduce split
, it is already there. You could write that line that takes the semicolon delimitted input lines apart:
[ (a,b) | line <- lines fileContent, [a,b] <- ´;´.splitted line ]
See also http://www.frege-lang.org/doc/frege/java/util/Regex.html#Regex.splitted and http://www.frege-lang.org/doc/frege/java/util/Regex.html#Regex.split
Dierks answer makes an interesting point that we should have an utility function for running ST (or IO) actions from Java code. In fact, there is such a function, its name is ST.performUnsafe
(in Frege) and it is known in Haskell as unsafePerformIO
.
In fact, using this function would make Java code more robust against changes in the implementation and is therefore strongly recommended in place of the .apply(null)
code used here.