Showing posts with label interop. Show all posts
Showing posts with label interop. Show all posts

Friday, January 6, 2012

Porting libs to ClojureCLR: an example

Responses to the 2011 ClojureCLR survey gave high priority to the porting of Clojure contrib libs to ClojureCLR.  One action item resulting from an analysis of the responses was to provide examples of the porting process.  As an example, I decided to port the data.json contrib lib, authored by Stuart Sierra.  In looking across all the authorized contrib libs on github.com/clojure, this port was above trivial but below daunting in complexity.  (In a later post, I will analyze all the contrib libs for complexity.)

The following recipe will work for simple ports.

Figure out how the original code works.  This step might be optional on the simplest ports involving just simple interop method renaming.  For this port, I had to modify one algorithm and think through extending a protocol to CLR types.

Create a project for the port.  I did not fork the original data.json project on github; the project needs its own identity and you're not going to be issuing pull requests back to the original.

You are on your own for picking project names and namespace structures for these ports.  I chose the name cljclr.data.json for both the project and the namespace.  You will find my project here.

I would appreciate input on naming these projects.  Here's my current thoughts.  I didn't want to call  this project clr.data.json--I plan to use clr.X.X for ports of contrib libs that have names like java.X.X.  The namespace for data.json is actually clojure.data.json, so I decided to use cljclr.data.json.  I thought of data.json.clr and other variations adding clr as a component, but was offended by having on extra layer of subdirectory injected.  I don't actually like cljclr--I can't read it and I always mistype it.  Help!
In the absence of tools such as leiningen or a working Visual Studio extension, for now you will have to come up with your project structure.  I just used

  • <root>
    •  src
    •  test

as the two main branches, with subdirectories corresponding to the namespaces.

Copy files from the source project.  Usually, you can preserve the basic code structure, relocating files appropriately for your namespace structure.  The data.json project really has only two files of significance:  json.clj and json_test.clj.  I copied them into src/cljclr/data/json.clj and test/cljclr/data/json_test.clj.

Modify file headers. I changed the ns directive in each file to match my namespace structure. If you are interested in things like copyrights, you will have figure out how you want to acknowledge the original author according the license on the original project.

Scan for trouble.  I look for trouble.  Here are some of the things I look for.

  • Imports in the ns directive. Imports of clojure.lang classes are usually okay--I've been carerful to maintain class names and public method names to match Clojure as best I can.  Anything starting  java. will have to be replaced.
  • Interop calls. Scan for (.name,  (name.,  and (CapName/name.  If you are lucky, a simple capitalization will handle many of the (.name calls.
  • Type hints.  Most type hints of classes outside clojure.lang will need to be changed.  Carats feel like sticks.
  • Exceptions.  The only exception class that works unchanged is Exception itself.
  • CapitalLetters. Given that Clojure code is mostly lowercase, anything with CapitalLetters is likely to be trouble.
Do simple renamings.  Hack out the bad imports and add new ones as you work through the code.  Work through each interop call and determine equivalents; ditto for type hints and exceptions.

I adopt a uniform method of marking the places where I make changes.  This makes it easier to update the port as the original lib changes.  You will see lines like this in my code:

 (Char/IsWhiteSpace c) (recur (.Read stream) result)             ;DM: Character/isWhitespace .read   

where the comment shows what was changed from in the original code.

For json.clj, I made the following simple changes.

Method renamings:

FromToCount
(.read(.Read
30
(.append(.Append
14
(.print(.Write
11
(.unread(.Unread
3
(Character/isWhitespace(Char/IsWhiteSpace
3
(.isArray(.IsArray
2
(.charAt(.get_Chars
1
(.toString(.ToString
1

Type renamings:

FromToCount
PushbackReaderPushbackTextReader
9
PrintWriterTextWriter
5
PrintWriterStreamWriter
1
CharSequenceString
1
EOFExceptionEndOfStreamException
5

PrintWriter was mostly used as a type hint, and could have TextWriter substituted.  In one place, it was used new'd.  TextWriter is abstract and can't be constructed, so StreamWriter had to be used in that place.

This project is obviously quite I/O-centric.  Fortunately, there is sufficient commonality between the I/O libraries on the JVM and CLR to make this part of the translation fairly routine.

At this point, you are probably very close to being able to load the file. You can try loading or compiling and let the errors guide you.

Check for reflection warnings.  I find it useful to  (set! *warn-on-reflection* true) at the start of each code file.  I don't do this for performance, but it can catch bad type hints and misnamed methods in the code.   The perf I care about is coding perf, not runtime.

Do the hard parts. The remaining changes were not as easy.  There were two other type renamings, not I/O-related.
java.util.Map => System.Collections.IDictionary
java.util.Collection => System.Collections.ICollection
If you see Java collection types, you are going to have think about the correct alternatives.  In general, we have no way to deal with CLR generic collection types -- most uses of these type names will require the type parameters to be instantiated, so you can't say you want all System.Collections.GenericICollection<T> to be handled. However, most of the generic collection types will provide non-generic interfaces to work with.  That is the solution I chose for this code:

(defn- pprint-json-dispatch [x escape-unicode]
 (cond (nil? x) (print "null")
       (instance? System.Collections.IDictionary x) 
                        (pprint-json-object x escape-unicode)        ;DM: java.util.Map
       (instance? System.Collections.ICollection x) 
                        (pprint-json-array x escape-unicode)         ;DM: java.util.Collection
       (instance? clojure.lang.ISeq x) 
                        (pprint-json-array x escape-unicode)
       :else (pprint-json-generic x escape-unicode)))

Another common trouble spot is dealing with the Java primitive wrapper classes, such as Integer.  You mostly  like won't see many type hints, but you will see calls to static methods.  There is only one in this code.  In read-json-hex-character, I translated (Integer/parseInt s 16) to  (Int32/Parse s NumberStyles/HexNumber).  (I added an import for System.Globalization.NumberStyles.)  I don't have a list of rules for this.  You're on your own.

Extending protocols to Java library classes will also cause you to spend some time thinking.  The primary difficulty here came in extensions to the Write-JSON protocol.  Extensions to things like nil and clojure.lang.Named were unchanged.  Other extensions had to be modifed.

You will have a problem anytime you run into java.lang.Number.  This is a base class for all the primtive numeric wrapper classes such as Integer.  There is no equivalent in the CLR. I created extensions for each primitive numeric type.

(extend System.Byte Write-JSON {:write-json write-json-plain})
(extend System.SByte Write-JSON {:write-json write-json-plain})
(extend System.Int16 Write-JSON {:write-json write-json-plain})
...

Also, java.math.BigInteger and java.math.BigDecimal will need to be translated to clojure.lang.BigInteger and clojure.lang.BigDecimal, and CharSequence usualy goes to String.

The only significant algorithmic change was in write-json-string.  Stuart was careful to properly handle full Unicode, which means not just iterating through the string character by character but dealing with actual Unicode code points as expressed in the UTF-16 encoding used in Java strings.  CLR strings use the same encoding, but the proper way to iterate through Unicode code points is different.  This took the most time for me to translate due to my own ignorance.  I ended up with

(defn- write-json-string [^String s ^PrintWriter out escape-unicode?]
  (let [sb (StringBuilder. ^Integer(count s))]
    (.append sb \")
    (dotimes [i (count s)]
      (let [cp (Character/codePointAt s i)]
        (cond
         ;; Handle printable JSON escapes before ASCII
         (= cp 34) (.append sb "\\\"")
...
         (< 31 cp 127) (.append sb (.charAt s i))
...
         :else (if escape-unicode?
   ;; Hexadecimal-escaped
   (.append sb (format "\\u%04x" cp))
   (.appendCodePoint sb cp)))))
 ...

becoming

(defn- write-json-string [^String s ^TextWriter out escape-unicode?]
  (let [sb (StringBuilder. ^Int32 (count s))
        chars32 (StringInfo/ParseCombiningCharacters s)]
    (.Append sb \")
    (dotimes [i (count chars32)]
      (let [cp (Char/ConvertToUtf32 s (aget chars32 i))]
        (cond
         ;; Handle printable JSON escapes before ASCII
         (= cp 34) (.Append sb "\\\"")
...
         (< 31 cp 127) (.Append sb (.get_Chars s i))
...
         :else (if escape-unicode?
   ;; Hexadecimal-escaped
   (.Append sb (format "\\u%04x" cp))
   (.Append sb  (Char/ConvertFromUtf32 cp))))))
...

Translate your tests.  This is generally easier.  The only changes I had to make were to some setup code for certain tests.  Only a handful of changes were required.

Test. Repair. Repeat.  Good luck.

After just a few iterations, I had all tests passing except one.  In the pretty-print test, I was getting output for some floating point number that were exact integers without a .0 at the end as was required by the test.  Having run into this before, I recalled that I had defined a helper function name fp-str to take care of this.

(defn- write-json-float [x ^TextWriter out escape-unicode?] 
  (.Write out (fp-str x)))                                  

and then modifed the two float-type extends:

(extend System.Double Write-JSON {:write-json write-json-float})
(extend System.Single Write-JSON {:write-json write-json-float})

GREEN!!!!

Celebrate.  
Publish.

I leave these steps to you.


Next steps? Choose something to port and go for it.  


Many of the contrib libs will require significantly less work than this.  There are others that will require almost complete rewrites.  Outside of the core contrib libs, many of the requested ports such as leiningen and the web frameworks are going to a lot of work.

Resources

* data.json:  https://github.com/clojure/data.json
* clrclj.data.json -- https://github.com/dmiller/clrclj.data.json

Tuesday, January 3, 2012

Referring to types

The basics for referring to types for CLR interop are the same for ClojureCLR as for Clojure on the JVM. I will assume you are familiar with interop as covered in http://clojure.org/java_interop or your favorite Clojure intro.

Standard Clojure allows use of the symbols int, double, float, etc. in type hints to refer to the corresponding primitive types. ClojureCLR allows this and extends this to the numeric types present in the CLR but not in the JVM: uint, ulong, etc. Similarly, the shorthand array references such ints and doubles work, and are joined by uints, ulongs, etc.

The CLR is not C#.
Do not let the presence of int and company for type hinting put you in a C# frame of mind. When specifying generic types, you cannot use C# notation:

System.Collections.Generic.IList<int>

Instead, you must use the actual CLR type name:

System.Collections.Generic.IList`1[System.Int32]

Remember that floatbecomes System.Single; I've had to dope-slap myself on that one a few times.

Clojure uses symbols to refer to types. This works on the JVM because package-qualified class names are lexically compatible with symbols. Not so here. The backquote and square brackets in the type name shown above cannot be part of a symbol name. If you type that string of characters into the REPL, you will get

user=> System.Collections.Generic.IList`1[System.Int32]
CompilerException System.InvalidOperationException:
  Unable to resolve symbol: System.Collections.Generic.IList in this context
   at ...
1
[System.Int32]

The input string is parsed as separate entities:

System.Collections.Generic.IList
`1
[System.Int32]

Not what was intended.

In addition to backquotes and square brackets, a fully-qualified type name can contain an assembly identifier--that involves spaces and commas. In fact, CLR typenames can contain arbitrary characters. Backslashes can escape characters that do have special meaning in the typename syntax (comma, plus, ampersand, asterisk, left and right square bracket, left and right angle bracket, backslash).

To allow symbols to contain arbitrary characters, ClojureCLR extends the reader syntax using "vertical bar quoting". Vertical bars are used in pairs to surround the name or a part of the name of a symbol. Any characters between the vertical bars are taken to be part of the symbol name. For example,

|A(B)|
A|(|B|)|
A|(B)|

all mean the symbol whose name consists of the four characters A, (, B, and ).  I consider only the first one to be readable; quoting the entire name is to be preferred.  To quote the IList example above, you would write

|System.Collections.Generic.IList`1[System.Int32]|

To include a vertical bar in a symbol name that is |-quoted, use a doubled vertical bar.

|This has a vertical bar in the name ... || ...<>@#$@#$#$|

With this mechanism can we make a symbol for a fully-qualified typename, such as:

(|com.myco.mytype+nested, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b14a123334343434|/DoSomething x y)

or

(reify 
 |AnInterface`2[System.Int32,System.String]| 
 (m1 [x] ...)
 I2
 (m2 [x] ...))


There are a number of things you should note about |-quoting and about generic type references.

First, what |-quoting does is to prevent characters from stopping the token scan. Checks on symbol validity that follow token scanning are still in effect. These include not starting with a digit, containing a non-intial colon, and a few others. When scanning A(B), the left parenthesis stops scanning the token that begins with A.  When scanning |A(B)|, the left and right parentheses do not stop the scan.  However, scanning |ab:| is the same as scanning ab:, a colon being a perfectly fine token constituent.  However, the colon at the end is a no-no, and so the token is rejected and the reader throws an exception.

(I could have taken more radical approach and allowed |ab:|.   One then gets into all kinds of edge cases that I didn't want to solve.  I feel that a more radical quoting approach requires consultation and agreement with the Clojure powers-that-be.)


Second, be careful with namespaces for symbols. Any / appearing |-quoted does not count as a namespace/name separator. If you have special characters in either the namespace name or the symbol name, you must |-quote either one separately. Thus,

(namespace 'ab|cd/ef|gh)    ;=> nil
(name 'ab|cd/ef|gh)         ;=> "abcd/efgh"
 
(namespace 'ab/cd|ef/gh|ij) ;=> "ab"
(name 'ab/cd|ef/gh|ij)      ;=> "cdef/ghij"

Rather than

ab/cd|ef/gh|ij

it would be more readable to write

ab/|cdef/ghij|

Third, you will usually need to fully namespace-qualify generic types and their parameters.  For example,

|System.Collections.Generic.IList`1[System.Int32]|

works as a type reference while

|System.Collections.Generic.IList`1[Int32]|

does not.

Also, aliasing via import is not of much help.  After eval'ing

(import '|System.Collections.Generic.IList`1|)

the symbol |IList`1| will refer in the current namespace to the generic type |System.Collections.Generic.IList`1|, but that is of no help in referring to instantiated IList types. You cannot then refer to

|IList`1[System.Int32]|

Perhaps someday we will introduce a more compositional approach to generic types and symbols that will accommodate this.

Fourth, if you are familiar with |-quoting in Common Lisp, the ClojureCLR mechanism is not as inclusive.   In CL you could include a literal vertical bar in a symbol name with backslash-escaping: abc\|123 has name “abc|123”. CL has  \-escaping for characters in symbol tokens; ClojureCLR does not.


Finally, note that when printing with *print-dup* true, symbols with 'bad' characters will be |-quoted.

Thursday, December 29, 2011

Calling generic methods

To take advantage of some of the recent API goodness coming from Microsoft, interoperating with generic methods is a must.  Here's an example from System.Linq.Reactive.Observable:

public static IObservable<TResult> Generate<TState, TResult>( 
    TState initialState, Func<TState, bool> condition, 
    Func<TState, TState> iterate, 
    Func<TState, TResult> resultSelector, 
    Func<TState, TimeSpan> timeSelector )

and from the land of Linq, in System.Linq.Enumerable:

public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>( 
    this IEnumerable<TSource> source, 
    Func<TSource, TKey> keySelector, 
    Func<TSource, TElement> elementSelector, 
    Func<TKey, IEnumerable<TElement>, TResult> resultSelector, 
    IEqualityComparer<TKey> comparer )

Much of the goodness of Linq, Reactive Framework and others comes from the ability to chain together generic method calls with minimum specification of type arguments for those calls.   If you are in a statically-typed language such as C#, there are plenty of types floating around to do inferencing on. Of course, with dynamic, C# is not quite the paragon of static typing it once was. The mechanisms C# uses for dynamic call sites surface in the Dynamic Language Runtime and so are available to the wider world. Following the path blazed by IronPython, we have recently enhanced ClojureCLR's ability to interoperate with generic methods.

Start a REPL and start typing:

(import 'System.Linq.Enumerable)
(def r1 (Enumerable/Where [1 2 3 4 5] even?))
(seq r1)                                        ;=> (2 4)

"But of course", you say. Not so fast; under the covers there is merry mischief.

The generic method Where is overloaded with the following signatures.

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, int, bool> predicate)

In the Where call above, the [1 2 3 4 5] is a clojure.lang.PersistentVector. This class implements IEnumerable<Object> and so matches the IEnumerable<TSource> in the first argument position.

The value of even? is a clojure.lang.IFn, more specifically a clojure.lang.AFn. In ClojureCLR, clojure.lang.AFn implements interfaces allowing it take take part in the DLR's generic method type inferencing protocol. One interface answers queries about the arities supported by the function. The function even? reports that it supports one argument and does not support two arguments. Therefore, it supports casting to Func<TSource, bool> but not to Func<TSource, int, bool>, allowing discrimination between the two overloads. (Func<TSource, bool> is a delegate class representing a method taking one argument of type TSource and returning a value of type Boolean.)

From this information, we can pick the overload of Where to use. The value returned by the Where call is of type System.Linq.Enumerable+WhereEnumerableIterator<Object>. This type implements IEnumerable and seq can do the expected thing to it.

If we had a function f that supports one and two arguments, say

(defn f
  ([x] x)  
  ([x y] [x y]))

the type inferencing illustrated in the previous example would not work.

(Enumerable/Where [1 2 3 4 5] f)   ;=> FAIL!
The error messages is quite clear:

ArgumentTypeException Multiple targets could match:  
  Where(IEnumerable`1, Func`2),
  Where(IEnumerable`1, Func`3)

There are two ways around this. The simplest is to use an anonymous function of one argument that calls f:

(Enumerable/Where [1 2 3 4 5] #(f %1))

The second way is to declare the types explicitly. Macros sys-func and sys-action are available to create a function with delegate type matching  System.Func<,...> and System.Action<,...>, respectively.

(Enumerable/Where [1 2 3 4 5] (sys-func [Object Boolean] [x] (f x))))

The first way clearly is preferable. However, when type inferencing does not suffice, sys-func and sys-action can be used. (If delegates of types other then Func<,...> and Action<,...> are required, gen-delegate is available.)

Not just Clojure data structures can participate as sources:

(def r2 (Enumerable/Range 1 10))
(seq r2) ;=> (1 2 3 4 5 6 7 8 9 10)
(seq (Enumerable/Where r2 even?)) ;=> (2 4 6 8 10) 

In fact, any of the following calls will work:

(Enumerable/Where r2 (sys-func [Int32 Boolean] [x] (even? x)))
(Enumerable/Where r2 (sys-func [Object Boolean] [x] (even? x)))
(Enumerable/Where (seq r2) even?)

There are situations where you need to supply type arguments explicitly to a generic method. For example, the following fails:

(def r3 (Enumerable/Repeat 2 5) ;=> FAILS!

The error message states:

InvalidOperationException Late bound operations cannot be performed 
on types or methods for which ContainsGenericParameters is true.

We can cause the type arguments on the Repeat<T> method to be filled in using the type-args macro:

(def r3 (Enumerable/Repeat (type-args Int32) 2 5))
(seq r2) ;=> (2 2 2 2 2)

If you'd like to do Linq-style method concatenation, don't forget the threading macro:

(seq (-> (Enumerable/Range 1 10)
            (Enumerable/Where even?)
            (Enumerable/Select #(* %1 %1))))  ;=> (4 16 36 64 100)

Of course, if you'd like to have the Linq syntax that is available in C#, you are free to write a macro.

Tuesday, December 27, 2011

Working with enums


Working with enum types is straightforward. We have provided a few core methods to simplify certain operations.

Accessing/creating enum values

An enum type is a value type derived from System.Enum. The named constants in an enum type are implemented as static fields on the type.  For example, System.IO.FileMode, defined in C# as

public enum FileMode
{
    Append = 6,
    Create = 2,
    CreateNew = 1,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5
}

has an MSIL implementation more-or-less equivalent to

[Serializable]
public sealed class FileMode : System.Enum
{
    public static System.IO.FileMode CreateNew = 1;
    public static System.IO.FileMode Create = 2;
    public static System.IO.FileMode Open = 3;
    public static System.IO.FileMode OpenOrCreate 4;
    public static System.IO.FileMode Truncate = 5;
    public static System.IO.FileMode Append = 6;
}

Thus, we can use our regular static-field access interop syntax to retrieve these values:

(import 'System.IO.FileMode) ;=> System.IO.FileMode
FileMode/CreateNew           ;=> CreateNew

These are not integral-type values. They retain the enumeration type.

(class FileMode/CreateNew) ;=> System.IO.FileMode

You can convert them to an integer value if you desire:

(int FileMode/CreateNew) ;=> 1

If you want to convert from an integer value to an enumeration value, try:

(Enum/ToObject FileMode 4) ;=> OpenOrCreate

If you want convert from the name of an integer to an enumeration value, the enum-val method will work with strings or anything that name works on:

(enum-val FileMode "CreateNew") ;=> CreateNew
(enum-val FileMode :CreateNew)  ;=> CreateNew


Working with bit fields

Enumeration types that have the Flags attribute are often used to represent bit fields. For convenience, we provide methods bit-or and bit-and to to combine and mask bit field values. For example, System.IO.FileShare has the Flags attribute. It is defined as follows:

[Serializable, ComVisible(true), Flags]
public enum FileShare
{
 Delete = 4,
 Inheritable = 0x10,
 None = 0,
 Read = 1,
 ReadWrite = 3,
 Write = 2
}

Use enum-or to combine values.

(import 'System.IO.FileShare)
(enum-or FileShare/Read FileShare/Write) ;=> ReadWrite

Use enum-and to mask values.

(def r (enum-or FileShare/ReadWrite FileShare/Inheritable))
(= (enum-and r FileShare/Write) FileShare/Write) ;=> true
(= (enum-and r FileShare/Write) FileShare/None)  ;=> false
(= (enum-and r FileShare/Delete) FileShare/None) ;=> true


You can also use the HasFlag method to test if a bit is set:

(.HasFlag r FileShare/Write)  ;=> true
(.HasFlag r FileShare/Delete) ;=> false