Mizzen tutorial
From HalyardWiki
Halyard uses a SmallTalk style object model called Mizzen, which was influenced heavily by Ruby. This is intended to be a basic, top-down view of the object model, for anyone with some familiarity with object-oriented programming and some familiarity with Scheme. If you are interested in the low-level details of how this all works, see Mizzen details. If you need want a tutorial that starts from the ground up on developing multimedia applications in Halyard, see the Halyard tutorial.
Contents |
[edit] Classes
You can define a class using the define-class form. If you don't specify a superclass, it will by default inherit from %object%. Here's an example of a simple class.
(define-class %car% () (attr doors) (attr seats))
Here we see the basic declaration of the class, (define-class %car% (), which defines a class %car% and uses the default superclass %object% by leaving the superclass empty, (). We name classes with % at the beginning and end of the name, so we can tell when we're talking about a class more easily, but you can use any identifier you want if you really feel like.
A %car% has two attributes defined, doors and seats. These are like slots, attributes, or member variables in other languages, depending on what language you are familiar with. They do not have default values, so they must be initialized when you create the object.
We can now create %car% objects, and access the attributes. When you see ;;=>, that means that the previous expression returns the value shown afterwards; for instance, if you open up a Halyard project, and type this code into the Listener, you will get the result listed.
(define my-car (%car% .new :doors 4 :seats 5)) (my-car .doors) ;;=> 4 (my-car .seats) ;;=>5
As you can see, you create an object using (%class% .new :attribute-name value ...), and you can then access the attributes using (object .attribute-name).
[edit] Subclasses
We can create a subclass of %car%, %coupe%. A coupe is a 2-door car, so we want to fix the value of doors to 2. We'll also provide a default value for the number of seats, but allow that to be different between two different coupes.
(define-class %coupe% (%car%) (value doors 2) (default seats 2)) (define sporty (%coupe% .new)) (sporty .doors) ;;=> 2 (sporty .seats) ;;=> 2 (define small (%coupe% .new :seats 4)) (small .doors) ;;=> 2 (small .seats) ;;=> 4
[edit] Modifying objects
By default, all attributes on an object are immutable. If you want to be able to modify an object, you need to declare it as writable?.
(define-class %sedan% (%car%) (value doors 4) (default seats 5) (attr trunk-contents '() :writable? #t)) (define family-car (%sedan% .new :trunk-contents '(soccer-ball))) (family-car .trunk-contents) ;;=> soccer-ball (set! (family-car .trunk-contents) (cons 'cleats (family-car .trunk-contents))) (family-car .trunk-contents) ;;=> (cleats soccer-ball)
This introduces a few new things. First, we setup values and defaults just like before with the %coupe%. Then we create a new attribute, with a default value, '() (this is how you can give an attribute a default in the class it's defined in). We declare that this attribute is writable?, so we can modify it later.
We can get the value of the attribute just like before. But now, we can also set the value of the attribute, using (set! (object .attribute-name) value). After we set the value, we'll get the new value when we fetch it.
[edit] Methods
Now that we can get and set attributes on our objects, we're probably going to want do define some methods. We'll create a better version of the %sedan% class that better encapsulates its data.
(define-class %sedan% (%car%)
(value doors 4)
(default seats 5)
(attr trunk-contents '() :writable? #t)
(def (add-to-trunk! item)
(set! (.trunk-contents) (cons item (.trunk-contents))))
(def (remove-from-trunk! item)
(set! (.trunk-contents) (remove item (.trunk-contents)))))
(define family-car (%sedan% .new :trunk-contents '(soccer-ball)))
(family-car .add-to-trunk! 'baseball-bat)
(family-car .trunk-contents)
;;=> (baseball-bat soccer-ball)
(family-car .remove-from-trunk! 'soccer-ball)
(family-car .trunk-contents)
;;=> (baseball-bat)
Whoa, there's a lot going on here. Let's take this piece by piece. You've seen the basic definition of %sedan% and its attributes, so lets start from the first method definition.
[edit] Method definition
(def (add-to-trunk! item) ...)
A method is created using (def (method-name arguments ...) body ...). This acts just like a regular Scheme (define (function-name arguments ...) body ...), except it defines a method on the class that you're in. The exclamation mark at the end of our name is just a convention to indicate that this method alters the object in some way, rather than just computing a value and returning it.
[edit] Self
Now, how about the (.trunk-contents) inside of here? This is just a way of accessing the trunk-contents attribute on the current object, rather than explicitly specifying an object. Whenever you are inside a method on an object, and access an attribute or call a method, it is assumed that you are talking about the current object unless otherwise specified. If you need to access the current object explicitly, for instance to pass it to a function or method call, you can refer to it as self. In fact, (.trunk-contents) is just a convenient abbreviation for (self .trunk-contents)
When we define the add-to-trunk! method, we are defining a method that adds the given item to the list of objects in the current object's trunk-contents attribute. Likewise, remove-from-trunk! removes an item from the current object's trunk-contents
[edit] Calling methods
Once we've defined a method, we need a way to call it. You saw that in the (family-car .add-to-trunk! 'baseball-bat) expression. Calling a method on an object is easy; just call (object .method-name arguments ...). This works a lot like function application in scheme, except that inside the method, self will equal the object that you called the method on, as described above.
This looks an awful lot like fetching an attribute from a method. In fact, it is the same thing; when you define an attribute, Halyard automatically defines a getter method for you, which is a method that takes no arguments and returns the value of that attribute. If you're familiar with most basic object-oriented design principles, you'll know that you usually want to access slots or member variables through getters and setters rather than directly. This way, you can replace the implementation of an attribute with something else (perhaps a method that computes the value from another attribute), and none of the code that calls it will have to know about the change. Halyard conveniently provides automatic getter and setter methods for each attribute, so you don't have to wrap getters and setters around slots of member variables yourself.
[edit] Inheritance
What does it mean to inherit from a superclass? It generally means that you will respond to all of the same methods that your superclass does, and unless you explicitly define some behavior to be different, all of your methods and attributes will behave as they would in your superclass.
We've already seen some examples of inheritance, where %sedan% has all of the attributes from %car%, but modifies their defaults. We can also override our superclass's methods, providing new definitions, or wrap the old definitions in something new.
(define-class %limited-sedan% (%sedan%)
(def (add-to-trunk! item)
(unless (< (length (.trunk-contents)) 5)
(super))))
In this example, we override the add-to-trunk! method, providing a new definition that checks if we have room in the trunk before adding an item. Rather than duplicate the code we had in the original method, we can instead call (super), which is a special function that acts like you had called the superclass's version of the method, with the same arguments as were passed in originally.
[edit] Advanced features
The object model in Halyard is designed to be extremely powerful and flexible, to allow people to add their own features and extend the language to the needs of their current project. Halyard's own card and element system takes very heavy advantage of this, modifying the object model so you can declare custom nested elements with their own attributes and methods that will automatically be created and properly nested in the display hierarchy. For further information on how this is all built up from a simple core, see Halyard object model details.
[edit] Re-opening classes
Sometimes, instead of creating a subclass and overriding methods, what you really want to do is to modify an existing class. This is very easy; just use (with-instance %class% body ...), and do anything in the body that you would do in the body of a define-class.
(with-instance %car%
(def (door-to-seat-ratio)
(/ (.doors) (.seats))))
(define my-sedan (%sedan% .new))
(my-sedan .door-to-seat-ratio)
;;=> 4/5

