4. Create and Test API.jl
In the API, you define the functions that one can use to write a software program using your module Accounts
. The API uses only the elements from the inner shells, so Domain and the core consisting of Julia and the modules or packages. You will define a function create
that one can use to create:
- an address.
- a person.
Julia's functions are dispatchable. Julia searches for the right method based on the number of arguments and their data-types. Functions that you can invoke with different parameters are called methods.
Contents
API.jl
module API #1
import ..Accounts: Domain #2
using .Domain #3
export create #4
"""
create(address_type::AddressType, address::String)::Address
create(name::String, addresses::Array{Address,1})::Person
create(name::String)::Person
Create an Address or a Person object.
# Example
```julia
julia> using Accounts
julia> address_email = create(EMAIL, "donald@duckcity.com");
julia> donald = create("Donald Duck", [address_email]);
julia> donald.id
"14456927583164318539"
julia> donald.created
2020-09-28T10:56:29.997
julia> donald.name
"Donald Duck"
julia> donald.addresses
1-element Array{Accounts.Domain.Address,1}:
Accounts.Domain.Address("7763679977726623090", Dates.DateTime("2020-09-28T10:56:29.461"), EMAIL, "donald@duckcity.com")
```
"""
function create end #5
create(address_type::AddressType, address::String)::Address = Address(address_type, address) #6
create(name::String, addresses::Array{Address,1})::Person = Person(name, addresses) #7
create(name::String)::Person = Person(name) #8
end
#1 The module name is API
.
#2 The API
sub-module uses only the functions and types that are defined in the sub-module Domain, Julia, and any loaded packages. ..Accounts
refers to the main-module of API
. import ..Accounts: Domain
give us a reference to the sub-module Domain
, we can load in the next statement.
#3 The code loads the sub-module Domain (using .Domain
). The dot tells Julia that Domain is a sub-module. Julia loads all exported elements of Domain (Person, Address, AddressType, EMAIL, and WORK) into the scope of users code because we specify using
. If we would use import
instead of using
, we also have to mention the module name (e.g. Domain.EMAIL
).
Now, we can call them without mentioning the name of the sub-module. Julia warns if conflicts arise. In that case you use the naming as in import
.
#4 We export the create
methods. It means that other modules and programs can use them directly unless it conflicts with similar names.
#5 Here, we document the different use cases (methods) of the function create
. One calls it multiple dispatch
and it can be compared to overloading
in Object-Oriented programming languages.
The methods must be indented and their signatures must be different. A signature is determined by the number of arguments and their data-types.
When users type a question mark followed by the function-name (? create
) then Julia displays the text and the example. Run the example code by pasting it into the REPL, including the julia>
-prompts.
#6 The method create
when we want to create an Address
. (address_type::AddressType, address::String)
is its signature. Address(address_type, address)
is the constructor of the data-type Address that we have defined in Domain.jl.
#7 The method create
when we want to create a Person
with one or more addresses. addresses::Array{Address,1}
specifies that the argument must be a one-dimensional array with Address
objects.
#8 This line creates a Person with an empty Address array. Although the object Person is not mutable, we can still add elements to the array. For example push!(donald.addresses, <Address object>
).
Accounts.jl
The main module Accounts
.
module Accounts
#export EMAIL, WORK # Domain
#export create # API
include("Domain.jl"); using .Domain # load the module Domain
include("API.jl"); using .API # load the module API
end
test_api.jl
using Pkg
Pkg.activate(".") # make the current folder the working environment
import Accounts: Domain, API # import the sub-modules Domain and API
using .Domain, .API # load the sub-modules Domain and API
donald_email = create(EMAIL, "donald@duckcity.com") # create an address object
donald_work = create(WORK, # create another addresses object
"""
Donalds Hardware Store
attn. Donald Duck
1190 Seven Seas Dr
FL 32830 Lake Buena Vista
USA
"""
)
addresses = [donald_email, donald_work] # create an array with addresses
donald = create("Donald Duck", addresses) # create a person
println(donald) # print the data of the person to the console.
runtests.jl
using Accounts # load the module Accounts
using Test # Test provides the macros @testset and @test
import Accounts: Domain, API
using .Domain, .API
@testset "Domain.jl" begin
donald_email = Address(EMAIL, "donald@duckcity.com")
donald = Person("Donald duck", [donald_email])
email_addresses = filter(x -> x.address_type == EMAIL, donald.addresses) #1
@test email_addresses[1].address == "donald@duckcity.com"
end
@testset "API.jl" begin
donald_email = Address(EMAIL, "donald@duckcity.com")
donald = Person("Donald Duck", [donald_email])
email_addresses = filter(x -> x.address_type == EMAIL, donald.addresses) #1
@test email_addresses[1].address == "donald@duckcity.com"
end
#1 The function filter
operates on a collection (Array
) of Address
's and returns an new filtered collection. The first argument x -> x.address_type == EMAIL
represents an anonymous function. x
is a consecutive value from the Array
. If the test x.address_type == EMAIL
yields a true
the function adds the Address
to the new collection.
Similar high order functions are map
, reduce
, and zip
.
Exercise 4.1: Adding the Sub-module API.
Prerequisites
- Previous activities.
In this exercise you create the sub-module API. You can apply everything you've learned so far.
- Create the file
API.jl
and add the code of section API.jl to the file. Remove the comments. - Copy the code of section runtests.jl to the file runtests.jl.
- Modify
Accounts.jl
according to section Accounts.jl. - Create the file
test_api.jl
and paste the code of section test_api.jl into it. Test the code. - Go to the package manager, activate the Accounts module (
]activate .
) and run the tests (test Accounts
). You should see:
Test Summary: | Pass Total
Domain.jl | 1 1
Test Summary: | Pass Total
API.jl | 1 1
Testing Accounts tests passed
- Return to the julia-prompt and type:
? create
. The help text of the functioncreate
is displayed.
help?> create
search: create searchsortedlast
create(address_type::AddressType, address::String)::Address
create(name::String, addresses::Array{Address,1})::Person
create(name::String)::Person
Create an Address or a Person object.
Example
≡≡≡≡≡≡≡≡≡
julia> address_email = create(EMAIL, "donald@duckcity.com")
julia> donald = create("Donald Duck", [address_email])
- Push the changes to your GitHub repository. Check the changes on GitHub.
- Go to the Accounts folder
cd ~/.julia/dev/Accounts
and and typegit log --oneline
. You sloud see:
~/.julia/dev/Accounts$ git log --oneline
3b1af29 (HEAD -> master) Add API.jl sub-module
c76901f (origin/master) Add Domain.jl sub-module
0cf05da Files generated by PkgTemplates
82338c3 Initial commit
Summary
Julia supports multiple dispatching, one function that has different signatures.
You can document functions by describing it between triple quotes directly above itself. Use four spaces to indent methods.
It is also customary to give cases of the use of the methods between triple backslashes. If you write it in the form of Julia REPL prompts julia>
, then the user can copy the code entirety and paste it into the Julia REPL.
The function filter
allows you to filter elements of an array. Because it has an (anonymous) function as an argument, one calls it a higher-order function
. Other high order functions are map
and reduce
.