Search code examples
javacompilationfrege

Calling Frege From Java Doesn't Match Number of Parameters


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.)


Solution

  • 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

    Addition

    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.