--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>Writing a high-level wrapper around a Perl library</title>
+ <link rel="stylesheet" href="http://www.merjis.com/css/default.css"
+ type="text/css">
+ </head>
+
+ <body bgcolor="#ffffff">
+ <h1>Writing a high-level wrapper around a Perl library</h1>
+
+ <p>
+ This document discusses the theory and practice behind writing
+ a wrapper around a typical object-oriented Perl library. We
+ use <a href="../html/Pl_LWP_UserAgent.html">Pl_LWP_UserAgent</a>
+ as an example. (This is the high-level wrapper around the
+ <a href="http://search.cpan.org/dist/libwww-perl/lib/LWP/UserAgent.pm">LWP::UserAgent</a>
+ library).
+ </p>
+
+ <p>
+ Don't worry - writing wrappers is really not very hard at all. I
+ hope that you, the reader, will write some wrappers around your
+ favorite Perl libraries and contribute them back to
+ <a href="http://www.merjis.com/developers/perl4caml/">perl4caml</a>
+ development.
+ </p>
+
+ <h2>First steps</h2>
+
+ <p>
+ I'm going to use <a href="http://search.cpan.org/dist/libwww-perl/lib/LWP/UserAgent.pm">LWP::UserAgent</a>
+ as my example throughout this document. Substitute that for whatever
+ library you want to wrap up and call from OCaml.
+ First of all make sure you have the library installed and working
+ under Perl, and make sure you have the manual page for that
+ library in front of you:
+ </p>
+
+<pre>
+perldoc LWP::UserAgent
+</pre>
+
+ <p>
+ or <a href="http://search.cpan.org/dist/libwww-perl/lib/LWP/UserAgent.pm">follow
+ this link</a>.
+ </p>
+
+ <h2>Understanding what we're doing</h2>
+
+ <p>
+ The low-level <a href="../html/Perl.html">Perl</a> module offers
+ two useful functions and a useful datatype which we'll be using
+ extensively. The useful functions are:
+ </p>
+
+ <table class="table">
+ <tr>
+ <th> Function name </th>
+ <th> Perl equivalent </th>
+ <th> Description </th>
+ </tr>
+
+ <tr>
+ <td> <code>call_class_method</code> </td>
+ <td> <code>$obj = LWP::UserAgent->new (args...)</code> </td>
+ <td>
+ <p> Calls a static method or constructor on a class. </p>
+ </td>
+ </tr>
+
+ <tr>
+ <td> <code>call_method</code> </td>
+ <td> <code>$obj->some_method (args...)</code> </td>
+ <td>
+ <p> Calls an instance method on an object. </p>
+ </td>
+ </tr>
+ </table>
+
+ <p>
+ The useful datatype is called the <code>Perl.sv</code> (an
+ abstract type), which represents a scalar value in Perl (anything
+ you would normally write in Perl with a <code>$</code>, including
+ numbers, strings, references and blessed objects). To find out
+ more about "SVs" see <a href="http://www.perldoc.com/perl5.8.0/pod/perlguts.html">the perlguts(3) man page</a>.
+ </p>
+
+ <p>
+ To see how these three things interact, let's create an
+ <code>LWP::UserAgent</code> object and call a method on it:
+ </p>
+
+<pre>
+# #load "perl4caml.cma";;
+# open Perl;;
+# let sv = call_class_method "LWP::UserAgent" "new" [];;
+<i>val sv : Perl.sv = <abstr></i>
+# let agent = call_method sv "agent" [];;
+<i>val agent : Perl.sv = <abstr></i>
+# string_of_sv agent;;
+<i>- : string = "libwww-perl/5.69"</i>
+</pre>
+
+ <p>
+ Note how the variable <code>sv</code> contains the actual Perl
+ object (an instance of <code>LWP::UserAgent</code>). To be
+ quite clear, this is the equivalent of the following Perl code:
+ </p>
+
+<pre>
+$sv = LWP::UserAgent->new ();
+$agent = $sv->agent ();
+print $agent;
+</pre>
+
+ <p>
+ You could actually just continue to use the low-level interface
+ to access Perl objects directly, but there are three problems with
+ this: firstly it's cumbersome because you have to continually
+ convert to and from SVs; secondly it's not type safe
+ (eg. calling <code>string_of_sv</code> might fail if the SV
+ isn't actually a string); thirdly there are more pleasant ways
+ to present this interface.
+ </p>
+
+ <p>
+ Writing a high-level wrapper around these low-level operations
+ is what is described in the rest of this document ...
+ </p>
+
+ <h2>Classes and constructors</h2>
+
+ <p>
+ Our general plan, therefore, will be to create an OCaml class
+ corresponding to <code>LWP::UserAgent</code>, which hides the
+ implementation (the <code>sv</code>, the calls to
+ <code>call_method</code>, and the conversion of arguments
+ to and from SVs). We will also need to write one or more constructor
+ function.
+ </p>
+
+ <p>
+ We will write at least one method for every method exported by
+ the Perl interface. Sometimes we'll write two methods for each
+ Perl method, as in the case where a Perl method is a "getter/setter":
+ </p>
+
+<pre>
+$ua->agent([$product_id])
+ Get/set the product token that is used to identify the user agent
+ on the network. The agent value is sent as the "User-Agent" header
+ in the requests.
+</pre>
+
+ <p>
+ becomes two methods in the OCaml version:
+ </p>
+
+<pre>
+class lwp_useragent sv = object (self)
+ (* ... *)
+ method agent : string
+ method set_agent : string -> unit
+end
+</pre>
+
+ <p>
+ We will also write at least one function for every
+ constructor or static function exported from Perl.
+ </p>
+
+ <p>
+ The OCaml object itself contains the <code>sv</code> which
+ corresponds to the Perl SV (ie. the actual Perl object).
+ </p>
+
+ <p>
+ Here is the shape of our class:
+ </p>
+
+<pre>
+(** Wrapper around Perl [LWP::UserAgent] class.
+ *
+ * Copyright (C) 20xx <i>your_organisation</i>
+ *
+ * $ Id $
+ *)
+
+open Perl
+
+let _ = eval "use LWP::UserAgent"
+
+class lwp_useragent sv = object (self)
+
+ <i>The methods will go here ...</i>
+
+end
+
+(* The "new" constructor. Note that "new" is a reserved word in OCaml. *)
+let new_ ... =
+ ...
+ let sv = call_class_method "LWP::UserAgent" "new" [<i>args ...</i>] in
+ new lwp_useragent sv
+
+<i>Any other static functions will go here ...</i>
+</pre>
+
+ <p>
+ Notice a few things here:
+ </p>
+
+ <ol>
+ <li> There is some ocamldoc describing the class.
+ <li> We "open Perl" to avoid having to prefix everything with
+ <code>Perl.</code>.
+ <li> We <code>eval "use LWP::UserAgent"</code> when the module
+ is loaded. This is required by Perl.
+ <li> The <code>sv</code> (scalar value representing the actual
+ object) is passed as a parameter to the class.
+ </ol>
+
+ <h2>Writing methods</h2>
+
+ <h3>Getters and setters</h3>
+
+ <p>
+ Of all types of methods, getters and setters are the easiest
+ to write. First of all, check the manual page to find out what
+ type the slot is. You'll need to write one get method and
+ one set method. (Rarely you'll find getters and setters which
+ are quasi-polymorphic, for instance they can take a string or
+ an arrayref. You'll need to think more deeply about these,
+ because they require one set method for each type, and the
+ get method can be complicated).
+ </p>
+
+ <p>
+ Here's our getter and setter for the agent slot, described above.
+ The agent is a string:
+ </p>
+
+<pre>
+ method agent =
+ string_of_sv (call_method sv "agent" [])
+ method set_agent v =
+ call_method_void sv "agent" [sv_of_string v]
+</pre>
+
+ <p>
+ Note:
+ </p>
+
+ <ol>
+ <li> The get method is just called <code>agent</code> (not
+ "get_agent"). This is the standard for OCaml code.
+ <li> We use <code>string_of_sv</code> and <code>sv_of_string</code>
+ to convert to and from SVs. This will ensure that the
+ class interface will have the correct type (string), and
+ thus be type safe as far as the calling code is concerned,
+ and also means the caller doesn't need to worry about
+ SVs.
+ <li> The set method called <code>call_method_void</code> which
+ we haven't seen before. This is exactly the same as
+ <code>call_method</code> except that the method is called
+ in a "void context" - in other words, any return value is
+ thrown away. This is slightly more efficient than ignoring
+ the return value.
+ </ol>
+
+ <p>
+ Here's another example, with a boolean slot:
+ </p>
+
+<pre>
+ method parse_head =
+ bool_of_sv (call_method sv "parse_head" [])
+ method set_parse_head v =
+ call_method_void sv "parse_head" [sv_of_bool v]
+</pre>
+
+ <h3>Ordinary methods</h3>
+
+ <p>
+ Other methods are perhaps simpler to wrap than getters and
+ setters. <code>LWP::UserAgent</code> contains an interesting
+ method called <code>request</code> (which actually runs the
+ request).
+ </p>
+
+ <p>
+ What's particularly interesting about this method are the
+ parameter and return value. It takes an <code>HTTP::Request</code>
+ object and returns an <code>HTTP::Response</code>.
+ </p>
+
+ <p>
+ I have already wrapped <code>HTTP::Request</code> and
+ <code>HTTP::Response</code> as modules
+ <a href="../html/Pl_HTTP_Request.html">Pl_HTTP_Request</a> and
+ <a href="../html/Pl_HTTP_Response.html">Pl_HTTP_Response</a>
+ respectively. You should go and look at the code in those
+ modules now.
+ </p>
+
+ <p>
+ If <code>request</code> requires a parameter, what should that
+ parameter be? Naturally it should be the SV corresponding to
+ the <code>HTTP::Request</code> object. To get this, I provided
+ an <code>#sv</code> method on the <code>http_request</code> class.
+ </p>
+
+ <p>
+ And what will <code>request</code> return? Naturally it will
+ return an SV which corresponds to the (newly created inside
+ Perl) <code>HTTP::Response</code> object. We need to wrap
+ this up and create a new OCaml <code>http_response</code> object,
+ <em>containing that SV</em>.
+ </p>
+
+ <p>
+ This is what the final method looks like:
+ </p>
+
+<pre>
+ method request (request : http_request) =
+ let sv = call_method sv "request" [request#sv] in
+ new http_response sv
+</pre>
+
+ <p>
+ It's actually not so complicated.
+ </p>
+
+ <h2>Writing constructors and static functions</h2>
+
+ <p>
+ Constructors are fairly simple, although the <code>new_</code>
+ function inside <code>Pl_LWP_UserAgent</code> is complicated
+ by the many optional arguments which <code>LWP::UserAgent->new</code>
+ can take.
+ </p>
+
+ <p>
+ Here is the guts, omitting all but one of the optional args:
+ </p>
+
+<pre>
+let new_ ?agent (* ... *) () =
+ let args = ref [] in
+ let may f = function None -> () | Some v -> f v in
+ may (fun v ->
+ args := sv_of_string "agent" :: sv_of_string v :: !args) agent;
+(* ... *)
+ let sv = call_class_method "LWP::UserAgent" "new" !args in
+ new lwp_useragent sv
+</pre>
+
+ <p>
+ It works simply enough, first building up a list of <code>sv</code>s
+ corresponding to the arguments, then calling
+ <code>call_class_method</code> to create the Perl object, then
+ returning a constructed OCaml <code>lwp_useragent</code> object
+ containing that <code>sv</code>.
+ </p>
+
+ <h2>Contributing wrappers back to perl4caml</h2>
+
+ <p>
+ If you write a wrapper for a Perl class, particularly one from
+ <a href="http://www.cpan.org/">CPAN</a>, I urge you to
+ contribute it back to the <a
+ href="http://www.merjis.com/developers/perl4caml/">perl4caml</a>
+ development effort. Your contribution enriches the project
+ as a whole, and makes OCaml more useful too.
+ </p>
+
+ <hr>
+ <address><a href="mailto:rich@annexia.org">Richard W.M. Jones</a></address>
+<!-- Created: Thu Oct 16 13:36:59 BST 2003 -->
+<!-- hhmts start -->
+Last modified: Thu Oct 16 14:39:02 BST 2003
+<!-- hhmts end -->
+ </body>
+</html>
*
* Copyright (C) 2003 Merjis Ltd.
*
- * $Id: perl.mli,v 1.7 2003-10-16 08:54:56 rich Exp $
+ * $Id: perl.mli,v 1.8 2003-10-16 13:41:06 rich Exp $
*)
type t
(** Returns an [SV] which is false. *)
val sv_yes : unit -> sv
(** Returns Perl's internal [PL_sv_yes]. (There are some unresolved issues
- * with using this, so use {!sv_true} instead). *)
+ * with using this, so use {!Perl.sv_true} instead). *)
val sv_no : unit -> sv
(** Returns Perl's internal [PL_sv_no]. (There are some unresolved issues
- * with using this, so use {!sv_false} instead). *)
+ * with using this, so use {!Perl.sv_false} instead). *)
(* Actually there are many more types defined than this ... *)
type sv_t = SVt_NULL
val av_of_sv_list : sv list -> av
(** Create an array from a list of [SVs]. *)
val av_push : av -> sv -> unit
-(** Append the [SV] to the end of the array. Same as Perl [push @av, $sv]. *)
+(** Append the [SV] to the end of the array. Same as Perl
+ * [push \@av, $sv]. *)
val av_pop : av -> sv
(** Remove the [SV] at the end of the array and return it. Same as
- * Perl [$sv = pop @av]. *)
+ * Perl [$sv = pop \@av]. *)
val av_shift : av -> sv
(** Remove the [SV] at the beginning of the array and return it. Same as
- * Perl [$sv = shift @av]. *)
+ * Perl [$sv = shift \@av]. *)
val av_unshift : av -> sv -> unit
(** Prepend the [SV] to the start of the array. Same as Perl
- * [unshift @av, $sv]. *)
+ * [unshift \@av, $sv]. *)
val av_length : av -> int
(** Return the length of the [AV]. *)
val av_set : av -> int -> sv -> unit
val av_get : av -> int -> sv
(** Get the i'th element of the [AV]. *)
val av_clear : av -> unit
-(** Remove all elements from the [AV]. Same as Perl [@av = ()]. *)
+(** Remove all elements from the [AV]. Same as Perl [\@av = ()]. *)
val av_undef : av -> unit
-(** Delete the [AV] (and all elements in it). Same as Perl [undef @av]. *)
+(** Delete the [AV] (and all elements in it). Same as Perl [undef \@av]. *)
val av_extend : av -> int -> unit
(** Extend the [AV] so it contains at least [n+1] elements. *)
val av_map : (sv -> 'a) -> av -> 'a list
* this function will return the [SV] for [undef].
*)
val get_av : ?create:bool -> string -> av
-(** Same as {!get_sv} except will return and/or create [@a]. *)
+(** Same as {!Perl.get_sv} except will return and/or create [\@a]. *)
val call : ?sv:sv -> ?fn:string -> sv list -> sv
(** Call a Perl function in a scalar context, either by name (using the [?fn]
* parameter) or by calling a string/CODEREF (using the [?sv] parameter).
*
- * Returns the Perl [SV] containing the result value. (See {!int_of_sv} etc.).
+ * Returns the Perl [SV] containing the result value. (See
+ * {!Perl.int_of_sv} etc.).
*
* If the Perl code calls [die] then this will throw [Perl_failure].
*)