clojureclojureclr

Write a lazy sequence of lines to a file in ClojureClr without reflection


I have a large lazy seq of lines that I want to write to a file. In C#, I would use System.IO.File/WriteAllLines which has an overload where the lines are either string[] or IEnumerable<string>.

I want to do this without using reflection at runtime.

(set! *warn-on-reflection* true)

(defn spit-lines [^String filename seq]
  (System.IO.File/WriteAllLines filename seq))

But, I get this reflection warning.

Reflection warning, ... - call to WriteAllLines can't be resolved.

In general I need to know when reflection is necessary for performance reasons, but I don't care about this particular method call. I'm willing to write a bit more code to make the warning go away, but not willing to force all the data into memory as an array. Any suggestions?


Solution

  • Here are two options to consider, depending on whether you are using Clojure's core data structures.

    Convert from a seq to an IEnumerable<string> with Enumerable.Cast from LINQ

    This option will work for any IEnumerable that contains only strings.

    (defn spit-lines [^String filename a-seq]
      (->> a-seq
           (System.Linq.Enumerable/Cast (type-args System.String))
           (System.IO.File/WriteAllLines filename)))
    

    Type Hint to force the caller to supply IEnumerable<string>

    If you want to use a type hint, do this. But watch out, the clojure data structures do not implement IEnumerable<String>, so this could lead to a runtime exception.

    ^|System.Collections.Generic.IEnumerable`1[System.String]|
    

    Wrapping the full CLR name of the type in vertical pipes (|) lets you specify characters that are otherwise illegal in Clojure syntax.

    (defn spit-lines [^String filename ^|System.Collections.Generic.IEnumerable`1[System.String]| enumerable-of-string]
      (System.IO.File/WriteAllLines filename enumerable-of-string))
    

    Here's the exception from (spit-lines "filename.txt" #{}) when passing a set to the type-hinted version:

    System.InvalidCastException: Unable to cast object of type 'clojure.lang.PersistentTreeSet' to type 'System.Collections.Generic.IEnumerable`1[System.String]'.

    More information about specifying types.