views:

150

answers:

2

I'm running up against a problem in understanding the CLOS way of handling file access within a class. In c++ I would be able to do this:

class Foo {
   Foo (string filename);  // opens the file (my_file) requested by the filename
   ~Foo ();  // close the file

   FILE * my_file;  // a persistent file-handle
   DataStruct my_data;  // some data

   void ParseData ();  // will perform some function on the file and populate my_data
   DataStruct * GetData () { return &my_data; }  // accessor to the data
};

What I'd like to point out is that PraseData() will be called multiple times, and each time a new block of data will be parsed from the file and my_data will be altered.

I'm trying to perform the same trick in CLOS - create all the generic methods to parse the data, load the file, read headers, etc. as well as the class definition which I have as:

(defclass data-file ()
  ((filename :initarg :filename :accessor filename)
   (file :accessor file)
   (frame :accessor frame)))

In the "constructor" (i.e. initialize-instance) I open the file just as my c++ idiom. Then I have access to the data and I can parse the data as before. However, I'm told that using a "destructor" or (finalize) method to close the file is not idiomatic CLOS for handling this type of situation where I need the file to be around so I can access it outside of my data-file methods.

I'm going to define a function that loads a data-file, and then performs a series of analyses with its data, and then hopefully close it. What's a way to go about doing this? (I'm assuming a macro or some type of closure would work in here, but I'm not familiar enough with the lisp way to decide what is needed or how to implement it).

+6  A: 

One option is to have the stream as a slot instead of the filename, and then scope it with WITH-OPEN-FILE:

(with-open-file (stream file)
  (let ((foo (make-instance 'foo :stream stream)))
    (frob foo)
    (...other processing of foo...)))

Then your stream will be closed automatically.

Xach
the early bird gets the answer
Rainer Joswig
+3  A: 

I think I would lean towards making classes only to store complete authoritative data (what you call DataStruct?).

You don't really need a special class for "loading + storage of another class". Plus, that way has the unspoken invariant that my_data holds the data of my_file up to the current seek position, which seems a bit strange to my eye.

Put another way: what does Foo do? Given a filename, it loads data, and gives you a DataStruct. That sounds like a function to me. If you need to be able to run it in a thread, or fire events between loading records, a class is the natural way to do it in C++, but you don't need a class for those things in Lisp.

Also, remember that you don't need to use DEFCLASS in order to use generic methods in CLOS.

I don't know what the structure of your data is, but in similar situations I've made a parse-one-chunk function that takes a stream and returns one record, and then create a complete Foo instance inside a loop in a with-open-file. If the stream is never needed outside the scope of a with-open-file expansion, you never need to worry about closing it.

Ken
I'm having trouble parsing some of this, but thanks for the insights, though. True, Foo loads a file and gives back a DataStruct, but it also holds the file pointer. If the file were closed reading in some data, I would need to seek the new position - updating the file pointer with each new frame of data. What I'm gathering from your comment is that rather than use a single object for manipulating the file stream and also parsing the data, it would be better form to create an object that can take a given file stream for data parsing, and manage the file and stream elsewhere. Is that accurate?
Shamster
And I think I'm similarly confused by your comment. "create an object that can take a given file stream for data parsing" -> sure, and this "object" I call a "function". :-) I don't really see the purpose behind "class Foo" here. There's no inheritance going on. It has an input, an output, a temporary, and some code to run at the beginning, middle, and end. In my world, that describes a function, not a class. But I don't entirely understand the context.
Ken
Ken: well put. I've been stuck in a particular rut with learning to form functions/classes, so I appreciate the insight. In this case, there's no reason to create an object other than the DataStruct that will hold "authoritative data" and write the function to perform the stream manipulation around it.
Shamster