Note that initializers support overloading, which makes the use of named initializers unnecessary in most cases, and will be discussed in the next chapter.
The Language Reference & Guide
- Welcome to Emojicode
- Syntax
- The Basics
- Literals
- Variables and Assignment
- Control Flow
- Classes & Value Types
- Overloading
- Operators
- Optionals
- Errors
- Inheritance and Overriding
- Protocols
- Enumerations
- Types and Namespaces
- Types as Values
- Documentation
- Generics
- Callables
- Packages
- Threads
- Safe and Unsafe Code
- Memory Management
- References
- Appendix: The Emojicode Compiler
Classes & Value Types
Emojicode features three kind of types that feature characteristics of object-orientation: Classes, Value Types and Enumerations. This chapter is soley dedicated to classes and value types. A separate chapter is devoted to enumerations, which have a lot in common with value types.
Classes versus Value Types
There are two significant differences between classes and value types:
Instances of classes are always allocated on the heap and are passed by reference. Instances of value types are, as their name suggests, passed by value.
Classes feature inheritance while value types donβt.
This makes value types suitable when only the actual data represented matters and not the identity of the object. In other words, you should use value types when you only care about the values they carry and not about whether you have a particular instance of the value type.
Dates or mathematical vectors are good examples of types that should be value types, whereas a type representing a customer should be a class, as it does matter with which customer youβre dealing and not just the data it holds. Think of it like this: There may be many customers named βJohn Smithβ but the customers are still different people and therefore are represented by different objects.
Defining a Class
Let us define a class representing a customer:
π π©βπΌ π
π
As mentioned previously, classes feature inheritance. We can therefore also declare a subclass of our π©βπΌ class:
π π©βπ π©βπΌ π
π
Some of our customers are astronauts, so we created the subclass π©βπ. To make a class a subclass, denote its superclass behind the new classes name. If you donβt provide a superclass, the class doesnβt have one.
Defining a Value Type
Naturally, we also need to maintain our customers credit card information to be able to bill them. Credit card information is a great example of a value type so letβs define one:
π π³ π
π
The definition of a value type is quite similar to the definition of a class. We just used π instead of π. Furthermore, value types cannot have a supertype.
Instance Variables
We have declared various types now, but so far these are pretty useless as they do not store any information at all.
Let us change this by adding instance variables. The normal syntax for declaring variables is used in value types and classes too:
π π³ π
ππ number π‘
ππ expiration_date π‘
ππ security_code π‘
π
We have added some variables to store the credit card information. (We do not maintain that this is a particularly good way of structuring credit card information but this is just an example. π)
π π©βπΌ π
ππ firstname π‘
ππ lastname π‘
ππ creditcard π³
π
π π©βπ π©βπΌ π
ππ days_in_space π’
π
We have also added some information to the normal customer π©βπΌ and the astronaut customer π©βπ.
Instance variables are private to the instance and cannot be accessed from outside but only in initializers and methods. If you want to access instance variables from outside you have to write getters and setters. Instance variables are also kept private from subclasses.
Default Initialization Value
You can also specify a value to which an instance variable will be initialized:
π π©βπΌ π
ππ firstname π‘ β¬
οΈ π€Susanπ€
ππ lastname π‘ β¬
οΈ π€Rodgersπ€
ππ creditcard π³ β¬
οΈ ππ³ π€48829284848291π€ π€12/22π€ π€513π€βοΈ
π
Note that the expressions are not evaluated in the context of the initializer. Furthermore, these expressions are always evaluated when an initializer is called.
Syntax
We have summarized the syntax here as it is a great deal of definitions and we didnβt want to clutter the previous sections.
type-definition βΆ [documentation-comment] [π] [ππ’] [π] [π»] type-definition-main
type-definition-main βΆ class | value-type | protocol | enum
class βΆ π type-identifier [generic-parameters] [superclass] type-body
type-body βΆ π type-body-declarations π
type-body-declarations βΆ type-body-declaration | type-body-declaration type-body-declarations
type-body-declaration βΆ type-body-attributes type-body-declaration-main
type-body-attributes βΆ [documentation-comment] [π₯―] [β οΈ] [π] [βοΈ] [π] [β£οΈ] [π] [π] [ππ₯‘] [access-level]
type-body-declaration-main βΆ instance-variable-declaration | method | initializer
type-body-declaration-main βΆ protocol-conformance | enum-value
type-body-declaration-main βΆ deinitializer
instance-variable-declaration βΆ declaration [β¬
οΈ expression]
superclass βΆ type
value-type βΆ π type-identifier [generic-parameters] type-body
initializer βΆ π [initializer-name] [init-parameters] [error-type] body
initializer-name βΆ βΆοΈ emoji-id
init-parameters βΆ init-parameter | init-parameter init-parameters
init-parameter βΆ [ππ₯‘] [πΌ] variable type
body βΆ block | external-link-name
access-level βΆ π | π | π
Initializers
Initializers are responsible to prepare an instance for use and when you create a new instance of the type.
In an initializer all instance variables must be initialized. Remember that variables of an optional type are automatically initialized to no value, which is also true for instance variables.
Let us define an initializer:
π π³ π
ππ number π‘
ππ expiration_date π‘
ππ security_code π‘
π a_number π‘ an_expiration_date π‘ a_security_code π‘ π
a_number β‘οΈπnumber
an_expiration_date β‘οΈπexpiration_date
a_security_code β‘οΈπsecurity_code
π
π
Now that was some tedious work, assigning all that instance variables. Because it is common to initialize instance variables from parameters, Emojicode provides a shortcut: πΌ.
πΌ is placed in front of the variable name of an parameters. Its value is then copied into the instance variable with the same name:
π πΌ number π‘ πΌ expiration_date π‘ πΌ security_code π‘ ππ
This is much better. Let us add an initializers to π©βπΌ as well.
π π©βπΌ π
ππ firstname π‘
ππ lastname π‘
ππ creditcard π³
π πΌ firstname π‘ πΌ lastname π‘ πΌ creditcard π³ ππ
π
Before implementing an initializer for π©βπ we must review one additional rule: If youβre writing an initializer for class that has a superclass you must call an initializer of the superclass. β€΄οΈ is used for that:
π π©βπ π©βπΌ π
ππ days_in_space π’
π πΌ days_in_space π’ firstname π‘ lastname π‘ creditcard π³ π
‴οΈπ firstname lastname creditcardβοΈ
π
π
Let us take a closer look at β€΄οΈ : The first thing it expects is the name of the initializer of the superclass you wish to call.
Instantiation
We have defined a value type and two classes, defined how to inititalize them, but we have yet to actually instantiate (get an instance) of them. Instatiation is performed with π.
Its syntax is:
instantiation βΆ π type-expr [initializer-name] [arguments] mood
Let us instantiate a credit card information π³:
ππ³ π€48829284848291π€ π€12/22π€ π€513π€βοΈ β‘οΈ credit_card
Directly after π comes π³, the name of the type we want to instantiate.
The following expressions are arguments to the initializer. βοΈ denotes the end of the arguments.
Having instantiated a credit card, we can also instantiate a customer:
ππ©βπΌ π€Mickeyπ€ π€Mouseπ€ credit_cardβοΈ β‘οΈ customer_mouse
ππ©βπ 3216 π€Jean-Lucπ€ π€Picardπ€ credit_cardβοΈ β‘οΈ astronaut_picard
Named Initializer
For completeness, letβs add an initializer with a name:
π π©βπΌ π
ππ firstname π‘
ππ lastname π‘
ππ creditcard π³
π πΌ firstname π‘ πΌ lastname π‘ πΌ creditcard π³ ππ
π βΆοΈπ§ββοΈ πΌ firstname π‘ πΌ creditcard π³ π
π€Mermaidπ€ β‘οΈ πlastname
π
π
In the above example, you can see an initializer named π§ββοΈ. In contrast to the other initializer, it does not take the lastname. Instead it initializes lastname
to the string Mermaid
.
We can use the π§ββοΈ initializer like this:
ππ©βπΌβΆοΈπ§ββοΈ π€Arielπ€ credit_cardβοΈ β‘οΈ ariel
Methods
Methods are functionality bound to a specific type: a class or value type.
The syntax to define a method is:
method βΆ identification [generic-parameters] [parameters] [return-type] [error-type] body
identification βΆ mood emoji-id | binary-operator
mood βΆ βοΈ | β | β‘οΈ
parameters βΆ parameter | parameter parameters
parameter βΆ [ππ₯‘] variable type
return-type βΆ β‘οΈ type
Let us define a method for π©βπΌ to print an invoice:
βοΈ πΈ total π― π
π π€Invoiceπ€βοΈ
π π€To π§²firstname𧲠π§²lastnameπ§²π€ βοΈ
π π€Total: π§²π‘total 2βοΈπ§²π€βοΈ
π π€Your credit card will be charged. π€βοΈ
π
Returning Values
Methods can, of course, also return a value. Unless you declare a return type, the method is assumed to not return a value.
Let us add a method to π³ that returns a value:
βοΈ π β‘οΈ π‘ π
β©οΈ number
π
This method simply returns the credit card number. It uses the return statement β©οΈ to return the value from the method.
return βΆ β©οΈ expression | β©οΈβ©οΈ
Returning from Methods without Return Value
You can also return from a method that does not have a return type at any point using β©οΈβ©οΈ
.
For example, this method will never print Cheap prices!
because it immediately returns:
βοΈ π π
β©οΈβ©οΈ
π π€Cheap prices!π€βοΈ
π
Method Moods
In Emojicode every method has a mood. The methods we have previously defined, are of imperative mood as we used βοΈ. The other mood is interrogative. Interrogative methods are defined with β instead.
The mood is like part of the name of the method. You can have an interrogative and imperative method with the same basic name.
Let us define an interrogative method for the π©βπ class:
β π β‘οΈ π π
β©οΈ days_in_space βΆοΈ 0
π
This method returns true if the astronaut ever boarded a rocket. We can define an imperative method with the same name that allows us to change the number of days the astronaut spent in space:
βοΈ π days π’ π
days β‘οΈ πdays_in_space
π
Calling Methods
We have defined two methods, but we have yet to fully understand how to call a method.
Weβll have a look at some examples first:
πΈ astronaut_picard 109.12βοΈ
πΈ customer_mouse 59.00βοΈ
π astronaut_picardβ π Was he ever in space?
π astronaut_picard 6390βοΈ π Change the number of days to 6390
As you can see above, the syntax to call a method is special:
method-call βΆ emoji-id callee [generic-arguments] [arguments] mood
method-call βΆ emoji-id mood
callee βΆ expression
arguments βΆ expression [arguments]
If an emoji occurs that is not reserved for a built-in statement or expression (e.g. β©οΈ or π¨), it is considered a method call. The compiler then expects an expression, evaluating to a value that has method with the provided name. Then arguments are expected until either βοΈ or β occurs.
In a method or initializer call all arguments are evaluated from left to right, but after the callee in the case of a method call.
A method call expression evaluates to the value the method returned. If the method does not declare a return type, the call expression returns a value of type no return, which is neither compatible to any type nor does it offer any functionality.
Note that methods like initializers support overloading as we will see in the next chapter.
This Context
You will naturally need to access the instance on which the method was called. This is what π is for.π returns the value on which the method or initializer was called.
For example, we could add a method to the π©βπ class that bills an astronaut if has traveled to space:
βοΈ πΈ π
βͺοΈ π πβ π
πΈπ 100βοΈ
π
π
Note that in an initializer, you canβt use π before the object is fully initialized, that is before all instance variables were set and the superinitializer was called. If this was allowed, you could call methods on the instance which might access instance variable that had not been initialized yet.
this βΆ π
Note that when you want to call a method on π that does not take any arguments you can omit π:
πβ
Assignable Methods
You can also define instance methods to which values can be assigned.
Consider that to get a value from a list the π½
method is used as in this example:
π½ a_list 1βοΈ
It would be very intuitive, if we could β to assign a value β write this:
π€Cocoπ€ β‘οΈ π½ a_list 1βοΈ
And, indeed, we can. This works, because Emojicode allows us to define methods that can be assigned to. Although this might sound complicated to you, in fact, it isnβt. Take a look at this example, in which an assignee method is defined.
βοΈ π½ index π’ β‘οΈ π¬Element π
π ...
π
β‘οΈ π½ assigned_value Element index π’ π
π ...
π
In this example two methods are defined. The first one is rather obviously just the getter we used before. The second method, on the other hand, uses the assignment operator (β‘οΈ) instead of βοΈ. This indicates to the compiler that this method should be called, when an assignment to a method of the name π½
occurs.
The first argument of the assignee method is always the value that is being assigned. It must not be provided on the right hand side of the assignment, as we have seen in the assignment example before. The method itself than is free to do whatever it needs to do with one limitation: It may not return a value.
method-assignment βΆ expression β‘οΈ method-call
Mutability of Value Types
We have seen examples of methods that modify class objects, but we have not seen any examples of changing the instance variables of a value type. Thereβs a good reason why: Value types instance cannot be arbitrarily modified.
Methods of value types that wish to mutate instance variables must be attributed with π. Let us define a method for the π³ type that allows us to update the cardβs security code:
πβοΈπ code π‘ π
code β‘οΈπsecurity_code
π
If we hadnβt used the π attribute, the compiler would emit an error when compiling this. Of course, we can only call a method marked with π on π if the method we are in is attributed with π too. Thus the following would not work:
π π³ π
ππ number π‘
ππ expiration_date π‘
ππ security_code π‘
πβοΈπ code π‘ π
code β‘οΈπsecurity_code
π
βοΈπ¦ code π‘ π
ππ π€000π€βοΈ π We cannot call a mutating method from a non-mutating one.
π
π
We have defined a mutating method. We should call it as well, which brings us to another important aspect of value type mutability:
Only value types in mutable variables are mutable.
Letβs see an example:
ππ³ π€48829284848291π€ π€12/22π€ π€513π€βοΈ β‘οΈ ππcredit_card
πcredit_card π€126π€βοΈ
This is perfectly fine, while the below example will not compile as credit_card
is not mutable:
ππ³ π€48829284848291π€ π€12/22π€ π€513π€βοΈ β‘οΈ credit_card
πcredit_card π€789π€βοΈ
Since instance variables are always mutable, you can always call mutating methods on the values of instance variables.
The return of a method is always immutable. The following code does not work:
π πΌ π
πβοΈπ³ β‘οΈ π³ π
β©οΈ ππ³ π€48829284848291π€ π€12/22π€ π€513π€βοΈ
π
π
π π
ππ³ππΌβοΈβοΈ
π
Type Methods
Itβs possible to define type methods which are called on the type rather than on an instance of the type. Still, type methods are also inherited by subclasses.
Type methods are defined like normal methods but with the π attribute. As for example:
π π π
π Return available pizza dishes. π
πβοΈ π β‘οΈ π¨ππ‘π π
β©οΈ πΏ π€Margheritaπ€ π€Tonnoπ€ π€Quattro Formaggiπ€ π
π
π
π π³ π
π Returns some credit card providers. π
πβοΈ π’ β‘οΈ π¨ππ‘π π
β©οΈ πΏ π€Visaπ€ π€MasterCardπ€ π€Discoverπ€ π
π
π
We can call our type methods like this:
πππβοΈ
π’ππ³βοΈ
This calls the type method π on the class π, which we just defined above. In class type methods, π represents the type value on which the method was called. To learn more about what this means please see Types As Values.
Access Levels
Access Levels describe from which context a method or initializer can be called. There are three access levels:
- π: The method or initializer can be accessed from everywhere.
- π: The method or initializer may only be accessed within the type and package it was defined.
- π: The method or initializer may only be accessed within the type it was defined or within a class that inherits from that class that defined this method.
The following example cannot be compiled, as π is a private method and can therefore not be called from π.
π π π
π ππ
π βοΈ π π
π π€Iβm a fish.π€βοΈ
π
π
π π
ππβοΈ β‘οΈ fish
π fishβ
π
If you do not specify an access level the method will default to π, unless it overrides another method.
Deprecation
From time to time methods or initializers need to be deprecated. Emojicode allows you to mark a method or initializer as deprecated with the β οΈ attribute.
The compiler will emit a warning wherever a deprecated method or initializer is used.
Inline
You can attribute a method or an initializer with π₯―, which indicates to the compiler that it could be advantegous to inline the method. This attribute furthermore causes the compiler to include the function body in the interface file if one is generated.