【问题标题】:How to pass a list of integers from Clojure to a Frege function?如何将整数列表从 Clojure 传递给 Frege 函数?
【发布时间】:2015-12-20 08:46:26
【问题描述】:

受上一个问题what is the easiest way to pass a list of integers from java to a frege function? 和@Ingo 答案中的评论的启发,我尝试了

(Foo/myfregefunction (java.util.List. [1,2,3,4]))

但是得到(ctor = 构造函数):

CompilerException java.lang.IllegalArgumentException: No matching ctor found for interface java.util.List

有什么想法吗?至少 java.util.List 没有产生 ClassCastException;这是否意味着这是在正确的轨道上?

我可以从 Clojure 向 Frege 发送几乎任何 Java 集合类型,请参阅 Converting Clojure data structures to Java collections

顺便说一句,使用普通的(Foo/myfregefunction [1,2,3,4]) 会产生ClassCastException clojure.lang.PersistentVector cannot be cast to free.runtime.Lazy,@Ingo 指出,“clojure 列表不是 frege 列表。”转换为 java.util.ArrayList 时的类似响应。

在弗雷格方面,代码类似于

module Foo where

myfregefunction :: [Int] -> Int
-- do something with the list here

【问题讨论】:

    标签: java clojure frege


    【解决方案1】:

    好的,不知道 Clojure,但是从您提供的链接中,我认为您需要提供可实例化类的名称(即 java.util.ArraList),因为 java.util.List 只是一个接口,因此无法构造。

    对于弗雷格方面,在这种情况下是消费者,假设接口就足够了。

    整个事情变得有点复杂,因为弗雷格知道 java 列表是可变的。这意味着不存在纯函数

    ∀ s a. Mutable s (List a) → [a]
    

    每次用纯语言编写这样的函数的尝试都必须失败,并且会被编译器拒绝。

    相反,我们需要一个 ST 操作来包装纯部分(在本例中,您的函数 myfregefunction)。 ST 是使处理可变数据成为可能的 monad。这将是这样的:

    import Java.Util(List, Iterator)   -- java types we need
    
    fromClojure !list = 
        List.iterator list >>= _.toList >>= pure . myfregefunction
    

    从 clojure,你现在可以调用类似的东西(如果我弄错了 clojure 语法,请原谅我(欢迎编辑)):

    (frege.prelude.PreludeBase$TST/run (Foo/fromClojure (java.util.ArrayList. [1,2,3,4])))
    

    这种通过 Java 进行的接口有两个缺点,恕我直言。一方面,我们引入了可变性,弗雷格编译器不允许我们忽略它,因此接口变得更加复杂。此外,列表数据将被复制。我不知道 Clojure 是如何做到的,但至少在 Frege 方面,有这段代码会遍历迭代器并将数据收集到 Frege 列表中。

    因此,更好的方法是让 Frege 了解 clojure.lang.PersistentVector 是什么,并直接在 Frege 中处理 clojure 数据。我知道有人用 clojure 持久哈希映射这样做了,所以我想应该可以对列表做同样的事情。

    (此时我不得不指出,贡献一个经过深思熟虑的 Clojure/Frege 接口库是多么有价值!)

    编辑:正如@0dB 的自我回答所暗示的,他即将实施前面段落中提到的卓越解决方案。我鼓励每个人都以赞成票来支持这项崇高的事业。

    第三种方法是直接在 Clojure 中构造 Frege 列表。

    【讨论】:

    • 为了完整起见:您还应该能够使用 Clojure 中的 to-array 并将其视为 Frege 端的 JArray a。当您可以直接处理数组时,这很好,但如果您需要列表 API 进行处理,这可能不是最有效的方式。
    • 无需调用(java.util.ArrayList. [1,2,3,4]) - [1,2,3,4] 已经实现 List 并且不可变。如果有帮助的话,clojure 中的[] 也实现了 Iterable。
    • @Ingo,是的,我曾尝试切换到[Long],但我的一个功能使用replicate ∷ Int → α → [α]?。 @noisesmith,明白了,谢谢。 @Ingo,嗯,切换到 JArray Int 意味着(对于另一个函数)研究如何使用 JArray Int 而不是 [Int] 处理递归(例如,寻找一个空的 JArray 而不是 [])以及如何翻译模式匹配(x:xs)。或者我可以将JArray Int 转换为[Int]? (这就是包装器的作用?!)但是,由于我现在有一个可行的临时解决方案(谢谢,伙计们!),我可能会花更少的时间来改进它,而是检查亚当的方法。
    • @Ingo、@noisesmith、@Dierk,看来我要搞定了。注释 native clojure.lang.IPersistentVector,使用 pure native 获取所需的方法,并将 Frege 列表的转换函数添加到 IPersistentVector 并返回,现在 (ClojureInterface/fromClojure [7,8,11,13,17]) 产生 [8 11 13 17],无需在 Clojure 端进行包装或展开。 (弗雷格函数fromClojure 只是将tail 应用于输入。)。 @Ingo,当它准备好发布时,我应该在你的答案中添加并标记一些编辑吗?
    • @0dB 很高兴听到这个消息!您可以简单地添加一个答案,我会编辑我的答案以指出您已经找到了更好的解决方案。还请考虑在 OSS 存储库中维护您的 clojure-bindings,每当有人遇到类似问题时,我们都会很乐意指出。
    【解决方案2】:

    根据@Ingo 的回答,

    更好的方法是让 Frege 了解 clojure.lang.PersistentVector 是什么,并直接处理 Frege 中的 clojure 数据。

    和 cmets 以及 Adam Bard 的 PersistentMap 解决方案,我想出了一个可行的解决方案:

    module foo.Foo where
    

    [EDIT] 正如 Ingo 所指出的,作为 ListView 的一个实例,我们可以理解列表,头,尾,......

    instance ListView PersistentVector
    

    我们需要注释一个 Clojure 类以在 Frege 中使用(pure native 基本上使 Java 方法对 Frege 可用,而不需要任何 monad 来处理可变性,这可能是因为——通常——数据在 Clojure 中也是不可变的):

    data PersistentVector a = native clojure.lang.IPersistentVector where
      -- methods needed to create new instances
      pure native empty clojure.lang.PersistentVector.EMPTY :: PersistentVector a
      pure native cons :: PersistentVector a -> a -> PersistentVector a
      -- methods needed to transform instance into Frege list
      pure native valAt :: PersistentVector a -> Int -> a
      pure native length :: PersistentVector a -> Int
    

    现在有一些函数被添加到这个数据类型中,用于从 Frege 列表或其他方式创建 Clojure 向量:

      fromList :: [a] -> PersistentVector a
      fromList = fold cons empty
    
      toList :: PersistentVector a -> [a]
      toList pv = map pv.valAt [0..(pv.length - 1)]
    

    注意我对“点”符号的使用;请参阅@Dierk,The power of the dot 的精彩文章。

    [编辑] 对于ListView(以及PersistentVector 在弗雷格的一些乐趣),我们还需要实现unconsnulltake(对于这里的快速和肮脏的解决方案,我很抱歉;我会尝试尽快解决):

      null :: PersistentVector a -> Bool
      null x = x.length == 0
    
      uncons :: PersistentVector a -> Maybe (a, PersistentVector a)
      uncons x
        | null x = Nothing
        -- quick & dirty (using fromList, toList); try to use first and rest from Clojure here
        | otherwise = Just (x.valAt 0, fromList $ drop 1 $ toList x)
    
      take :: Int -> PersistentVector a -> PersistentVector a
      -- quick and dirty (using fromList, toList); improve this
      take n = fromList • PreludeList.take n • toList
    

    在我上面的快速而肮脏的解决方案中,注意使用PreludeList.take 以避免在PersistentVector 创建的命名空间中调用take,以及我如何不必为fromListtoList、@ 添加前缀987654338@和empty

    使用此设置(如果您不想直接在 Frege 中对 PersistentVector 执行任何操作,则可以省略 unconsnulltake 以及顶部的 instance 声明) 你现在可以调用一个 Frege 函数,该函数通过正确包装一个列表并返回一个列表:

    fromClojure :: PersistentVector a -> PersistentVector a
    fromClojure = PersistentVector.fromList • myfregefn • PersistentVector.toList
    
    -- sample (your function here)
    myfregefn :: [a] -> [a]
    myfregefn = tail
    

    在 Clojure 中,我们只需调用 (foo.Foo/fromClojure [1 2 3 4]) 并通过 myfregefn 所做的任何处理(在此示例中为 [2 3 4])返回一个 Clojure 向量。如果 myfregefn 返回 Clojure 和 Frege 都能理解的内容(StringLong、...),请忽略 PersistentVector.fromList(并修复类型签名)。试试这两种方法,tail 如上所述用于返回列表,head 用于返回,例如 LongString

    对于包装器和您的 Frege 函数,请确保类型签名“匹配”,例如。 G。 PersistentVector a 匹配 [a]

    向前迈进:我这样做是因为我想将我的一些 Clojure 程序移植到 Frege,“一次一个函数”。我确信我会遇到一些我必须研究的更复杂的数据结构,而且我仍在研究 Ingo 提出的改进建议。

    【讨论】:

    • 嗯,这是一个好的开始,至少。不过,它肯定可以增强。首先,fromList 就是fold PV.cons PV.empty。接下来,我会将 PV 包装在类似数据结构的迭代器中(查看 frege.data.Iterators 以获得灵感)。在此基础上,我们可以实现各种类型的类,如 Functor、Foldable,甚至 Monad,如果你愿意,还可以实现 Frege 特定的 ListView 和 ListMonoid 类。这将为我们提供 fmap、fold、head、tail、null、uncons、toList、(++) 等等,并能够在列表推导中使用(包装的)PV。
    • @Ingo,我缩短了答案,以便更容易理解“临时解决方案”(它在技术上解决了我最初的问题中描述的问题,但从架构的角度来看“还没有” -of-view) 并且会在我实施包装 PV 后报告。
    • 更像前一个。假设您有类似于 Data.Iterators 中的 ArrayIterator 的东西,但基于 PV。根据我从您那里看到的情况,API 似乎非常类似于数组:我们知道 PV 的长度,并且可以 O(1) 访问每个索引处的元素(如果我错了,请纠正我)。现在,在迭代器中,我们维护:对原始 PV 的引用、长度和“当前”索引。然后,例如,null 只是检查当前索引是否 >= PV 长度。 length 只是 PV.length - 当前索引。 tail 只是推进索引,等等。 (1/2)
    • 通过这种方式,可以实现大多数列表功能,并且它们无需创建真正的列表即可工作。但是,因为我们在 PV 中也有 cons,所以我们可以做更多的事情(假设 cons 是非破坏性的)。我们的想法是尽可能多态地编写代码,使用 ListView、Monoid、Foldable、Traversable、Functor、Applicative、Monad 等类型类。或包裹在迭代器中的 PV。然后,您只需要一个从 Clojure 获取 PV、包装它并关闭它的函数!
    • @Ingo,是的,我看到关于使用Iterator 并具有多态性(然后能够处理 PV 并列出相同的内容)应该很容易。好的,看起来如果没有显式包装我们将无法做到。
    猜你喜欢
    • 1970-01-01
    • 2020-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多