简体   繁体   中英

How to pass a list of integers from Clojure to a Frege function?

Inspired by the previous question what is the easiest way to pass a list of integers from java to a frege function? and a comment in the answers by @Ingo, I tried

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

but get (ctor = constructor):

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

Any ideas? At least java.util.List didn't yield a ClassCastException; does this mean this is on the right track?

I can send Frege pretty much any Java collection type from Clojure, see Converting Clojure data structures to Java collections .

BTW, using plain (Foo/myfregefunction [1,2,3,4]) instead yields ClassCastException clojure.lang.PersistentVector cannot be cast to free.runtime.Lazy , to which @Ingo points out, “A clojure list is not a frege list.” Similar response when casting as java.util.ArrayList .

On the Frege side, the code is something like

module Foo where

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

Ok, not knowing Clojure, but from the link you gave I take it you need to give the name of an instantiable class (ie java.util.ArraList ) since java.util.List is just an interface and so cannot be constructed.

For the Frege side, which is the consumer in this case, it suffices to assume the interface.

The whole thing gets a bit complicated, since Frege knows that java lists are mutable. This means that there cannot exist a pure function

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

and every attempt to write such a function in a pure language must fail and will be rejected by the compiler.

Instead, what we need is a ST action to wrap the pure part (in this case, your function myfregefunction ). ST is the monad that makes it possible to deal with mutable data. This would go like this:

import Java.Util(List, Iterator)   -- java types we need

fromClojure !list = 
    List.iterator list >>= _.toList >>= pure . myfregefunction

From clojure, you can now call something like (forgive me if I get the clojure syntax wrong (edits welcome)):

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

This interfacing via Java has two disadvantages, IMHO. For one, we introduce mutability, which the Frege compiler doesn't allow us to ignore, so the interface gets more complicated. And in addition, list data will be duplicated. I don't know how Clojure is doing it, but on the Frege side, at least, there is this code that goes over the iterator and collects the data into a Frege list.

So a better way would be to make Frege aware of what a clojure.lang.PersistentVector is and work directly on the clojure data in Frege. I know of someone who has done it this way with clojure persistent hash maps, so I guess it should be possible to do the same for lists.

(At this point I cannot but point out how valuable it would be to contribute a well thought-out Clojure/Frege interface library!)

Edit: As the self-answer from @0dB suggests, he's about to implement the superior solution mentioned in the previous paragraphs. I encourage everyone to support this noble undertaking with upvotes.

A third way would be to construct the Frege list directly in Clojure.

Based on the answer by @Ingo,

a better way would be to make Frege aware of what a clojure.lang.PersistentVector is and work directly on the clojure data in Frege.

and comments thereto as well as the solution for PersistentMap by Adam Bard, I came up with a working solution:

module foo.Foo where

[EDIT] As Ingo points out, being an instance of ListView gives us list comprehension, head, tail, …

instance ListView PersistentVector

We need to annotate a Clojure class for use in Frege ( pure native basically makes the Java methods available to Frege without needing any monad to handle mutability, possible because—in general—data is immutable in Clojure, too):

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

Now there follow some functions that are added to this data type for creating a Clojure vector from Frege list or the other way around:

  fromList :: [a] -> PersistentVector a
  fromList = fold cons empty

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

Note my use of the "dot" notation; see the excellent article by @Dierk, The power of the dot .

[EDIT] For ListView (and some fun in Frege with PersistentVector ) we need to also implement uncons , null and take (sorry for the quick & dirty solutions here; I will try to fix that soon):

  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

In my quick & dirty solution above, note the use of PreludeList.take to avoid calling take in the namespace that PersistentVector creates, and how I did not have to prefix fromList , toList , cons and empty .

With this setup (you can leave out uncons , null and take as well as the instance declaration at the top, if you don't want to do anything with PersistentVector in Frege directly) you can now call a Frege function that takes and returns a list by wrapping it properly:

fromClojure :: PersistentVector a -> PersistentVector a
fromClojure = PersistentVector.fromList • myfregefn • PersistentVector.toList

-- sample (your function here)
myfregefn :: [a] -> [a]
myfregefn = tail

In Clojure we just call (foo.Foo/fromClojure [1 2 3 4]) and get a Clojure vector back with whatever processing myfregefn does (in this example [2 3 4] ). If myfregefn returns something that both Clojure and Frege understand ( String , Long , …), leave out the PersistentVector.fromList (and fix the type signature). Try both out, tail as above for getting back a list and head for getting back, say, a Long or a String .

For the wrapper and for your Frege function, make sure the type signatures 'match', eg PersistentVector a matches [a] .

Moving forward: I am doing this because I would like to port some of my Clojure programs to Frege, “a function at a time“. I am sure I will be encountering some more complex data structures that I will have to look into, and, I am still looking into the suggestions by Ingo to improve things.

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