Copying things is easy, right? For years, we’ve been using the simple equal (=) assignment operator in SAS to make copies of our variables. Once we’ve done that we have two completely independent variables which happen, at that time, to have identical values. This is certainly true in Base SAS but the picture becomes a little more complicated when we start looking at SAS DS2. This article will demonstrate that when copying package variables in SAS DS2 there are two types of copy operation and we must be careful which one we choose or leave our programs vulnerable to unintended consequences.
The Example Package
To demonstrate the problem let’s say we’re modelling a school and have a simple Pupil package based on SASHELP.CLASS. Here is the code for the package:
proc ds2; package Pupil / overwrite=yes; dcl int age; dcl double height weight; dcl varchar(10) name; dcl char(1) sex; forward makepupil; method Pupil(int inAge, double inHeight, varchar(10) inName, char(1) inSex,
double inWeight); makepupil(inAge, inHeight, inName, inSex, inWeight); end; method Pupil(package Pupil oldPupil); makepupil(oldPupil.Age, oldPupil.Height, oldPupil.Name, oldPupil.Sex, oldPupil.Weight); end; method makepupil(int inAge, double inHeight, varchar(10) inName, char(1)
inSex, double inWeight); this.age=inAge; this.height=inHeight; this.name=inName; this.sex=inSex; this.weight=inWeight; end; method GetAge() returns int; return age; end; method GetHeight() returns double; return height; end; method GetWeight() returns double; return weight; end; method GetName() returns varchar(10); return name; end; method GetSex() returns char(1); return sex; end; method SetAge(int age); this.age=age; end; endpackage; run; quit;
There are several things to note here:
Normally when you want to copy the values of one variable to another you simply use the equals assignment operator and that works in DS2 in the same way as in traditional Base SAS code for all the standard DS2 data types (integer, float, double etc.). The picture becomes a little cloudier however when we start to think about copying instances of packages. Here’s the full code for the test run:
proc ds2; data _null_; dcl package Pupil jane(15,1,'Jane','F',2); dcl package Pupil emma; dcl int janeage; dcl int emmaage; method shallowcopy(); put '****** Shallow Copying ******'; /* Make Emma a copy of Jane */ emma=jane; emmaage=emma.GetAge(); put emmaage=; janeage=jane.GetAge(); put janeage=; /* Change Jane's age */ jane.SetAge(16); emmaage=emma.GetAge(); put emmaage=; janeage=jane.GetAge(); put janeage=; end; method deepcopy(); put '****** Deep Copying ******'; /* Abandone the first instance of Emma and make a new copy */ emma=_new_ Pupil(jane); emmaage=emma.GetAge(); put emmaage=; janeage=jane.GetAge(); put janeage=; /* Change Jane's age */ jane.SetAge(14); emmaage=emma.GetAge(); put emmaage=; janeage=jane.GetAge(); put janeage=; end; method run(); shallowcopy(); deepcopy(); end; enddata; run; quit;
If we run the code this is what we get in the log when we run the first method - shallowcopy() which uses the equals operator to perform the copy before changing Jane's age:
****** Shallow Copying ****** emmaage=15 janeage=15
So far so good – both Emma and Jane have the same age which is what we would expect. However, what happens when we change Jane’s age by calling the SetAge() method? This is what we get in the log:
You can see that not only has Jane’s age changed but also Emma’s has changed! This is because the assignment operator has caused both the package instance for Jane and the package instance for Emma to point at the same area of memory, making Emma a shallow copy of Jane (with apologies to anyone named Emma reading this). It may be that this is what you want and if so that’s OK but it may well not be - so how do we handle this? The answer lies in creating Emma as a deep copy of Jane.
If we want to create a completely independent copy of a package instance we need to create something called a deep copy. In the example package we can do that by using the second of the Pupil package constructors (the one which accepts an instance of the pupil package as it’s argument).
This constructor takes each of the variables from the package and assigns their values one by one to new variables. Of course, if any of these variables are themselves instances of other packages then care must be taken to ensure that they too are deep copied in the same way.
This time we’ll make Emma a copy of Jane but using this second constructor, which is what happens when the deepcopy() method runs and see what we get in the log:
****** Deep Copying ****** emmaage=16 janeage=16
Now we'll see what happens after Jane's age is changed by calling the SetAge() method:
This time Emma’s age remains unchanged.
We’ve seen that there are two distinct types of copy operation when using SAS DS2 package variables creating either shallow or deep copies and that care must be taken to ensure that we really have what we want and are aware of the implications of each type of copy. We’ve also seen how to create deep copies to ensure data integrity against accidental and unexpected changing of data values enabling us to employ copying of packages with greater confidence and security.