Next: Documentation Tips, Previous: Performance Tips, Up: Tips [Contents][Index]
Writing compatible code is very easy whenever a problem is decoupled enough. That is when your problem is of abstract nature and does not explicitly depend on a certain OS, emacs flavour, external tools, or file system layout.
In that case you would simply divide your code in two parts, the abstract layer which contains the solution of your problem in an abstract (read independent) way, and the implementation layer which contains OS specific, flavour specific, tool specific and/or file system layout specific implementations and which thus provides one or more interfaces for your abstract layer.
Fine, so why are there so many problems between, say, code for FSF Emacs and (S)XEmacs? Well, programmers are not always professional software designers. Also, compatibility often plays a minor role, especially in young projects or projects with little man power. But the most understandable reason might be that one or more pieces of code simply cannot be decoupled or at least not in an efficient way.
Anyway, let us look at very simple compatibility issues. The most
trivial case of course is a problem independent from all of the
aforementioned requisites, like sorting a list of numbers
ascendingly. This can be done using only an iteration function (like
while
), assignment operators (setq
), a predicate
(<
) and the list primitives cons
, car
,
cdr
. All of these routines are available in any emacs on any
OS. Hence compatibility is not an issue.
Nonetheless, even such a simple thing can be transformed into a
broadly incompatible mess. Just assume that programmers choose the
way with least obstacles – some people prefer the term lazy though –
they will definitely use a primitive like sort
,
stable-sort
or sort*
. Or assume that sorting numbers is
just a small subproblem of a larger one and the context stipulates
that numbers must be arranged in vectors instead of lists. In such
cases consider the following small checklist.
Conditionalise with fboundp
and boundp
constructions
like:
(when (fboundp #'make-hash-table) ...)
or emacs flavours:
(when (featurep 'sxemacs) ...)
or operating systems, e.g.:
(when (eq system-type 'linux) ...)
Check for number, order and type of the arguments. If feasible write a small, simple testcase, like
(hash-table-p (make-hash-table :size 120 :test #'eq))
In order to use this sort of test within your code, for example to
trigger an implementation based on the success, you could use the
condition-case
construction:
(unless (condition-case nil (prog1 (hash-table-p (make-hash-table :size 120 :test #'eq)) (garbage-collect)) (error nil)) ;; return `nil' in case of error ...)
If feasible consider distributing a compatibility layer (see below). In this case, you must decouple your code such that it can either use the native implementation or the one provided by your compatibility layer.
If reinventing the wheel is not an issue, consider distributing the foreign requisites along with your code. This implies of course that you carefully checked the foreign resources for their compatibility. Also, make sure that the licences permit your decision.
In case there are too many requisite libraries to stuff them all into your package, or if licences forbid this, you should definitely give your users a notice, preferrably with detailed instructions on how to resolve the dependencies, at least include a location where users can fetch a known-to-work version.
This is probably the hardest case and there is definitely no patent remedy. If you cannot grant compatibility or detect missing necessities for a specific target, you must at least notify your users accordingly.
If you know other code or projects which manage the situation ‘better’ than you (i.e. with less requisites or better/native support) name those alternatives. If you do not know alternatives or other approaches seem to far away from your own it is definitely a good idea to explain in great detail what you are trying to do. This way users get the chance to look for alternatives themselves.
There is no generic approach to do that. However, there is a general idea which has been mentioned already in the introduction. Decouple your program so much that you do not call any functions, macros or variables which are suspicious to be incompatible.
Let us work out this idea in a practical scenario. Imagine you have to deal with aspect ratios as they occur in image data or video material, e.g. 16:9, 16:10 or the like. Now initial research reveals that both XEmacs 21.5.x and SXEmacs may provide built-in support for rational quotients, whereas XEmacs 21.4.x and GNU Emacs do not.
Now since we know that we only want to multiply fractions with integers, we merely provide this single operation, along with a constructor, a predicate and two accessors. Note, we will put the detection code along with the two implementations in the following example. In practice each implementation usually occupies a file of its own and the detection resides at a central location, for example in the library’s main file.
(when ;; check if SXEmacs is build with ENT support (bigq submodule) (and (featurep 'ent) (featurep 'bigq)) ;; the constructor (takes two arguments, two rational integers) (defalias 'expkg-make-quotient #'//) ;; the predicate (takes one argument, a bigq) (defalias 'expkg-quotient-p #'bigqp) ;; an accessor for the numerator of a quotient (takes a bigq) (defalias 'expkg-quotient-num #'numerator) ;; an accessor for the denominator of a quotient (takes a bigq) (defalias 'expkg-quotient-den #'denominator) ;; a * function for quotients by integers (defalias 'expkg-mult-quo-int #'*)) (when ;; or is it XEmacs with ratio support? SXE says `t', too, so check ;; for non-SXE (and (not (featurep 'sxemacs)) (featurep 'ratio)) (defalias 'expkg-make-quotient #'div) (defalias 'expkg-quotient-p #'ratiop) (defalias 'expkg-quotient-num #'numerator) (defalias 'expkg-quotient-den #'denominator) (defalias 'expkg-mult-quo-int #'*)) ;; just check if expkg-make-quotient is bound already (unless (fboundp #'expkg-make-quotient) ;; constructor (defun expkg-make-quotient (numerator denominator) "Return the quotient numerator/denominator" ;; we choose to store in a 3 component vector (vector 'quotient numerator denominator)) ;; predicate (defun expkg-quotient-p (object) "Return `t' if OBJECT is a quotient." ;; we check for the vector property, if length equals 3 and ;; finally if our indicator is present (and (vectorp object) (= (length object) 3) (eq (aref object 0) 'quotient))) ;; num accessor (defun expkg-quotient-num (quotient) "Return the numerator of QUOTIENT." ;; check if QUOTIENT is indeed what we expect (if (expkg-quotient-p quotient) (aref quotient 1) (error "Wrong type argument, quotientp `%s'" quotient))) ;; den accessor (defun expkg-quotient-den (quotient) "Return the denominator of QUOTIENT." ;; check if QUOTIENT is indeed what we expect (if (expkg-quotient-p quotient) (aref quotient 2) (error "Wrong type argument, quotientp `%s'" quotient))) ;; multiplication (defun expkg-mult-quo-int (quo int) "Return the product of QUO with INT." (if (and (expkg-quotient-p quo) (integerp int)) (expkg-make-quotient ;; new-num <- num * int (* (expkg-quotient-num quo) int) ;; new-den <- den (expkg-quotient-den quo)) ;; otherwise barf (error "Wrong type argument, quotientp `%s', integerp `%s'" quo int))))
Now that’s it! Now we can use the functions defined here to interface the quotient functionality in an abstract way. Moreover, this example demonstrates how to decouple a concept (quotients in this case) from the actual data type used to represent the concept, in order of appearance quotients can be represented as bigqs, as ratios or as vectors.
However, as you may have noticed not all internals can be abstracted in a sane way. In our case, the abstract layer will make up quotients of integers. We simply assumed – or knew – that these exist in all emacs flavours and behave identically.
Next: Documentation Tips, Previous: Performance Tips, Up: Tips [Contents][Index]