从 Java 调用 Frege 与参数数量不匹配

Calling Frege From Java Doesn't Match Number of Parameters

我有如下弗雷格代码(主要是注意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)

然后从Java我想这样定义下面的函数(其中DATABASE是一个字符串常量):

private void readDatabase() {
    myList = Delayed.<ArrayList<TTuple2>>forced(
            fregeHelper.FregeCode.getDatabase(DATABASE));
}

然而,这给了我一个 java.lang.ClassCastException: frege.prelude.PreludeBase$TST cannot be cast to java.util.ArrayList

通过实验,我不得不将代码更改为

private void readDatabase() {
    fighters = Delayed.<ArrayList<TTuple2>>forced(
            fregeHelper.FregeCode.getDatabase(DATABASE)
            .apply(null)
            .apply(null)
            );
}

我把 null 放在后者 apply 中只是为了表明我传入的内容无关紧要。我不知道为什么我必须应用该函数三次(我不能立即强制求值).有什么办法可以删除应用程序或对为什么需要它们进行一些合理化? (注意:使用 .result() 对这种情况没有帮助。)

我相信其他人可以提供更好的答案,但是在强制评估延迟时必须传递额外参数的原因是 IO 和 STMutable 类型。

我在这里遇到了同样的问题:https://github.com/Frege/FregeFX/blob/f2f548071afd32a08e9b24f6fb6bbece74d4213b/fregefx/src/main/java/org/frege/FregeFX.java#L19-L19

可能值得考虑一种实用方法 "deeplyForced" (?) 来保护 Java 开发人员免受这些细节的影响。

这样做的原因是,在此实现中,ST 操作表示为 "function object",其中实现伴随函数的方法忽略它的参数。

回忆一下ST的定义可能有助于理解:

abstract data ST s a = ST (s -> a) where ...

首先要注意的是 data 实际上会写成 Haskell 中的 newtype。所以ST只是类型重命名,即一个ST动作其实是一个函数

但是,abstract 确保您无法查看 ST 数据构造函数,因此不能 运行 直接从 Frege 代码中调用函数。

这解释了为什么从 Java 开始,在将参数应用到 return 是一个 ST 操作的函数之后,需要将该额外参数应用到结果,即,正如我们所见,只不过是另一个功能。

那么,为什么要在代码中执行两次呢?因为 IO 是(从我的头顶):

type IO = ST RealWorld

并且STMutable

type STMutable s x = ST s (Mutable s x)

所以问题出在你的getDatabase函数上,它return是一个IO动作,当执行时,return是一个ST动作,它,执行时,return 是一个可变的 ArrayList。

这可能不是您想要的。我猜你在 getDatabase 中的最后一行挣扎了一段时间,它可能应该是:

list <- ArrayList.new ()
foldM (\xs\x -> ArrayList.add xs x >> return xs) list processedData 

当时的return类型

IO (Mutable RealWorld ArraList)

或者只是

IOMutable ArrayList

另外一点:你应该不需要重新引入split,它已经存在了。您可以编写将分号分隔的输入行分开的行:

[ (a,b) | line <- lines fileContent, [a,b] <- ´;´.splitted line ]

另见 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 的回答提出了一个有趣的观点,即我们应该为 Java 代码中的 运行ning ST(或 IO)操作提供一个效用函数。事实上,有这样一个函数,它的名字是 ST.performUnsafe(在 Frege 中),在 Haskell 中被称为 unsafePerformIO

事实上,使用此函数会使 Java 代码对实现中的更改更加健壮,因此强烈建议代替此处使用的 .apply(null) 代码。