Skip to Content
Skip to Table of Contents

← Previous Article Next Article →

ATPM 9.03
March 2003

Columns

Extras

Reviews

Download ATPM 9.03

Choose a format:

Roll Your Own

by Charles Ross, cross@atpm.com

The Object of Programming—Part 2

Welcome back to Roll Your Own, the column where we learn together how to program Macintosh computers using the AppleScript language included with every system. Last month we learned about objects, and began building a simple object to store and manipulate angles. This month we’re going to continue on our object journey, improving our Angle object, and using it to build another object called a TrigAngle.

Before we dive into objects again, I’d like to announce that from now on I’m going to make the source code of our examples available on my Web site. Personally, when I learn new programming topics, I always type in the example code. I find that it helps me learn the concepts much more quickly. However, if you learn just as well from reading the source code and experimenting on your own, feel free to download the source code from my homepage. You may read this before I actually get a chance to set up the files, but hey, if you want to see pictures of my kids, stop on by.

As a refresher, here’s the source code for the latest version of our Angle object. The changes we will make to this object are based entirely on the work we did last month, so if you haven’t read the previous article, do so before continuing.

-- Demonstration of AppleScript's object oriented techniques
-- using an Angle as the sample object.
-- Written by Charles E. Ross, 12/26/02
-- Version 1.0v4
on MakeAngle(theSize, theUnits)
    -- Declare a script object called Angle.
    script Angle
        -- Angle has one property, the measurement of the angle in degrees.
        -- The size of the angle in other units is derived from the degrees.
        -- Initialize the size to the value of theSize.
        property degrees : theSize
        -- Sets the degrees property to the value passed to it.
        on SetDegrees(theSize)
            set degrees to theSize
        end SetDegrees
        -- Returns the value of the degrees property.
        on GetDegrees()
            return degrees
        end GetDegrees
        -- Sets the size of the angle by converting from radians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetRadians(theSize)
            set degrees to theSize * 180 / pi
        end SetRadians
        -- Returns the size of the angle in radians by converting
        -- from degrees.
        on GetRadians()
            return degrees * pi / 180
        end GetRadians
        -- Sets the size of the angle by converting from radians
        -- divided by pi, since the internal structure of the angle
        -- is stored in degrees.
        on SetRadiansByPi(theSize)
            set degrees to theSize * 180
        end SetRadiansByPi
        -- Returns the size of the angle in radians divided
        -- by pi by converting from degrees.
        on GetRadiansByPi()
            return degrees / 180
        end GetRadiansByPi
        -- Sets the size of the angle by converting from gradians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetGradians(theSize)
            set degrees to theSize * 9 / 10
        end SetGradians
        -- Returns the size of the angle in gradians by converting
        -- from degrees.
        on GetGradians()
            return degrees * 10 / 9
        end GetGradians
        -- Ensures that the angle is between 0 and 360 degrees.
        on NormalizeSize()
            -- If the angle is negative, add 360 to it until 
            -- it is positive.
            repeat until degrees ≥ 0
                set degrees to degrees + 360
            end repeat
            -- Perform a modulus operation on degrees to ensure that
            -- the size is less than 360.
            set degrees to degrees mod 360
        end NormalizeSize
    end script
    -- If theSize is not a number, produce an error message and code.
    if class of theSize is not in {real, integer} then
        error "Invalid size" number 101
        -- If theUnits is not a valid unit, produce an error message 
        -- and code.
    else if theUnits is not in ¬
        {"degrees", "radians", "gradians", "radians by pi"} then
        error "Invalid units" number 102
        -- If theUnits is degrees, just normalize the size.
    else if theUnits is "degrees" then
        tell Angle to NormalizeSize()
        -- If theUnits is radians, set the angle size using the 
        -- SetRadians() handler.
    else if theUnits is "radians" then
        tell Angle to SetRadians(theSize)
        -- If theUnits is gradians, set the angle size using the
        -- SetGradians() handler.
    else if theUnits is "gradians" then
        tell Angle to SetGradians(theSize)
        -- Otherwise the units must be radians by pi, so
        -- set the angle size using the SetRadiansByPi()
        -- handler.
    else
        tell Angle to SetRadiansByPi(theSize)
    end if -- class of theSize is not in {real, integer} then
    return Angle
end MakeAngle

Another useful feature of an angle object would be the ability to do arithmetic on angles, such as adding and subtracting angles. So we’ll add four new methods: AddAngle(), SubtractFromAngle(), MultiplyBy(), and DivideBy(). Each of these new handlers will return a new Angle object to the calling code.

-- Demonstration of AppleScript's object oriented techniques
-- using an Angle as the sample object.
-- Written by Charles E. Ross, 12/26/02
-- Version 1.0v5
on MakeAngle(theSize, theUnits)
    -- Declare a script object called Angle.
    script Angle
        -- Angle has one property, the measurement of the angle in degrees.
        -- The size of the angle in other units is derived from the degrees.
        -- Initialize the size to the value of theSize.
        property degrees : theSize
        -- Sets the degrees property to the value passed to it.
        on SetDegrees(theSize)
            set degrees to theSize
        end SetDegrees
        -- Returns the value of the degrees property.
        on GetDegrees()
            return degrees
        end GetDegrees
        -- Sets the size of the angle by converting from radians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetRadians(theSize)
            set degrees to theSize * 180 / pi
        end SetRadians
        -- Returns the size of the angle in radians by converting
        -- from degrees.
        on GetRadians()
            return degrees * pi / 180
        end GetRadians
        -- Sets the size of the angle by converting from radians
        -- divided by pi, since the internal structure of the angle
        -- is stored in degrees.
        on SetRadiansByPi(theSize)
            set degrees to theSize * 180
        end SetRadiansByPi
        -- Returns the size of the angle in radians divided
        -- by pi by converting from degrees.
        on GetRadiansByPi()
            return degrees / 180
        end GetRadiansByPi
        -- Sets the size of the angle by converting from gradians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetGradians(theSize)
            set degrees to theSize * 9 / 10
        end SetGradians
        -- Returns the size of the angle in gradians by converting
        -- from degrees.
        on GetGradians()
            return degrees * 10 / 9
        end GetGradians
        -- Ensures that the angle is between 0 and 360 degrees.
        on NormalizeSize()
            -- If the angle is negative, add 360 to it until 
            -- it is positive.
            repeat until degrees ≥ 0
                set degrees to degrees + 360
            end repeat
            -- Perform a modulus operation on degrees to ensure that
            -- the size is less than 360.
            set degrees to degrees mod 360
        end NormalizeSize

        -- Takes another angle as an argument and returns a new angle
        -- whose size is equal to the current angle plus the other angle.
        on AddAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the sum of the sizes of the current angle and the angle
            -- passed as a parameter.
            return MakeAngle(degrees + (GetDegrees() of otherAngle), ¬
                "degrees")
        end AddAngle
        -- Returns a new angle whose size is equal to the size of another
        -- angle minus the size of the current angle.
        on SubtractFromAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the difference between the angle passed as a parameter
            -- and the current angle.
            return MakeAngle((GetDegrees() of otherAngle) - degrees, ¬
                "degrees")
        end SubtractFromAngle
        -- Multiplies the size of the current angle by the provided factor
        -- and returns a new angle of that size.
        on MultiplyBy(factor)
            -- Create the new angle, setting the size of the new angle
            -- to the product of the size of the current angle multiplied
            -- by the factor provided.
            return MakeAngle(degrees * factor, "degrees")
        end MultiplyBy
        -- Divides the size of the current angle by the provided quotient.
        on DivideBy(quotient)
            -- Create the new angle, seeting the size of the new angle
            -- to the ratio of the size of the current angle divided by
            -- the quotient provided.
            return MakeAngle(degrees / quotient, "degrees")
        end DivideBy
    end script
    -- If theSize is not a number, produce an error message and code.
    if class of theSize is not in {real, integer} then
        error "Invalid size" number 101
        -- If theUnits is not a valid unit, produce an error message 
        -- and code.
    else if theUnits is not in ¬
        {"degrees", "radians", "gradians", "radians by pi"} then
        error "Invalid units" number 102
        -- If theUnits is degrees, just normalize the size.
    else if theUnits is "degrees" then
        tell Angle to NormalizeSize()
        -- If theUnits is radians, set the angle size using the 
        -- SetRadians() handler.
    else if theUnits is "radians" then
        tell Angle to SetRadians(theSize)
        -- If theUnits is gradians, set the angle size using the
        -- SetGradians() handler.
    else if theUnits is "gradians" then
        tell Angle to SetGradians(theSize)
        -- Otherwise the units must be radians by pi, so
        -- set the angle size using the SetRadiansByPi()
        -- handler.
    else
        tell Angle to SetRadiansByPi(theSize)
    end if -- class of theSize is not in {real, integer} then
    return Angle
end MakeAngle



on run
    set firstAngle to MakeAngle(300, "degrees")
    set secondAngle to MakeAngle(90, "degrees")
    display dialog "firstAngle + secondAngle = " & ¬
        GetDegrees() of (firstAngle's AddAngle(secondAngle))
    display dialog "secondAngle - firstAngle = " & ¬
        GetDegrees() of (firstAngle's SubtractFromAngle(secondAngle))
    display dialog "firstAngle - secondAngle = " & ¬
        GetDegrees() of (secondAngle's SubtractFromAngle(firstAngle))
    display dialog "firstAngle * 3 = " & ¬
        GetDegrees() of (firstAngle's MultiplyBy(3))
    display dialog "secondAngle / 9 = " & ¬
        GetDegrees() of (secondAngle's DivideBy(9))
end run

Of note with these new changes are how the passed object is referenced (just as if we were referencing it from anywhere except within the object itself), and the fact that we are returning an object. Once we’ve run these handlers we return an Angle object to the calling portion. The calling portion of the program can then store this object in a variable, or, as we’re doing, simply use the size in other commands. If the returned object isn’t stored in a variable, then it is unavailable after the current command, and would need to be re-calculated if there were a need for it again.

That’s as far as we’re going to take the Angle object. But we are going to continue using it in its current form and create another object that will inherit the properties and methods of the Angle object.

Yet another advantage of object oriented programming is the concept of inheritance, where a new object can inherit all of the properties and handlers of an existing object, can modify them as needed, or even overwrite them completely. The Angle object that we’ve created here would be fine in many circumstances, but any mathematical application is going to want more. Some of the calculations on angles that would be useful in such cases are the trigonometric functions, such as sine, cosine, and tangent. AppleScript doesn’t include these functions, although there are a number of scripting additions that do. We haven’t really covered scripting additions, but you can think of them as extensions to the AppleScript language. Truthfully, my first inclination was to simply put these handlers into our Angle object, but then I thought that creating a new TrigAngle object would be a great way to demonstrate the concept of inheritance.

An object can have a special property called the parent. When a child object has a parent object, all of the properties, handlers, and any other code within the parent object are part of the child object. Our first version of the TrigAngle object is below.

-- Demonstration of AppleScript's object oriented techniques
-- using an Angle as the sample object.
-- Written by Charles E. Ross, 12/26/02
-- Version 1.0v6
on MakeAngle(theSize, theUnits)
    -- Declare a script object called Angle.
    script Angle
        -- Angle has one property, the measurement of the angle in degrees.
        -- The size of the angle in other units is derived from the degrees.
        -- Initialize the size to the value of theSize.
        property degrees : theSize
        -- Sets the degrees property to the value passed to it.
        on SetDegrees(theSize)
            set degrees to theSize
        end SetDegrees
        -- Returns the value of the degrees property.
        on GetDegrees()
            return degrees
        end GetDegrees
        -- Sets the size of the angle by converting from radians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetRadians(theSize)
            set degrees to theSize * 180 / pi
        end SetRadians
        -- Returns the size of the angle in radians by converting
        -- from degrees.
        on GetRadians()
            return degrees * pi / 180
        end GetRadians
        -- Sets the size of the angle by converting from radians
        -- divided by pi, since the internal structure of the angle
        -- is stored in degrees.
        on SetRadiansByPi(theSize)
            set degrees to theSize * 180
        end SetRadiansByPi
        -- Returns the size of the angle in radians divided
        -- by pi by converting from degrees.
        on GetRadiansByPi()
            return degrees / 180
        end GetRadiansByPi
        -- Sets the size of the angle by converting from gradians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetGradians(theSize)
            set degrees to theSize * 9 / 10
        end SetGradians
        -- Returns the size of the angle in gradians by converting
        -- from degrees.
        on GetGradians()
            return degrees * 10 / 9
        end GetGradians
        -- Ensures that the angle is between 0 and 360 degrees.
        on NormalizeSize()
            -- If the angle is negative, add 360 to it until 
            -- it is positive.
            repeat until degrees ≥ 0
                set degrees to degrees + 360
            end repeat
            -- Perform a modulus operation on degrees to ensure that
            -- the size is less than 360.
            set degrees to degrees mod 360
        end NormalizeSize
        -- Takes another angle as an argument and returns a new angle
        -- whose size is equal to the current angle plus the other angle.
        on AddAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the sum of the sizes of the current angle and the angle
            -- passed as a parameter.
            return MakeAngle(degrees + (GetDegrees() of otherAngle), ¬
                "degrees")
        end AddAngle
        -- Returns a new angle whose size is equal to the size of another
        -- angle minus the size of the current angle.
        on SubtractFromAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the difference between the angle passed as a parameter
            -- and the current angle.
            return MakeAngle((GetDegrees() of otherAngle) - degrees, ¬
                "degrees")
        end SubtractFromAngle
        -- Multiplies the size of the current angle by the provided factor
        -- and returns a new angle of that size.
        on MultiplyBy(factor)
            -- Create the new angle, setting the size of the new angle
            -- to the product of the size of the current angle multiplied
            -- by the factor provided.
            return MakeAngle(degrees * factor, "degrees")
        end MultiplyBy
        -- Divides the size of the current angle by the provided quotient.
        on DivideBy(quotient)
            -- Create the new angle, seeting the size of the new angle
            -- to the ratio of the size of the current angle divided by
            -- the quotient provided.
            return MakeAngle(degrees / quotient, "degrees")
        end DivideBy
    end script
    -- If theSize is not a number, produce an error message and code.
    if class of theSize is not in {real, integer} then
        error "Invalid size" number 101
        -- If theUnits is not a valid unit, produce an error message 
        -- and code.
    else if theUnits is not in ¬
        {"degrees", "radians", "gradians", "radians by pi"} then
        error "Invalid units" number 102
        -- If theUnits is degrees, just normalize the size.
    else if theUnits is "degrees" then
        tell Angle to NormalizeSize()
        -- If theUnits is radians, set the angle size using the 
        -- SetRadians() handler.
    else if theUnits is "radians" then
        tell Angle to SetRadians(theSize)
        -- If theUnits is gradians, set the angle size using the
        -- SetGradians() handler.
    else if theUnits is "gradians" then
        tell Angle to SetGradians(theSize)
        -- Otherwise the units must be radians by pi, so
        -- set the angle size using the SetRadiansByPi()
        -- handler.
    else
        tell Angle to SetRadiansByPi(theSize)
    end if -- class of theSize is not in {real, integer} then
    return Angle
end MakeAngle



on MakeTrigAngle(theSize, theUnits)
    set Angle to MakeAngle(theSize, theUnits)
    script TrigAngle
        property parent : Angle
    end script
end MakeTrigAngle

on run
    GetRadiansByPi() of MakeTrigAngle(270, "degrees")
end run

At this point, the properties and handlers of the TrigAngle object are exactly the same as those of the Angle object. This is a case where the order of code makes a big difference. You’ll notice that in our MakeAngle() handler, all of the code that is run in the handler appears after the code for the script object. In this case the set Angle to MakeAngle(theSize, theUnits) line appears before the script object code. This is because we need an initiated object before we can say that it is the parent of our new object.

At this time, our TrigAngle object would behave exactly like our Angle object. We can call handlers such as SetDegrees() or AddAngle() just as if we were using an Angle object instead. That’s not very useful, so let’s add some new features to our TrigAngle object.

-- Demonstration of AppleScript's object oriented techniques
-- using an Angle as the sample object.
-- Written by Charles E. Ross, 12/26/02
-- Version 1.0v7
on MakeAngle(theSize, theUnits)
    -- Declare a script object called Angle.
    script Angle
        -- Angle has one property, the measurement of the angle in degrees.
        -- The size of the angle in other units is derived from the degrees.
        -- Initialize the size to the value of theSize.
        property degrees : theSize
        -- Sets the degrees property to the value passed to it.
        on SetDegrees(theSize)
            set degrees to theSize
        end SetDegrees
        -- Returns the value of the degrees property.
        on GetDegrees()
            return degrees
        end GetDegrees
        -- Sets the size of the angle by converting from radians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetRadians(theSize)
            set degrees to theSize * 180 / pi
        end SetRadians
        -- Returns the size of the angle in radians by converting
        -- from degrees.
        on GetRadians()
            return degrees * pi / 180
        end GetRadians
        -- Sets the size of the angle by converting from radians
        -- divided by pi, since the internal structure of the angle
        -- is stored in degrees.
        on SetRadiansByPi(theSize)
            set degrees to theSize * 180
        end SetRadiansByPi
        -- Returns the size of the angle in radians divided
        -- by pi by converting from degrees.
        on GetRadiansByPi()
            return degrees / 180
        end GetRadiansByPi
        -- Sets the size of the angle by converting from gradians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetGradians(theSize)
            set degrees to theSize * 9 / 10
        end SetGradians
        -- Returns the size of the angle in gradians by converting
        -- from degrees.
        on GetGradians()
            return degrees * 10 / 9
        end GetGradians
        -- Ensures that the angle is between 0 and 360 degrees.
        on NormalizeSize()
            -- If the angle is negative, add 360 to it until 
            -- it is positive.
            repeat until degrees ≥ 0
                set degrees to degrees + 360
            end repeat
            -- Perform a modulus operation on degrees to ensure that
            -- the size is less than 360.
            set degrees to degrees mod 360
        end NormalizeSize
        -- Takes another angle as an argument and returns a new angle
        -- whose size is equal to the current angle plus the other angle.
        on AddAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the sum of the sizes of the current angle and the angle
            -- passed as a parameter.
            return MakeAngle(degrees + (GetDegrees() of otherAngle), ¬
                "degrees")
        end AddAngle
        -- Returns a new angle whose size is equal to the size of another
        -- angle minus the size of the current angle.
        on SubtractFromAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the difference between the angle passed as a parameter
            -- and the current angle.
            return MakeAngle((GetDegrees() of otherAngle) - degrees, ¬
                "degrees")
        end SubtractFromAngle
        -- Multiplies the size of the current angle by the provided factor
        -- and returns a new angle of that size.
        on MultiplyBy(factor)
            -- Create the new angle, setting the size of the new angle
            -- to the product of the size of the current angle multiplied
            -- by the factor provided.
            return MakeAngle(degrees * factor, "degrees")
        end MultiplyBy
        -- Divides the size of the current angle by the provided quotient.
        on DivideBy(quotient)
            -- Create the new angle, seeting the size of the new angle
            -- to the ratio of the size of the current angle divided by
            -- the quotient provided.
            return MakeAngle(degrees / quotient, "degrees")
        end DivideBy
    end script
    -- If theSize is not a number, produce an error message and code.
    if class of theSize is not in {real, integer} then
        error "Invalid size" number 101
        -- If theUnits is not a valid unit, produce an error message 
        -- and code.
    else if theUnits is not in ¬
        {"degrees", "radians", "gradians", "radians by pi"} then
        error "Invalid units" number 102
        -- If theUnits is degrees, just normalize the size.
    else if theUnits is "degrees" then
        tell Angle to NormalizeSize()
        -- If theUnits is radians, set the angle size using the 
        -- SetRadians() handler.
    else if theUnits is "radians" then
        tell Angle to SetRadians(theSize)
        -- If theUnits is gradians, set the angle size using the
        -- SetGradians() handler.
    else if theUnits is "gradians" then
        tell Angle to SetGradians(theSize)
        -- Otherwise the units must be radians by pi, so
        -- set the angle size using the SetRadiansByPi()
        -- handler.
    else
        tell Angle to SetRadiansByPi(theSize)
    end if -- class of theSize is not in {real, integer} then
    return Angle
end MakeAngle


on MakeTrigAngle(theSize, theUnits)
    set Angle to MakeAngle(theSize, theUnits)
    script TrigAngle
        property parent : Angle
        

        on Factorial(x)
            if x is 0 then
                return 1
            else
                return x * (Factorial(x - 1))
            end if
        end Factorial
        on Cosine()
            set convergingValue to 0
            repeat with n from 0 to 9
                set convergingValue to convergingValue + ¬
                    ((-1) ^ n) * (GetRadians() ^ (2 * n)) / ¬
                    (Factorial(2 * n))
            end repeat
            return convergingValue
        end Cosine
        on Sine()
            return (1 - (Cosine() ^ 2)) ^ (1 / 2)
        end Sine
        on Tangent()
            return Sine() / (Cosine())
        end Tangent
        on Cosecant()
            return 1 / (Sine())
        end Cosecant
        on Secant()
            return 1 / (Cosine())
        end Secant
        on Cotangent()
            return 1 / (Tangent())
        end Cotangent
    end script
end MakeTrigAngle

Now we have a more useful example of inheritance. While our TrigAngle object can do everything our Angle object can, it also has the ability to return the trigonometric functions of the angle.

When one object inherits from another, it can also alter the handlers in the parent object. Our TrigAngle object would serve our needs better if it stored the size of the angle in radians rather than in degrees since the trigonometric functions deal with radians. But storing the size of the angle in radians would necessitate changing how some of the original Angle code needs to work. After all, the algorithms for computing the trigonometric functions use angles measured in radians, but since our angle stores its measurement in degrees, each time we compute one of the trigonometric functions, we have to convert the degrees to radians. By storing the size of the angle in radians and changing how some of the inherited handlers behave, we can glean a bit more speed from our object.

When an object inherits from a parent object, every handler called to the child object will behave as it would in the parent object unless we redefine it in the child object. We redefine such a handler by simply declaring it again and defining in the new handler code the new behavior of the handler. However, we can still access the functionality of the parent object by using the continue statement, which will call the parent object’s version of the handler and execute its code.

Below is the code for our refined TrigAngle object.

-- Demonstration of AppleScript's object oriented techniques
-- using an Angle as the sample object.
-- Written by Charles E. Ross, 12/26/02
-- Version 1.0v8
on MakeAngle(theSize, theUnits)
    -- Declare a script object called Angle.
    script Angle
        -- Angle has one property, the measurement of the angle in degrees.
        -- The size of the angle in other units is derived from the degrees.
        -- Initialize the size to the value of theSize.
        property degrees : theSize
        -- Sets the degrees property to the value passed to it.
        on SetDegrees(theSize)
            set degrees to theSize
        end SetDegrees
        -- Returns the value of the degrees property.
        on GetDegrees()
            return degrees
        end GetDegrees
        -- Sets the size of the angle by converting from radians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetRadians(theSize)
            set degrees to theSize * 180 / pi
        end SetRadians
        -- Returns the size of the angle in radians by converting
        -- from degrees.
        on GetRadians()
            return degrees * pi / 180
        end GetRadians
        -- Sets the size of the angle by converting from radians
        -- divided by pi, since the internal structure of the angle
        -- is stored in degrees.
        on SetRadiansByPi(theSize)
            set degrees to theSize * 180
        end SetRadiansByPi
        -- Returns the size of the angle in radians divided
        -- by pi by converting from degrees.
        on GetRadiansByPi()
            return degrees / 180
        end GetRadiansByPi
        -- Sets the size of the angle by converting from gradians,
        -- since the internal structure of the angle is stored
        -- in degrees.
        on SetGradians(theSize)
            set degrees to theSize * 9 / 10
        end SetGradians
        -- Returns the size of the angle in gradians by converting
        -- from degrees.
        on GetGradians()
            return degrees * 10 / 9
        end GetGradians
        -- Ensures that the angle is between 0 and 360 degrees.
        on NormalizeSize()
            -- If the angle is negative, add 360 to it until 
            -- it is positive.
            repeat until degrees ≥ 0
                set degrees to degrees + 360
            end repeat
            -- Perform a modulus operation on degrees to ensure that
            -- the size is less than 360.
            set degrees to degrees mod 360
        end NormalizeSize
        -- Takes another angle as an argument and returns a new angle
        -- whose size is equal to the current angle plus the other angle.
        on AddAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the sum of the sizes of the current angle and the angle
            -- passed as a parameter.
            return MakeAngle(degrees + (GetDegrees() of otherAngle), ¬
                "degrees")
        end AddAngle
        -- Returns a new angle whose size is equal to the size of another
        -- angle minus the size of the current angle.
        on SubtractFromAngle(otherAngle)
            -- Create the new angle, setting the size of the new angle
            -- to the difference between the angle passed as a parameter
            -- and the current angle.
            return MakeAngle((GetDegrees() of otherAngle) - degrees, ¬
                "degrees")
        end SubtractFromAngle
        -- Multiplies the size of the current angle by the provided factor
        -- and returns a new angle of that size.
        on MultiplyBy(factor)
            -- Create the new angle, setting the size of the new angle
            -- to the product of the size of the current angle multiplied
            -- by the factor provided.
            return MakeAngle(degrees * factor, "degrees")
        end MultiplyBy
        -- Divides the size of the current angle by the provided quotient.
        on DivideBy(quotient)
            -- Create the new angle, seeting the size of the new angle
            -- to the ratio of the size of the current angle divided by
            -- the quotient provided.
            return MakeAngle(degrees / quotient, "degrees")
        end DivideBy
    end script
    -- If theSize is not a number, produce an error message and code.
    if class of theSize is not in {real, integer} then
        error "Invalid size" number 101
        -- If theUnits is not a valid unit, produce an error message 
        -- and code.
    else if theUnits is not in ¬
        {"degrees", "radians", "gradians", "radians by pi"} then
        error "Invalid units" number 102
        -- If theUnits is degrees, just normalize the size.
    else if theUnits is "degrees" then
        tell Angle to NormalizeSize()
        -- If theUnits is radians, set the angle size using the 
        -- SetRadians() handler.
    else if theUnits is "radians" then
        tell Angle to SetRadians(theSize)
        -- If theUnits is gradians, set the angle size using the
        -- SetGradians() handler.
    else if theUnits is "gradians" then
        tell Angle to SetGradians(theSize)
        -- Otherwise the units must be radians by pi, so
        -- set the angle size using the SetRadiansByPi()
        -- handler.
    else
        tell Angle to SetRadiansByPi(theSize)
    end if -- class of theSize is not in {real, integer} then
    return Angle
end MakeAngle


on MakeTrigAngle(theSize, theUnits)
    -- Initiate an Angle object storing it in a variable by the same name.
    set Angle to MakeAngle(theSize, theUnits)
    script TrigAngle
        -- TrigAngle is a child of Angle.
        property parent : Angle
        -- TrigAngle should store the size in radians.
        property radians : GetRadians()
        -- The Factorial() handler is used to calculate the cosine
        -- (there therefore the other trig functions).
        on Factorial(x)
            if x is 0 then
                return 1
            else
                return x * (Factorial(x - 1))
            end if
        end Factorial
        -- Uses a converging series to calculate the cosine
        -- of the angle.
        on Cosine()
            set convergingValue to 0
            repeat with n from 0 to 9
                set convergingValue to convergingValue + ¬
                    ((-1) ^ n) * (radians ^ (2 * n)) / ¬
                    (Factorial(2 * n))
            end repeat
            return convergingValue
        end Cosine
        -- Calculates the sine of the angle by deriving it from
        -- the cosine.
        on Sine()
            return (1 - (Cosine() ^ 2)) ^ (1 / 2)
        end Sine
        -- Calculates the tangent of the angle by deriving it
        -- from the sine and cosine.
        on Tangent()
            return Sine() / (Cosine())
        end Tangent
        -- Calculates the cosecant of the angle by deriving it
        -- from the sine.
        on Cosecant()
            return 1 / (Sine())
        end Cosecant
        -- Calculates the secant of the angle by deriving it
        -- from the cosine.
        on Secant()
            return 1 / (Cosine())
        end Secant
        -- Calculates the cotangent of the angle by deriving it
        -- from the tangent.
        on Cotangent()
            return 1 / (Tangent())
        end Cotangent

        -- Sets the degrees property of the angle and the internal
        -- radians property by calling the parent SetDegrees()
        -- handler.
        on SetDegrees(theSize)
            continue SetDegrees(theSize)
            set radians to GetRadians()
        end SetDegrees
        -- Sets the degrees property of the angle and the internal
        -- radians property by calling the parent SetRadians()
        -- handler.
        on SetRadians(theSize)
            continue SetRadians(theSize)
            set radians to GetRadians()
        end SetRadians
        -- Sets the degrees property of the angle and the internal
        -- radians property by calling the parent SetGradians()
        -- handler.
        on SetGradians(theSize)
            continue SetGradians(theSize)
            set radians to GetRadians()
        end SetGradians
        -- Sets the degrees property of the angle and the internal
        -- radians property by calling the parent SetRadiansByPi()
        -- handler.
        on SetRadiansByPi(theSize)
            continue SetRadiansByPi(theSize)
            set radians to GetRadians()
        end SetRadiansByPi
        on NormalizeSize()
            -- This handler completely overrides the parent handler.
            -- If the angle is negative, add 2*pi to it until 
            -- it is positive.
            repeat until degrees ≥ 0
                set degrees to degrees + (2 * pi)
            end repeat
            -- Perform a modulus operation on degrees to ensure that
            -- the size is less than 2*pi.
            set degrees to degrees mod (2 * pi)
        end NormalizeSize
    end script
    tell TrigAngle to set radians to GetRadians() of Angle

end MakeTrigAngle

For the handlers in our original Angle object that set the size of the angle, we still want to have the functionality run, still setting the size as before. But now that we have a radians property, we also want to make sure that when the size of the angle changes, this value is updated. So our redefined handlers for setting the size of a TrigAngle will simply call the parent version of themselves and then manually set the radians property by using the GetRadians() handler (which we do not alter).

So what happens when we create a TrigAngle object and send a SetDegrees() message to it? Consider the following code fragment which does exactly that:

set myTrig to MakeTrigAngle(pi / 2, "radians")
tell myTrig to SetDegrees(270)

The first line creates a new TrigAngle object, sets its size to /2 radians (90 degrees), and stores it in the myTrig variable. When MakeTrigAngle() is called, the first thing it does is create a new Angle object by calling MakeAngle(). Then we have the code for the TrigAngle object, which, since we are executing the handler it is in, creates an instance of it of the same name (TrigAngle). Since TrigAngle has a radians property, we use the GetRadians() handler to set the value of this property. Remember, this handler is defined in the Angle object from which TrigAngle inherits. At this point, TrigAngle has a value of 90 in its degrees property and a value of /2 in its radians property. Finally, we return the TrigAngle object to the calling code.

The next line of our program snippet calls myTrig’s SetDegrees() handler, passing it a value of 270. Since TrigAngle has defined its own SetDegrees() handler, this is what gets executed, not the one defined in the Angle object. However, the first statement in TrigAngle’s SetDegrees() handler is a continue statement which does execute the version in the Angle object. Once we have set the degrees property, we can use the GetRadians() handler to set the radians property.

On the other hand, the NormalizeSize() handler should completely replace the handler parent handler of the same name. Normalizing now entails that the size of the angle is between 0 and 2 rather than 0 and 360 (because there are 2 radians in a circle), so there’s no need or desire to use the continue statement to call the parent handler.

Inheritance allows you to move from the simple to the complex with objects, specifying only the behavior that is changing for each level. We could have included everything our TrigAngle object does in our original Angle object. However, a general rule when creating objects is to build code only for what you need for each object and when you need to expand on that code, create a new object that inherits from the first. This keeps the code in each object as simple and as useful as possible.

This month, I have two exercises for you. First of all, some angles don’t have defined tangents, cotangents, cosecants and secants. Sines and cosines can be zero, and when this is the case the trigonometric functions that divide by them aren’t defined. Edit the TrigAngle object so that appropriate error messages are generated when these cases arise.

Your second task is to create a new object representing a list, but with sorting, adding and removing methods. Each method will return a new list rather than alter the list that the method belongs to. So, each of the lines in the following code snippet would return the appropriate list.

set sortedList to orignalList's Sort()
set addedList to orignalList's AddItem(newItem)
set removedList to originalList's RemoveItem(theItem)

Beginning next month, we’re going to work on putting everything we’ve learned together into a complete program built entirely with AppleScript. We’ll go from a definition of what the program will do to a complete application that will perform the specified task. Until next month, happy programming!

Also in This Series

Reader Comments (1)

Emmanuel · March 3, 2003 - 08:43 EST #1
If you want another example of OO in AppleScript, please check my BBEdit Disk Browser Suite.

http://scriptdigital.com/divers/bbeditdiskbrowserscripts.html and http://scriptdigital.com/divers/bbeditlib.html

Cheers

-Emmanuel

Add A Comment





 E-mail me new comments on this article