<div dir="ltr">Wow! I should have asked that a long time ago.<div><br></div><div>Actually, I'm glad your response was as in-depth as it was. It gave me time to really think about the things that have been stumbling blocks, whether externally (the size and scope of ObjC & Cocoa), or internally (adapting my brain to ObjC's syntax).</div>
<div><br></div><div>After working through the currency converter (first in ObjC, then CCL), I could see how the things were working, but it wasn't what I would call "independent knowledge" (i.e., I couldn't have taken what I knew and written something from scratch). Realizing this, I cast about for a small project to tinker with (as you suggested), and remembered a book that I had bought years ago (<span class="Apple-style-span" style="font-family: Helvetica; font-size: 12px; "><a href="http://www.amazon.com/dp/0387202293">http://www.amazon.com/dp/0387202293</a>) that had drawing exercises at the end of each chapter written in Basic. (Hmm. Basic, ObjC, Lisp. There's a joke in there somewhere, but, at the moment, it escapes me.)</span></div>
<div><span class="Apple-style-span" style="font-family: Helvetica; font-size: 12px;"><br></span></div><div><span class="Apple-style-span" style="font-family: Helvetica; font-size: 12px;">From what I've gathered, NSBezierPath will be my friend for accomplishing the drawing.</span></div>
<div><span class="Apple-style-span" style="font-family: Helvetica; font-size: 12px;"><br></span></div><div><span class="Apple-style-span" style="font-family: Helvetica; font-size: 12px;">Given Matthew's suggestion, it sounds like working through the Hillegass book first, and translating the exercises from the aforementioned book to ObjC might be the most optimal approach.</span></div>
<div><br></div><div>Thank you for the thoughtful response,</div><div>Chris.<br></div><div><br></div><div><div class="gmail_quote">On Thu, Aug 28, 2008 at 5:18 AM, Gary Byers <span dir="ltr"><<a href="mailto:gb@clozure.com">gb@clozure.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"><div class="Ih2E3d"><br>
<br>
On Wed, 27 Aug 2008, Chris Van Dusen wrote:<br>
<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
Agreed. I saw AppKiDo mentioned in the online documentation, and have been<br>
using it pretty heavily since.<br>
One thing I've been curious about (and may produce on my own, if none<br>
exists) is a cheat sheet for the various ways of interacting with<br>
Objective-C. Until I get used to it, when to use the appropriate reader<br>
macro is a source of frustration.<br>
<br>
All of that said, does anyone have suggestions/recommendations as to how<br>
best to learn to commingle CCL & Objective-C? I'm fairly comfortable with<br>
Lisp in general. Would it be worth my while to devote my time to learning<br>
Objective-C, Cocoa, etc. to the point that I'm conversant in those things,<br>
then come back at it from the CCL perspective?<br>
<br>
Thanks,<br>
Chris.<br>
<br>
</blockquote>
<br></div>
When learning CL, there were probably some fairly major concepts<br>
that needed to be absorbed, including:<br>
<br>
- macros<br>
- syntax (or the near total absence of it)<br>
- the general notion that everything's a function that returns<br>
some number of values, and that most primitive operations<br>
(arithmetic, array/string access) are expressed as function calls<br>
- closures and higher-order functions<br>
- special variables and dynamic binding<br>
- lambda-lists and variadic functions<br>
- garbage collection (which might or might not be a new concept)<br>
- CLOS<br>
- the reader, packages ..<br>
- the fact that there's a fairly rich library of standard functions<br>
for dealing with common data structures<br>
- ...<br>
<br>
At some point - when enough of these things make enough sense - one<br>
gets comfortable enough with enough of these concepts to be able to<br>
program productively in CL. Learning to program in CL probably has<br>
more to do with becoming comfortable with a relatively small set of<br>
core concepts (like those above) than it does with memorizing<br>
reference material describing all of the library functions. (If you<br>
quizzed me right now, I probably couldn't correctly enumerate all of<br>
the possible standard values that the :IF-EXISTS argument to OPEN can<br>
take; since we all know what keyword arguments are and how they<br>
can be used, we can look up those details in CLHS when we need<br>
to and expect that reference material to make sense. Well, usually.)<br>
<br>
For ObjC and Cocoa, the number of core concepts that we need to<br>
learn in order to start to make sense of things is (mostly) pretty<br>
small; if we want to be able to read ObjC code/examples, there<br>
are some additional syntactic details. For ObjC, these concepts<br>
include (some of this is a little circular):<br>
<br>
- certain data structures are "classes"; they serve a role<br>
very similar to that served by classes in CLOS and other<br>
object systems. It's possible to add new classes to a<br>
running ObjC program (just as it is in CLOS), though ObjC<br>
is less flexible than CLOS/MOP is about redefining/removing<br>
classes procedurally. ObjC classes are uniquely named (by<br>
a string); many of the predefined classes in Cocoa (which<br>
was derived from NeXT's NeXTStep framework) have names<br>
that start with the prefix "NS".<br>
<br>
ObjC classes are allocated in foreign memory (often as<br>
part of a shared library's initialized data.) A class<br>
decribes the layout of its instances' instance variables<br>
("slots" in CLOS) and describes methods which are specialized<br>
on its instances.<br>
<br>
Redefining ObjC classes at runtime probably doesn't work<br>
very well. In CCL's ObjC bridge, accidentally mousing<br>
on an unchanged class definition is probably harmless.<br>
Adding/removing slots won't affect existing instances<br>
in general, and I don't know/remember what Bad Things<br>
might happen if the class hierarchy was changed.<br>
<br>
- classes can be instantiated (just as in CLOS and most other<br>
object systems); this involves allocating a (somewhat opaque)<br>
block of foreign memory. If one knows that a block of<br>
foreign memory contains an ObjC instance, it is possible<br>
(and cheap) to determine the class of that instance at<br>
runtime.<br>
<br>
- unlike the case in CLOS - where classes are usually instances<br>
of the metaclass STANDARD-CLASS - ObjC classes of unique<br>
metaclasses. (Among other things, this scheme allows methods<br>
on classes to be specialized and customized.) An ObjC class<br>
is therefore an instance of its metaclass (which is itself<br>
an instance of a special root metaclass), and it's possible<br>
to consider any ObjC class, metaclass, or instance as being<br>
an "ObjC object". All three kinds of ObjC object are implemented<br>
as "blocks of foreign memory"; the blocks occupied by classes<br>
and metaclasses are "permanent" (for the lifetime of the<br>
application), while those occupied by ObjC instances can be<br>
freed (and possibly reallocated) when it appears that they<br>
can no longer be referenced. Apple is moving toward the<br>
use of real garbage collection to determine when instances<br>
can be reliably and automatically freed; traditionally, a<br>
reference-counting scheme has been used.<br>
<br>
A foreign type corresponding to "pointer to ObjC object"<br>
is predefined in ObjC; this type is named "id" (or :id from<br>
CCL).<br>
<br>
- some strings (usually in mixed case and often containing<br>
embedded and/or trailing colons) name ObjC "messages". An<br>
ObjC message isn't quite a first-class object, but it can be<br>
thought of as being similar to a kind of generic function<br>
which is specialized on the class of its first argument<br>
(which must be an ObjC object) and which may contain<br>
additional arguments. Those additional arguments may<br>
be ObjC objects or low-level C pointers or numbers.<br>
A string which names a message is sometimes called a<br>
"selector" (in some other ObjC implementations, a "selector"<br>
is distinct from the string which identifies it, but that's<br>
a separate issue.)<br>
<br>
The number of colons in a message name typically indicates<br>
the number of arguments (other than the first, special<br>
argument) that methods defined on the message should accept.<br>
<br>
- It's possible (and useful) to define methods on any message<br>
and any ObjC class or metaclass. A large number of methods<br>
(on a large number of classes ...) are predefined in Cocoa<br>
and other ObjC libraries; methdods can also be added at<br>
runtime. In order for ObjC code to be able to pass arguments<br>
(other than the first specialized argument of type "id")<br>
to and return results from an ObjC method, the (foreign)<br>
types of those arguments and return value must be specified.<br>
If a method shadows another method defined in a superclass<br>
(or is shadowed by a method defined in a subclass), the<br>
types of those arguments and return value must agree.<br>
(It's legal for methods defined on disjoint classes to<br>
have different type signatures; this happens occasionally<br>
in practice.)<br>
<br>
All methods defined on an ObjC message must take the same<br>
number of arguments, but (as noted above) the types of<br>
their non-specialized arguments may differe in some cases.<br>
<br>
It is possible to redefine an ObjC method at runtime; it<br>
generally works reliably to change the implementation (body)<br>
of the method, but may not work as well (or at all) to<br>
<br>
<br>
- From within the body of an ObjC method M, it's possible to<br>
invoke whatever method would have been applicable had M<br>
not been defined. (Conceptually, this is similar to<br>
CALL-NEXT-METHOD in CLOS.)<br>
<br>
- In ObjC (as in SmallTalk), invoking a generic function on<br>
a specialized argument ("the receiver") and some other<br>
arguments is called "sending a message", and it's written<br>
in a sort of infix syntax:<br>
<br>
[reciever messageName] if the message accepts no other arguments<br>
[receiver messageNameFirstArg: first-arg-value nextArg: next-arg-value]<br>
otherwise.<br>
<br>
In the first case, the actual message name is "messageName"; in the<br>
second case, it's "messageNameFirstArg:nextArg:"<br>
<br>
- Methods can be defined on classes (and therefore applicable to<br>
their instances) or on metaclasses (and therefore applicable to<br>
class methods). When a class method is defined or described, it's<br>
usually prefixed with a "+" character; an instance method is<br>
usually prefixed with a "-".<br>
<br>
That's a fair amount of stuff that really should be written down<br>
someplace and in greater detail. A short version is that ObjC offers<br>
an object system with dynamically typed objects and at least some<br>
amount of/some kinds of runtime extensibility, and a sort of<br>
restricted form of (something like) CLOS method dispatch that's based<br>
on the (runtime) class of the first/only specialized argument, and<br>
that ObjC methods typically deal with some combination of run-time<br>
typed Objc objects and statically-typed primitive C things (numbers,<br>
pointers, structures.)<br>
<br>
I suspect (I don't know for sure) that the thing that'd help most<br>
people the most in getting up to speed with using Cocoa in CCL is<br>
comfort and familiarity with CCL's FFI. If that's true (and to the<br>
extent that it is), it's somewhat unfortunate; when he first started<br>
working on it, Randall Beer said that ideally he'd like the bridge to<br>
expose as much of Cocoa as possible while hiding as much of ObjC as<br>
possible. It's gotten closer to that goal over the years, but I<br>
suspect that a lot of things that people have trouble with could<br>
be blamed on the fact that ObjC (and some of the arcana of dealing<br>
with it) still isn't hidden well enough. (If this is true, it's<br>
probably the "C" part that's problematic; the "Obj" part seems<br>
to fit a lot better into CL/CCL.) Looking back at it, I think<br>
that the bridge actually -does- do a good job of hiding a lot<br>
of the problematic C stuff (though admittedly not all of it),<br>
but a lot of it is ultimately nothing more than a few layers<br>
of syntactic sugar around the FFI, and I imagine that it's<br>
still hard to use unless one has some sense of what's going<br>
on underneath.<br>
<br>
For learning Cocoa itself ... well, it's a large class library (much<br>
larger than the standard CL library) that provides default behavior<br>
for a lot of things that an application needs to do (implemented as<br>
instances of predefined classes and predefined methods on those<br>
classes), and the general idea is that by subclassing some of those<br>
classes and overriding some of those methods you turn this generic<br>
application into something that offers custom behavior. It's<br>
generally hard to absorb all of the functionality that's available<br>
in a library by reading the reference manual from cover to cover<br>
(it might help to skim reference material to get a sense of<br>
what's available and to read a few passages that seem interesting.)<br>
It's often helpful - especially when starting out - to have a<br>
specific goal in mind, and then try to learn what you need to<br>
learn in order to achieve that. After you repeat that process<br>
a few times, it likely starts to get easier, and (as is often<br>
the case) the first foray into something new is the hardest.<br>
<br>
Suppose that we had a specific goal of writing an image-viewing<br>
program, like a watered-down version of Apple's Preview.app)<br>
(We could make the example more interesting, but let's not ...)<br>
If we've never done this before in Cocoa, there are clearly<br>
a bunch of specific questions we'd need answered, including:<br>
<br>
- what predefined support is there for loading files containing<br>
well-known image types (jpg, tiff, gif, png, ...) and displaying<br>
them ? If we wanted to construct an image from an unsupported<br>
file type or procedurally, what's involved in doing that ?<br>
<br>
- how exactly do you display a window and get an image to appear<br>
in it ? How do you control what happens when the window is<br>
resized (e.g., does the image scale ? do you want it to ?)<br>
<br>
- how do you provide a user interface that allows images to<br>
be loaded (and other behavior that a more interesting<br>
application would want) ?<br>
<br>
Apple does provide very good overview documentation (in the<br>
form of "guides".) At the moment, the Cocoa guides are<br>
available at:<br>
<br>
<<a href="http://developer.apple.com/documentation/Cocoa/index.html" target="_blank">http://developer.apple.com/documentation/Cocoa/index.html</a>><br>
<br>
and looking at the "Graphics and Imaging" section there we find links<br>
to guides describing "Image Views" (sounds promising) and a "View<br>
Programming Guide". The image view guide is a little terse (it<br>
basically just says that NSImageViews are NSViews that display<br>
NSImages, whatever they are. The "View Programming Guide" is a lot<br>
more to digest, and if we follow a link to NSImage we'd have even more<br>
to digest. If we follow the link from the Image View guide to the<br>
NSImageView reference, we see that NSImageViews can be set to allow<br>
cut copy and paste of images as well as drag-and-drop. That's maybe<br>
not ultimately what we want (if we want to load images from files),<br>
but it may be an easier way to get started.<br>
<br>
Let's make a window, put an NSImageView in it, set some options<br>
that enable drag-and-drop and cut-and-paste to work with the<br>
image view, center and display the window, and return the NSImageView<br>
object:<br>
<br>
(defun make-image-view-window ()<br>
"Make a window whose content view is an empty NSImageView. Allow<br>
the user to drag images to that view, and support transfer of images<br>
to/from the view via the clipboard. Return the NSImageView."<br>
(ccl::with-autorelease-pool<br>
(let* ((rect (ns:make-ns-rect 0 0 300 300))<br>
(w (make-instance 'ns:ns-window<br>
:with-content-rect rect<br>
:style-mask (logior #$NSTitledWindowMask<br>
#$NSClosableWindowMask<br>
#$NSMiniaturizableWindowMask<br>
#$NSResizableWindowMask)<br>
:backing #$NSBackingStoreBuffered<br>
:defer t))<br>
(image-view (make-instance 'ns:ns-image-view)))<br>
;; Set the window's title to a constant NSString (#@"...").<br>
(#/setTitle: w #@"Drag or paste an image here!")<br>
;; Make the image view respond to drag-and-drop<br>
(#/setEditable: image-view t)<br>
;; and make it support the clipboard<br>
(#/setAllowsCutCopyPaste: image-view t)<br>
;; Make the image view be the window's "content view".<br>
(#/setContentView: w (#/autorelease image-view))<br>
;; Center and display the window.<br>
(#/center w)<br>
(#/orderFront: w nil)<br>
image-view)))<br>
<br>
If we call this function, an empty 300x300 window should appear on<br>
the screen and an NSImageView object should be returned.<br>
<br>
? (make-image-view-window)<br>
#<NS-IMAGE-VIEW <NSImageView: 0x1279bf50> (#x1279BF50)><br>
? (defvar *v* *) ; so we can refer to the NS-IMAGE-VIEW later.<br>
*V*<br>
<br>
If we then switch to a web browser or other image-viewing application<br>
and copy an image to the clipboard, we can switch back to the IDE and<br>
paste that image into the image view.<br>
<br>
I happened to copy a fairly small image:<br>
<br>
? (#/image *v*)<br>
#<NS-IMAGE NSImage 0x12795df0 Size={120, 80} Reps=(<br>
NSBitmapImageRep 0x1279cf30 Size={120, 80} ColorSpace=NSCalibratedRGBColorSpace BPS=8 BPP=32 Pixels=120x80 Alpha=YES Planar=NO Format=2 CGImage=0x1279e340<br>
) (#x12795DF0)><br>
<br>
When I resized the window, the image stayed at 120x80 and happened to<br>
stay centered in the window. It may be more impressive to let the image<br>
scale itself to fit the view bounds:<br>
<br>
? (#/setImageScaling: *v* #$NSScaleToFit)<br>
NIL<br>
<br>
Getting images from the clipboard is all well and good, but the original<br>
goal was to display images that were loaded from files. If only<br>
NSImage had an "initWithContentsOfFile:" method, this would be trivially<br>
easy in most cases.<br>
<br>
Oh, wait: it does (as revealed by a little bit of poking around in the<br>
NSImage reference doc.) ObjC messages whose names start with<br>
"init" are treated specially by the bridge: MAKE-INSTANCE with keyword<br>
args derived from the rest of the message name will (among other<br>
things) send that initialization message to a newly-allocated instance<br>
of the class. So:<br>
<br>
(let* ((image (make-instance 'ns:ns-image :with-contents-of-file #@"/Library/Desktop Pictures/Nature/Aurora.jpg")))<br>
(unless (%null-ptr-p image) ; unless the image couldn't be initialized<br>
(#/setImage: *v* (#/autorelease image))))<br>
<br>
We know from an earlier message in this thread (if not otherwise)<br>
how to use NSOpenPanel to select a file, so we're maybe around 75%<br>
of the way towards the goal of developing a simple image-viewing<br>
program. That's probably enough for an off-topic reply, but it's<br>
worth noting that so far it's been about 20 lines of code and that<br>
NSImage and NSImageView and the rest of Cocoa are doing all of the<br>
heavy lifting.<br>
<br>
I'm sorry that this reply is so long and not entirely responsive. To<br>
try to summarize:<br>
<br>
- I think that the best way to learn Cocoa (or anything similar)<br>
is probably from the bottom up and from the inside out, and that<br>
it helps to have a concrete, narrowly focused goal and to try<br>
to learn just what you need to learn in order to achieve that.<br>
After repeating that process a few times, more stuff tends to<br>
resonate and make sense.<br>
<br>
It's certainly not practical to go in the other directions,<br>
as in "here are several hundred classes and a few thousand<br>
methods, go learn them."<br>
<br>
- From your comments and others, I think that it's fair to say<br>
that the goal of hiding ObjC (the C part of it, especially)<br>
hasn't been fully reached. It's still sometimes necessary<br>
to allocate foreign objects and pass pointers to them by<br>
reference, and that sort of thing is disconsonant with what's<br>
otherwise a very high-level and powerful framework (Cocoa.)<br>
That stuff is also inherently hard and un-lispy.<br>
<br>
One of the problems that I personally have here is that while<br>
I can believe that there are aspects of CCL's FFI that are hard<br>
to use and/or understand, it's hard for me to guess exactly<br>
what those things are (since it all makes perfect sense to me<br>
for some reason.)<br>
</blockquote></div><br></div></div>