TweetFollow Us on Twitter

Pearl Lisp
Volume Number:6
Issue Number:8
Column Tag:Programmer's Workshop

Objects in Pearl Lisp

By Stephan E. Miner, Menlo Park, CA

As a software engineer in the Information Sciences and Technology Center at SRI International, Steve Miner works on applied research in artificial intelligence, particularly in the areas of planning and decision aids. SRI’s hardware environment consists mainly of Sun workstations and Symbolics Lisp Machines, but Steve gets to use a Macintosh SE at home.

Introduction

Coral Software recently released Pearl Lisp, a subset of Common Lisp for about $100. Pearl Lisp is based on Coral’s Allegro Common Lisp which has been discussed in an earlier issue of MacTutor (see Paul Snively’s article in the March 1988 issue.) Although Allegro received very good reviews, its list price of about $600 made it impractical for most first-time Lisp programmers. Pearl Lisp offers an affordable introduction to Lisp programming on the Macintosh.

Although a complete review of Pearl Lisp is not the purpose of this article, a brief overview might be helpful. Some features of Common Lisp (such as packages, structures, hash tables and multiple values) are not supported. However, most of the list, control, and mathematical functions of Common Lisp are available. Lexical scoping and closures are also implemented. The development environment is very much like Allegro’s and includes an EMACS-like editor known as FRED (FRED Resembles EMACS Deliberately.) The reference manual is well-written, with cross references to Steele’s Common LISP: the Language and several popular textbooks. In addition to the subset of Common Lisp functions, Pearl Lisp includes a native object-oriented programming system, called Object Lisp, which is the topic of this article.

As a demonstration of Object Lisp, the sample program simulates a solar system. Objects are used to implement the planets as well as the Macintosh windows and menus. Each planet orbits around a gravitational center at some radius with a certain period. At any point in time, the planet has some X and Y coordinates relative to the center of the solar system. The simulation is displayed in various windows with each window offering the view from a particular planet. For example, a heliocentric view has the Sun appearing fixed at the center of the window. In contrast, a geocentric view keeps the Earth at the center of display with some of the other planets showing retrograde motion. In both cases, the same planets are used and they maintain the same relative motions. It is the window objects which implements the different points of view.

Object Lisp

Before describing the sample program in detail, a brief introduction to the basic concepts of Object Lisp will be presented. As in other object-oriented programming systems, an object encapsulates both data and functions. An object’s data is contained in internal variables, known as instance variables. (Instance variables are often called slots in other systems.) An object can also have specialized object functions that allow access to the object’s internal variables as if the internal variables were local to the function. (Object functions are often called methods in other object-oriented systems.)

Inheritance is another key concept in most object-oriented systems. In Object Lisp, objects are defined in terms of other objects. An object inherits the instance variables and object functions of its parents. (Object Lisp supports multiple inheritance, but the details are not discussed in this article.) An object can replace, specialize, or combine the characteristics of its parents by shadowing the appropriate object functions. A parent’s object function foo is also accessible by calling usual-foo, even when the parent’s object function is shadowed.

The special form ask is used to access the environment of an object. For example, suppose that OBJECT is bound to some object. The form (ASK OBJECT VAR) will return the object’s internal value for VAR. The form (ASK OBJECT (FUNC ‘ARG)) will return the result of calling the object function FUNC with the argument ‘ARG.

Conceptually, global variables and functions belong to the root object nil. Thus, (ASK NIL (GLOBAL-FUNC ‘ARG)) is equivalent to (GLOBAL-FUNC ‘ARG). This also implies that global variables and functions are accessible by all objects through inheritance from the root object. The root object also implements several useful object functions. The have object function is used to create instance variables. For example, (ASK OBJECT (HAVE ‘VAR ‘VAL)) will create an instance variable VAR with an initial value VAL for the object OBJECT. The self object function simply returns the current object.

Classes

Most object-oriented systems make a distinction between a class and an instance. A class defines a type of object with a description of its internal data and the associated procedures (or methods) for operating on that data. An instance, on the other hand, is a specific object with internal values which hold its state. Object Lisp does not enforce this strict distinction, but it does support a convention that implements classes as types of objects.

Class objects can be created with the macro defobject which defines the name of the class and the parents from which it inherits. Nil is used as the root object class.

An instance of a class is created by the function oneof which takes the parent class as the first argument, followed by a list of keyword-value pairs for initializing the instance. The initialization is actually executed by the exist object function defined for the class. We will see how the exist object function works in our sample program.

The Solar Program

The sample program can be divided into three major sections. The first part implements the planet class. Window objects are implemented by the second part. The final section provides menus for controlling the execution of the program.

The *planet* class is quite simple. It is defined using defobject and inherits only from the root class object, nil.

The exist object function for *planet* needs more explanation. Exist will be called automatically by the system when the oneof function is used to create a new instance of*planet*. The init-list argument will be a list of the keyword-value pairs given to oneof. The main purpose of the exist object function is to initialize the instance variables. The getf function is used to access the value associated with the indicated keyword. A default value can also be specified in case the keyword is not found. For example, if the period of a planet is not specified in the init-list, the default value of 25 is used.

In order to simplify the creation of new planets, the :center keyword is used to specify the orbital center of the new planet. When calculating coordinates, however, it is more natural to proceed from the center out to the satellites. The center is asked to add the new planet to the its list of satellites using the add-satellite object function.

The update-system object function calculates the new X and Y coordinates for the planet given the time and the coordinates of the planet’s orbital center. The planet then recursively asks its satellites to run update-system. Thus, a single update-system call to the sun of a solar system will be propagated through all the planets in the system.

The other class that the program defines is the *solar-window*. This class is a specialized version of the *window* class which is provided by Coral as an interface to the Macintosh window system. This is a good example of how one can easily extend a previously defined class. In this case, the new exist first calls usual-exist to handle the normal initialization of the window. The init-list-default function returns an init-list with the additional defaults. *Solar-window* also has a couple of its own instance variables. The center instance variable determines the gravitational center of the solar system (normally the *sun*.) The view instance variable controls the viewpoint of the display. For example, a geocentric view is given by setting the view to *earth*.

The center-origin object function resets the origin of the window to the center of view. This simplifies the display of the solar system. The window-zoom-event-handler and set-window-size object functions are also extended to recenter the origin.

The inheritedwindow-show object function displays the window on the screen. This indirectly calls the window-draw-contents object function. The sample program does not worry too much about animation flicker so it simply erases the entire window and then redraws everything. The usual-window-draw-contents takes care of redrawing the grow box.

The draw-system object function does most of the work. Its structure is similar to update-system in that it draws one planet and then recursively draws the satellites of that planet. In this case, however, the x-off and y-off arguments are offsets that are added to the absolute coordinates for the planet to determine the planet’s window coordinates. Each view assigns offsets so that the view planet remains centered in the window. The rlet macro lets a Lisp program create Pascal record structures, such as those used by the Macintosh ROM routines. Here, a rectangle is initialized based on the size of the planet and its window coordinates. The Quickdraw routines, fill-oval and frame-oval, are used to draw the planet. When rlet exits, the temporary record is disposed automatically.

The erase-window object function also accesses a Pascal record structure. In this case, it uses the rref macro to return the window’s portrect. The instance variable, wptr, is defined by the *window* class and holds a Macintosh window pointer. Rref allows the Lisp program to access the fields of the record using a Pascal-style notation. The window is erased by calling the Quickdraw function erase-rect.

The final section of the sample program involves the menu system. Once again, the menus and menu items are predefined classes of objects. A menu-item-action function is associated with each menu item. This function is called when the user selects the item.

The ubiquitous main event loop is implemented by the Pearl Lisp event system and its interface to the Macintosh operating system. It is important to note that the event system will interrupt the normal Lisp read-eval-print loop in order to run event handlers. Menus and dialogs can take advantage of this behavior by using the eval-enqueue function. Eval-enqueue queues the given form in the Lisp system’s read-eval-print loop for execution after the event handler exits. This allows the user to interact freely with the program while it is executing. The programmer is also freed from writing his own main event loop since the object system will call the appropriate handlers as needed.

In the sample program, the first three menu items create three different views of the solar system. Each item calls the new-solar function to create a new solar window with the appropriate view planet. The window title includes a number for easy reference. The program has no limit on the number of windows that it can display.

The *run-item* uses eval-enqueue to enqueue a call to the run-loop function. Run-loop maintains the checkmarks on the menu items and runs the simulations. On each pass through the loop, the global variable*time* is incremented, and the *sun* is asked to update-system for the new time. (This updates the locations of all the planets in the solar system.) Then each window is asked to redraw itself to reflect the new positions of the planets. The (ownp ‘wptr) test makes sure that the window still exists and protects against the user having closed the window while the loop was executing. Run-loop exits when the *stop-flag* is set or the window list is empty.

The *stop-item* action immediately sets the *stop-flag* during the execution of the handler. This action will interrupt the execution of the run-loop function which will later exit as soon as it rechecks the flag. The *exit-item* is similar but also enqueues a call to exit-solar which will execute after the run-loop function terminates. The exit-solar function then quits the demonstration by gathering a list of all *solar-window* objects and asking each of them to close-window (which is inherited from *window*.) Close-window also takes care of disposing of the window structure.

Conclusion

This article has presented an introduction to object-oriented programming in Pearl Lisp. The sample program demonstrates how the object system simplifies programming with windows and menus in Lisp. Without any extra work, the program also runs in the background under MultiFinder. The included screen shot shows how the program conveniently integrates with the Pearl Lisp environment.

;;; Sample program for "Objects in Pearl Lisp"
;;; by Stephen E. Miner
;;; Written in Pearl Lisp 1.01
;;; File:  Solar.lisp
;;; Version: 1.0

;;; NOTE:  The "object-variable" declarations prevent the ;;;compiler 
from issuing warnings about free variables.

;;; Set up environment
(eval-when (eval load compile)
  (require 'quickdraw))
(eval-when (eval compile)
  (require 'records))

;;; Global variables
(defvar *solar-num* 0 "Global counter for numbering windows.")
(defvar *time* 0 "Global variable holding the time that is     displayed.")
(defvar *stop-flag* t "Non-nil if simulation should stop.")

;;; The planet class
(defobject *planet* nil)

(defobfun (exist *planet*) (init-list)
  "Initializes an instance of the *planet* class according to INIT-LIST. 
 Useful init keywords are :period, :size, :pattern, :radius and :center. 
 The return value is undefined."
  (have 'period (getf init-list :period 25))
  (have 'size (getf init-list :size 3))
  (have 'pattern (getf init-list :pattern *black-pattern*))
  (have 'x 0)
  (have 'y 0)
  (have 'satellites nil)
  (let ((center (getf init-list :center))
           (me (self))) ;(self) returns object being defined
      (have 'radius (getf init-list :radius (if center 25 0)))
      (when center
         (ask center (add-satellite me)))))

(defobfun (add-satellite *planet*) (sat)
  "Add SAT to the planet's list of satellites and return the new list."
  (declare (object-variable satellites))
  (setq satellites (cons sat satellites)))
 
(defobfun (update-system *planet*) (time cx cy)
  "Update the x and y coordinates of the planet according to the TIME 
and the offsets CX and CY which should be the x and y coordinates of 
the center of the planet's orbit.  Then recursively send the update-system 
message to the satellites of the planet using the new x and y coordinates 
as the offsets.  The return value is undefined." 
  (declare (object-variable period radius x y satellites))
  (let* ((theta (* 2 pi (/ time period)))
             (new-x (+ cx (round (* radius (cos theta)))))
             (new-y (+ cy (round (* radius (sin theta))))))
      (setq x new-x y new-y)
      (dolist (sat satellites)
          (ask sat (update-system time new-x new-y)))))

;;; The planet objects (the numbers are not accurate, but they 
;;; produce a reasonable display.)
(defparameter *sun* (oneof *planet* :center nil :size 11 
                           :pattern *light-gray-pattern*))
  
(defparameter *mercury* (oneof *planet* :radius 20 :center     *sun* 
:period 12  :size 3 :pattern  *dark-gray-pattern*))

(defparameter *venus* (oneof *planet* :radius 35 :center       *sun* 
:period 32 :size 5 :pattern *dark-gray-pattern*))

(defparameter *earth* (oneof *planet* :radius 60 :center *sun* 
 :period 52 :size 6 :pattern *gray-pattern*))

(defparameter *moon* (oneof *planet* :radius 10 :center  *earth* :period 
4  :size 2))

(defparameter *mars* (oneof *planet* :radius 85 :center *sun*  :period 
90 :size 5 :pattern *dark-gray-pattern*))

;;; The solar window class
(defobject *solar-window* *window*)

(defobfun (exist *solar-window*) (init-list)
  "Initializes an instance of the *solar-window* according to the INIT-LIST. 
 Useful keywords are :center which specifies the gravitational center 
of the displayed system and :view which specifies the planet that controls 
the viewpoint of the display.  The return value is undefined."
  (declare (object-variable center))
  (usual-exist (init-list-default init-list 
                                  :window-title "Solar System"
                                  :window-size #@(250 250)
                                  :window-show nil))
  ;;don't show window until the window is fully initialized
  (have 'center (getf init-list :center))
  (have 'view (getf init-list :view center))
  (center-origin)
  (window-show))

;;; The event system will automatically ask windows to handle 
;;; certain events.  Specialized object functions for handling 
;;; these events are defined below.
(defobfun (window-draw-contents *solar-window*) ()
  "Specialized version of window-draw-contents called by the event system 
whenever part of the window needs to be redrawn.  The return value is 
undefined."
  (declare (object-variable center view x y))
  (erase-window)
  (usual-window-draw-contents)
  (draw-system center (- (ask view x)) (- (ask view y))))

(defobfun (window-zoom-event-handler *solar-window*) (message)
  "Specialized version of window-zoom-event-handler which is called by 
the operating system when the user clicks in the zoom box.  The MESSAGE 
is passed on to the usual-window-zoom-event-handler.  This version also 
recenters the origin.  The return value is undefined."
  (usual-window-zoom-event-handler message)
  (center-origin))

(defobfun (set-window-size *solar-window*) (h &optional v)
  "Specialized version of set-window-size.  Sets the size of the window 
according to horizontal and vertical dimensions, H and V.  H and V are 
either two integers or H is taken as a point if V is nil.  Also recenters 
the origin and redraws the window.  Returns the window's new size as 
a point."
  (prog1
    (usual-set-window-size h v)
    (center-origin)
    (window-draw-contents)))

(defobfun (center-origin *solar-window*) ()
  "Adjust the origin to the center of the window.  Returns the window's 
new upper lefthand corner as a point."
  (let ((pt (window-size)))
    (set-origin (floor (point-h pt) -2)
                (floor (point-v pt) -2))))

(defobfun (draw-system *solar-window*) (planet x-off y-off)
  "Draw the PLANET and its satellites in the window after adding X-OFF 
and Y-OFF to the planet's x and y coordinates.  The return value is undefined."
  (declare (object-variable x y size pattern satellites))
  (let ((x0 (+ (ask planet x) x-off))
        (y0 (+ (ask planet y) y-off))
        (size (ask planet size)))
    ;;allocate a temporary rectangle for graphics calls
    (rlet ((rec :rect :top (- x0 size) :left (- y0 size)
                :bottom (+ x0 size) :right (+ y0 size)))
      (fill-oval (ask planet pattern) rec)
      (frame-oval rec)))
  ;;draw the satellites
  (dolist (sat (ask planet satellites))
    (draw-system sat x-off y-off)))

(defobfun (erase-window *solar-window*) ()
  "Erase the contents of the window.  The return value is undefined."
  ;;rref access the Macintosh record and in this case returns 
  ;; the window's portrect.  See the Pearl Lisp documentation 
  ;; for more information about records.
  (declare (object-variable wptr))
  (erase-rect (rref wptr window.portrect)))

;;; Menu action functions
(defun new-solar (view-planet title)
  "Create a new solar window with VIEW-PLANET determining the point of 
view and the TITLE string used as base for the window title.  The global 
*solar-num* is incremented and appended to the window title to ease identification. 
 Returns the new window object."
  (setq *solar-num* (+ *solar-num* 1))
  (oneof *solar-window* :window-title 
         (format nil "~A ~A" title *solar-num*)
         :center *sun*
         :view view-planet))

(defun exit-solar ()
  "Close all the solar windows and deinstall the menu.  The return value 
is undefined."
  (dolist (w (windows *solar-window*)) 
    (ask w (window-close)))
  (ask *solar-menu* (menu-deinstall)))

(defun run-loop ()
  "Run the simulation until the global *stop-flag* is true.  This function 
also manages the solar menu."
  (setq *stop-flag* nil)
  (ask *stop-item* (set-menu-item-check-mark nil))
  (ask *run-item* (set-menu-item-check-mark t))
  (loop
    (let ((wlist (windows *solar-window*))) 
           ;;list of all *solar-window*'s
      (when (or *stop-flag* (null wlist))   
       ;;check for end of simulation
        (ask *run-item* (set-menu-item-check-mark nil))
        (ask *stop-item* (set-menu-item-check-mark t))
        (return))
      (setq *time* (+ 1 *time*))
      (ask *sun* (update-system *time* 0 0)) 
      ;;updates all the x and y coords
      (dolist (w wlist)
        (ask w (when (ownp 'wptr)   ;protect against close-box
            (window-draw-contents)))))))   ;redraw the window

;;; The menu items
(defparameter *new-helio-item* 
  (oneof *menu-item* :menu-item-title "New Helio"
         :menu-item-action '(new-solar *sun* "Heliocentric")))

(defparameter *new-geo-item* 
  (oneof *menu-item* :menu-item-title "New Geo"
         :menu-item-action '(new-solar *earth* "Geocentric")))

(defparameter *new-luna-item* 
  (oneof *menu-item* :menu-item-title "New Luna"
         :menu-item-action '(new-solar *moon* "Lunacentric")))

(defparameter *run-item* 
  (oneof *menu-item* :menu-item-title "Run"
         :menu-item-action '(when *stop-flag* 
                             (eval-enqueue '(run-loop)))))
                    
(defparameter *stop-item* 
  (oneof *menu-item* :menu-item-title "Stop"
         :menu-item-action '(setq *stop-flag* t)))

(defparameter *exit-item* 
  (oneof *menu-item* :menu-item-title "Exit"
         :menu-item-action 
         '(progn
            (setq *stop-flag* t)
            (eval-enqueue '(exit-solar)))))
;;The eval-enqueue makes sure that we wait for the run-loop to 
;; finish before we exit.

(defparameter *solar-menu* 
  (oneof *menu* :menu-title "Solar"
         :menu-items (list *new-helio-item*
                           *new-geo-item*      
                           *new-luna-item*
                           (oneof *menu-item* :menu-item-title "-":disabled 
t)
                           *run-item*
                           *stop-item*
                           *exit-item*)))

;;; Install the menu
(ask *run-item* (set-menu-item-check-mark (not *stop-flag*)))
(ask *stop-item* (set-menu-item-check-mark *stop-flag*))
(ask *solar-menu* (menu-install))

 
AAPL
$119.00
Apple Inc.
+1.40
MSFT
$47.75
Microsoft Corpora
+0.28
GOOG
$540.37
Google Inc.
-0.71

MacTech Search:
Community Search:

Software Updates via MacUpdate

Skype 7.2.0.412 - Voice-over-internet ph...
Skype allows you to talk to friends, family and co-workers across the Internet without the inconvenience of long distance telephone charges. Using peer-to-peer data transmission technology, Skype... Read more
HoudahSpot 3.9.6 - Advanced file search...
HoudahSpot is a powerful file search tool built upon MacOS X Spotlight. Spotlight unleashed Create detailed queries to locate the exact file you need Narrow down searches. Zero in on files Save... Read more
RapidWeaver 6.0.3 - Create template-base...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
iPhoto Library Manager 4.1.10 - Manage m...
iPhoto Library Manager lets you organize your photos into multiple iPhoto libraries. Separate your high school and college photos from your latest summer vacation pictures. Or keep some photo... Read more
iExplorer 3.5.1.9 - View and transfer al...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
MacUpdate Desktop 6.0.3 - Discover and i...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
SteerMouse 4.2.2 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. It also supports Apple Mighty Mouse very well. SteerMouse can assign various functions to buttons that Apple's software does not allow,... Read more
iMazing 1.1 - Complete iOS device manage...
iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
PopChar X 7.0 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
OneNote 15.4 - Free digital notebook fro...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more

Latest Forum Discussions

See All

Raby (Games)
Raby 1.0.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.3 (iTunes) Description: ***WARNING - Raby runs on: iPhone 5, iPhone 5C, iPhone 5S, iPhone 6, iPhone 6 Plus, iPad Mini Retina, iPad Mini 3, iPad 4, iPad Air,... | Read more »
Oddworld: Stranger's Wrath (Games)
Oddworld: Stranger's Wrath 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: ** PLEASE NOTE: Oddworld Stranger's Wrath requires at least an iPhone 4S, iPad 2, iPad Mini or iPod Touch 5th gen... | Read more »
Bounce On Back (Games)
Bounce On Back 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »
Dwelp (Games)
Dwelp 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: === 50% off for a limited time, to celebrate release === Dwelp is an elegant little puzzler with a brand new game mechanic. To complete a... | Read more »
Make Way for Fat Chicken, from the Maker...
Make Way for Fat Chicken, from the Makers of Scrap Squad Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Relevant Games has announced they will be releasing their reverse tower defense game, | Read more »
Tripnary Review
Tripnary Review By Jennifer Allen on November 26th, 2014 Our Rating: :: TRAVEL BUCKET LISTiPhone App - Designed for the iPhone, compatible with the iPad Want to create a travel bucket list? Tripnary is a fun way to do exactly that... | Read more »
Ossian Studios’ RPG, The Shadow Sun, is...
Ossian Studios’ RPG, The Shadow Sun, is Now Available for $4.99 Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mmmm, Tasty – Having the Angry Birds for...
The very first Angry Birds debuted on iOS back in 2009. When you sit back and tally up the number of Angry Birds games out there and the impact they’ve had on pop culture as a whole, you just need to ask yourself: “How would the birds taste... | Read more »
Rescue Quest Review
Rescue Quest Review By Jennifer Allen on November 26th, 2014 Our Rating: :: PATH BASED MATCH-3Universal App - Designed for iPhone and iPad Guide a wizard to safety by matching gems. Rescue Quest might not be an entirely original... | Read more »
You Can Play the Final Chapter of Lone W...
You Can Play the Final Chapter of Lone Wolf: Dawn Over V’taag Right Now Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

BEVL Releases Dock Tailored for iPhone 6 and...
Seattle based BEVL has released their first product: an iPhone dock that is divergent in build quality, rock-solid function and visual simplicity to complement the iPhone. BEVL is now accepting... Read more
Black Friday: $150 off 13-inch Retina MacBook...
 Best Buy has 13-inch 2.6GHz Retina MacBook Pros on sale for $150 off MSRP on their online store as part of their Black Friday sale. Choose free shipping or free local store pickup (if available).... Read more
Black Friday: $300 off 15-inch Retina MacBook...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for $300 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina... Read more
Black Friday: Up to $140 off MacBook Airs, fr...
 B&H Photo has 2014 MacBook Airs on sale for up to $140 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 11″ 128GB MacBook Air: $799 $100... Read more
Black Friday: 13-inch 2.5GHz MacBook Pro on s...
 Best Buy has the 13″ 2.5GHz MacBook Pro on sale for $899.99 on their online store as part of their Black Friday sale. Choose free shipping or free instant local store pickup (if available). Their... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has the new 1.4GHz Mac mini on sale for $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new model. Adorama... Read more
Early Black Friday pricing on 27-inch 5K iMac...
 B&H Photo continues to offer Black Friday sale prices on the 27″ 3.5GHz 5K iMac, in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP... Read more
Early Black Friday sale prices on iPad Air 2,...
 MacMall is discounting iPad Air 2s by up to $75 off MSRP as part of their Black Friday sale. Shipping is free: - 16GB iPad Air WiFi: $459 $40 off - 64GB iPad Air WiFi: $559 $40 off - 128GB iPad Air... Read more
Early Black Friday MacBook Air sale prices, $...
 MacMall has posted early Black Friday MacBook Air sale prices. Save $101 on all models for a limited time: - 11″ 1.4GHz/128GB MacBook Air: $798 - 11″ 1.4GHz/256GB MacBook Air: $998 - 13″ 1.4GHz/... Read more
Why iPhone 6 Tablet/Laptop Cannibalization Is...
247wallst.com blogger Douglas A. McIntyre noted last week that according to research posted on the Applovin blog site the iPhone 6 is outselling the iPhone 6 Plus by a wide margin . Hardly a surprise... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.