简体   繁体   中英

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

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.

I am sure that others can provide better answers but the reason for what looks like extra parameters that you have to pass when forcing evaluation of the Delayed are the IO and STMutable types.

I came across the same issue here: https://github.com/Frege/FregeFX/blob/f2f548071afd32a08e9b24f6fb6bbece74d4213b/fregefx/src/main/java/org/frege/FregeFX.java#L19-L19

It may be worth considering a utility method "deeplyForced" (?) that shields the Java developer from these details.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM