0% found this document useful (0 votes)
28 views93 pages

Logbook Example for Software Tasks

coursework

Uploaded by

adilhussain606
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views93 pages

Logbook Example for Software Tasks

coursework

Uploaded by

adilhussain606
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

CMP5361 Computer Mathematics and Declarative Programming UG2

Software Creation Tasks

Computer Mathematics and Declarative


Programming UG2 Software Creation
Tasks

Simon Roadknight 21126080

Simon Roadknight 1
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Table of contents

TABLE OF CONTENTS 2

T1 – GHERKIN SPECIFICATIONS 4

What are Gherkin Specifications? 4

Feature behaviours identified 4

Errors identified 5

Some Assumptions about what a valid file path and song file are 5

Feature: Playing a single song file 6

Why the program will not play any songs if errors are found when there are multiple song files 10

Feature: Playing multiple song files 10

T1 –DATA MODELLING 19

Kinds of data identified 19

Data model: Argument 20

Data model: Arguments 21

Data model: File Path 22

Data model: Song file Patterns 23

Data model: Song file 25

Data model: Frequency 26

Data model: Length of Time 28

Data model: Beep 29

Data model: Delay 29

Data model: Song Instruction 30

Data model: Song 30

Data model: Song play time 31

Data model: Output Message 31

Data model: Error message 32

Simon Roadknight 2
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Error / Song error 33

Data model: Exit code 34

T1 - BEHAVIOUR STEPS AND FUNCTION SPECIFICATION 35

Behaviour step: Playing a single song, and multiple songs holistic view 35

Behaviour step: Argument to File path 39

Behaviour step: File path to song file 40

Behaviour step: Song file to song 43

Behaviour step: Song results handler 45

Behaviour step: Getting the play time of a song 46

Behaviour step: Playing a song 48

T1 - REFLECTION 50

T2 - IMPLEMENTATION 51

Implementation of data models and behaviour steps for the data 51

Implementation – Tying it all together 68

T2 - REFLECTION 71

T3 – TESTING 72

Manual testing 72

Automated testing 85

T1 - REFLECTION 88

REFERENCE LIST 92

Simon Roadknight 3
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

T1 – Gherkin Specifications
What are Gherkin Specifications?
Gherkin specification is often used in Behaviour-Driven Development (BDD) which puts focus on
feature behaviours. Gherkin allows us to focus on our program’s behaviour features, these are
features of our program that capture how the user will interacts and uses it (Knight, 2017). We can
take those features and write well-defined scenarios using inputs, actions, and outcomes. This is
built upon Hoare logic, or more specifically the Hoare triple where a precondition and postcondition
are asserted, and a command is specified, when the precondition is met, and the command is
executed, the postcondition is established. Writing Gherkin specifications allow us to understand
our specification better, capture the behaviour features of our program, and better plan what data
and behaviours our program will have when we look at our data modelling and behaviour steps. We
are also able to use our Gherkin specification for testing, specifically user focused testing.

Feature behaviours identified


The following feature behaviours have been identified for implementation based on my analysis of
the requirements.

Table 1 Feature behaviours identified

Feature Behaviour Description


Play a single song file This will allow the user to play a single song file specified via an input
argument.
Play multiple song files This will allow the user to play multiple song files specified via the input
arguments.

From the song player specification given, I have identified two behaviour features, to play a single
song, and multiple songs via input arguments to the program. The user is a system administrator and
want to be able to use the program either manually or autonomously using scripts, this is the reason
the program will not be interactive.

Simon Roadknight 4
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Errors identified
The following errors have been identified for the Gherkin specifications outlined below.
Table 2 Errors identified

ID Error name Message Exit


code
E1 InvalidArgAmount ERROR: No input arguments specified. Try specifying a 1
song file (e.g.,
C:\users\anexampleuser\songfiles\song1.txt).
E2 InvalidFilePath ERROR: Invalid file path (Please check the file path was 160
entered correctly and try again).
E3 IOErrorOpeningFile ERROR: IO Error when trying to open the file. 4

E4 InvalidFilePermission ERROR: Invalid file permissions (Please check the file 5


permissions for the file and try again).
E5 InvalidSong ERROR: Invalid song instruction found in file (e.g., invalid 13
:beep or :delay instruction).
E6 EmptySongFile ERROR: File contains no song instructions (Please check 13
the correct file path was specified).
E7 FileNotFound ERROR: File not found (Please check the file path was 2
entered correctly).

Some Assumptions about what a valid file path and song file are

There are two assumptions that I will be making in the following features and gherkin scenarios.

1. Valid song file: A valid song file going forward is a file that contains the correct data to be
parsed and played as a song using our song player program.
2. Valid file path: A valid file path going forward is any file path that both is syntactically correct
file path for the OS specified AND where a file IS found, even if the contents of the file is not
a valid song file.

Simon Roadknight 5
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Feature: Playing a single song file

Feature narrative

Feature: Play a single song file


As a system administrator
I want to play a single song file via the command line
So that I can write scripts which allow me to provide custom alerts when particular events happen
Figure 1 Feature narrative for playing a single song

We have identified the first feature, the kind of user, what they want, and why they want it.

Scenario: Execute the song player program using a valid file path argument to a single valid
song file containing a valid song

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\song1.txt
Executing the Songplayer with a valid file path to a valid song file.

Scenario specification
Scenario Outline: Execute the song player program using a valid path argument to a single
valid song file

Given I have specified a sing valid file path <path>


And <path> is the location of a single valid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stdout “Now playing
<n> song(s) with a total play time of <total>s. Where <total> is the calculated play time of all the
songs.
And the program should output its second line of the output message to the stdout “Now playing:
<path>”
And the program should play the song through the default audio device
And the program should output its third line of the output message to the stdout "Finished
playing all songs."
And the program should end with an exit code of 0 indicating success

path Valid Operating System


C:\users\anexampleuser\songfiles\song1.txt Windows 7 – 10
/home/student/songfiles/song1.txt Linux (Modern Linux)

Figure 2 Executing the song player with a single valid song file

This is the only happy path for playing a single argument, where the file path is valid, and the file
contains a valid song. The play time for the song is calculated and then printed out to the standard
output. There are two scenarios, one with a Windows file path and one with a Linux file path (Each

Simon Roadknight 6
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

scenario and scenario out where there are file paths will also follow this pattern). It is stated in the
specification the System Administrator may be on Windows and/or Linux.

Scenario: Execute the song player program specifying no file path

Example usage
Songplayer.exe
Executing the Songplayer with no input arguments.

Scenario specification
Scenario: Execute the song player program without specifying a file path

Given I have specified no file path


When I execute the songplayer program
Then the program should output its first line of the output message to the stderr for error E1
And the program should end with the exit code for error E1

Figure 3 No file path specified

This is the scenario for when the song player is executed with no arguments.

Simon Roadknight 7
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using a valid file path argument to a single invalid
song file

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\invalidsong1.txt
Executing the Songplayer with a valid file path to an invalid song.

Scenario specification
Scenario Outline: Execute the song player program using a valid file path argument to a
single invalid song file

Given I have specified a single valid file path <path>


And <path> is the location of a single invalid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 1 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path> -
<errorID>” Where <errorID> is the error message for <path>’s <errorID>
And the program should end with the exit code for <errorID>

path errorID Valid Operating


System
C:\users\anexampleuser\songfiles\invalidsongfile1.txt E5 Windows 7 – 10
/home/student/songfiles/invalidsongfile1.txt E5 Linux (Modern
Linux)
Figure 4 Invalid file path and song file

This is the scenario outline for when a valid file path is entered as an input argument, but the file is
an invalid song file.

Simon Roadknight 8
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using an invalid file path argument

Example usage
Songplayer.exe C://\\
The Songplayer program being run with an invalid file path.

Scenario specification
Scenario Outline: Execute the song player program using an invalid file path argument

Given I have specified a sing invalid file path <path>


When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 1 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path> -
<errorID>” Where <errorID> is the error message for <path>’s <errorID>
And the program should end with the exit code for <errorID>

path errorID Valid Operating System


C://\\ E2 Windows 7 – 10
\home//\\ E2 Linux (Modern Linux)
Figure 5 Invalid file path

This is the scenario outline for when an invalid file path is provided.

Simon Roadknight 9
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Why the program will not play any songs if errors are found when there are multiple
song files

I have made the decision that if a single input argument is an invalid file path, invalid song file or
could not be opened for IO and permission reasons, then no songs will play. I have made this
decision as this is a noninteractive program, and the user may be expecting songs to play in a very
specific order to be alerted correctly of the system event that is taking place. If we were to play
some songs and not others, this could misrepresent the event taking place, and render the custom
alerts unusable.

Feature: Playing multiple song files

Feature narrative

Feature: Play multiple song files


As a system administrator
I want to play multiple songs file via the command line
So that I can write scripts which allow me to provide custom alerts when particular events happen
Figure 6 Feature description for playing multiple songs

We have identified the second feature, the kind of user, what they want, and why they want it.

Simon Roadknight 10
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using two valid file path arguments to two valid
song files

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\song1.txt
C:\users\anexampleuser\songfiles\song2.txt
Executing the Songplayer program with two valid file paths containing two valid song files.

Scenario specification
Scenario Outline: Execute the song player program using two valid file path arguments to
two valid song files

Given I have specified two valid file paths <path1> <path2>


And <path1> <path2> are the locations of a single valid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stdout “Now playing
<n> song(s) with a total play time of <total>s. Where <total> is the calculated play time of all the
songs. Where <total> is the calculated play time of the songs in <path1> and <path2>.
Then the program should output its second line of the output message to the stdout “Now
playing: <path1>”
And the program should first play the song at <path1> through the default audio device
And when the song at <path1> has finished playing the program should output its third line of the
output message to the stdout “Now playing: <path2>” after a 3 second delay
And the program should play the song at <path2> through the default audio
And the program should print out its fourth line of the output message to the stdout “Finished
playing all songs.”
And the program should end with an exit code of 0 indicating success

Path1 Path2 Valid


Operating
System
C:\users\anexampleuser\songfiles\song1.txt C:\users\anexampleuser\songfiles\song2.txt Windows 7
– 10
/home/student/songfiles/song1.txt /home/student/songfiles/song2.txt Linux
(Modern
Linux)
Figure 7 Playing two valid songs

This is the scenario outline for when all input arguments provided are valid file paths that contain
valid song files.

Simon Roadknight 11
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using two valid file path arguments to two invalid
song files

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\invalidsong1.txt
C:\users\anexampleuser\songfiles\invalidsong2.txt

Executing the Songplayer program with two invalid song files as input arguments.

Scenario specification
Scenario Outline: Execute the song player program using two valid file path arguments to
two invalid song files

Given I have specified two valid file paths <path1> <path2>


And <path1> <path2> are the locations of a single invalid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 2 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path1> -
<errorID>” Where <errorID> is the error message for <path1>’s <errorID>
And the program should output its second line of the output message to the stderr “<path2> -
<errorID>” Where <errorID> is the error message for <path2>’s <errorID>
And the program should end with the exit code for <path1>’s <errorID>
path1 error path2 error Valid
ID ID Operating
System
C:\users\anexampleuser\songfiles\inv E5 C:\users\anexampleuser\songfiles E6 Windows 7
alidsong1.txt \invalidsong2.txt – 10
/home/student/songfiles/invalidsong E5 /home/student/songfiles/invalidso E6 Linux
1.txt ng2.txt (Modern
Linux)

Figure 8 Scenario for specifying two invalid songs

This is the scenario outline for when two valid paths are specified that contain two invalid song files.
This shows how we can deal with multiple errors.

Simon Roadknight 12
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using one valid file path argument to one valid
song file and one valid file path argument to one invalid song file

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\song1.txt C:\users\anexampleuser\songfiles\
invalidsong2.txt

Executing the Songplayer program with one input argument that represents a valid song file and one
that does not.

Scenario specification
Scenario Outline: Execute the song player program using one valid file path argument to
one valid song file and one valid file path argument to one invalid song file

Given I have specified two valid file paths <path1> <path2>


And <path1> is the location of a single valid song file
And <path2> is the location of a single invalid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 1 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path2> -
<errorID>” Where <errorID> is the error message for <path2>’s <errorID>
And the program should end with the exit code for <errorID>

path1 path2 errorID Valid Operating


System

C:\users\anexampleuser\songfiles\ C:\users\anexampleuser\so E7 Windows 7 – 10


song1.txt ngfiles\invalidsong2.txt

/home/student/songfiles/song1.txt /home/student/songfiles/in E7 Linux (Modern Linux)


validsong2.txt

Figure 9 Scenario for one valid song and one invalid song

This is the scenario outline for when there is one valid song file and one invalid song file. This shows
how we can deal with a mixed input of valid and invalid arguments.

Simon Roadknight 13
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using two invalid file path arguments

Example usage
Songplayer.exe C://\\ C://\\2
Executing the Songplayer with two invalid file paths.

Scenario specification
Scenario Outline: Execute the song player program using two invalid file path arguments

Given I have specified two invalid file paths <path1> <path2>


When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 2 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path1> -
<errorID>” Where <errorID> is the error message for <path1>’s <errorID>
And the program should output its second line of the output message to the stderr “<path2> -
<errorID>” Where <errorID> is the error message for <path2>’s <errorID>
And the program should end with the exit code for <errorID>

path1 path2 errorID Valid Operating


System
C://\\ C://\\2 E2 Windows 7 – 10
\home//\\ \home//\\2 E2 Linux (Modern Linux)
Figure 10 Scenario for two invalid file paths

This is the scenario outline for when there are two invalid file paths.

Simon Roadknight 14
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using one valid file path argument to one valid
song file and one invalid file path argument

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\song1.txt C:\\//
Executing the Songplayer program with one valid song file and one invalid file path.

Scenario specification
Scenario Outline: Execute the song player program using one valid path argument to one
valid song file and one invalid file path argument

Given I have specified one valid file path <path1>


And one invalid file path <path2>
And <path1> is the location of a single valid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 1 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path2> -
<errorID>” Where <errorID> is the error message for <path2>’s <errorID>
And the program should end with the exit code for <errorID>

Path1 Path2 errorID Valid Operating


System
C:\users\anexampleuser\songfiles\song1.t C:\\// E2 Windows 7 – 10
xt
/home/student/songfiles/song1.txt /home//\\ E2 Linux (Modern Linux)

Figure 11 Scenario for one valid song and one invalid file path

This is the scenario outline for when there is one valid song file and one invalid file path argument.

Simon Roadknight 15
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using three valid file path arguments to three
valid song files

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\song1.txt
C:\users\anexampleuser\songfiles\song2.txt
C:\users\anexampleuser\songfiles\song3.txt

Executing the Songplayer program with three input arguments that are all valid file paths that
contain valid song files.

Scenario specification
Scenario Outline: Execute the song player program using three valid file path arguments to
three valid song files

Given I have specified three valid file paths <path1> <path2><path3>


And <path1> <path2><path3> are the locations of a single valid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stdout “Now playing
<n> song(s) with a total play time of <total>s. Where <total> is the calculated play time of all the
songs. Where <total> is the calculated play time of the songs in <path1>, <path2> and <path3>.
Then the program should output its second line of the output message to the stdout “Now
playing: <path1>”
And the program should first play the song at <path1> through the default audio device
And when the song at <path1> has finished playing the program should output its third line of the
output message to the stdout “Now playing: <path2>” after a 3 second delay
And the program should play the song at <path2> through the default audio
And when the song at <path2> has finished playing the program should output its fourth line of
the output message to the stdout “Now playing: <path3>” after a 3 second delay
And the program should play the song at <path3> through the default audio
And the program should print out its fifth line of the output message to the stdout “Finished
playing all songs.”
Path1 Path2 Path3 Valid Operating
System
C:\users\anexampleuser\s C:\users\anexampleuser\s C:\users\anexample Windows 7 – 10
ongfiles\song1.txt ongfiles\song2.txt user\songfiles\song3
.txt
/home/student/songfiles/s /home/student/songfiles/ /home/student/songfil Linux (Modern
ong1.txt song2.txt es/song3.txt Linux)
Figure 12 Scenario for playing three valid songs

This is the scenario outline for when all input arguments provided are valid file paths that contain
valid song files. This shows how we would deal with ‘N’ number of valid songs, where ‘N’ is the
number of valid input arguments.

Simon Roadknight 16
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using a mixture of valid and invalid file path
arguments to a mixture of valid and invalid song files

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\song1.txt
C:\users\anexampleuser\songfiles\invalidsong1.txt
C:\\//

Executing the Songplayer program with three input arguments where there is a mix of valid and
invalid arguments.

Scenario specification
Scenario Outline: Execute the song player program using a mixture of valid and invalid file
path arguments to a mixture of valid and invalid song files

Given I have specified two valid file paths <path1> <path2>


And one invalid file path <path3>
And <path1> is the location of a single valid song file
And <path2> is the location of a single invalid song file
When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 2 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path2> -
<errorID>” Where <errorID> is the error message for <path2>’s <errorID>
And the program should output its second line of the output message to the stderr “<path3> -
<errorID>” Where <errorID> is the error message for <path3>’s <errorID>
And the program should end with the exit code for <path2>’s <errorID>

path1 path2 errorID path3 errorID Valid Operating


System
C:\users\anexampleu C:\users\anexampleus E5 C:\\// E2 Windows 7 – 10
ser\songfiles\song1.t er\songfiles\invalidson
xt g1.txt

/home/student/songf /home/student/songfil E5 /home//\\ E2 Linux (Modern


iles/song1.txt es/invalidsong1.txt Linux)
Figure 13 Scenario with a mixture of valid and invalid input

This is the scenario outline for when there are a mix of valid and invalid argument inputs. This
demonstrates we can handle multiple invalid songs, even when they are mixed in with valid songs.

Simon Roadknight 17
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Scenario: Execute the song player program using three valid file paths, one to a file where
there are invalid file permissions, one to an empty file and one to a file which will cause an
I/O error

Example usage
Songplayer.exe C:\users\anexampleuser\songfiles\invalidsong3.txt
C:\users\anexampleuser\songfiles\invalidsong4.txt
C:\users\anexampleuser\songfiles\invalidsong5.txt

Executing the Songplayer program with three input arguments that are invalid.

Scenario specification
Scenario Outline: Execute the song player program using three invalid file paths, one to a
file where we have invalid file permissions, one to an empty file and one to a file which will
cause an I/O error

Given I have specified three invalid file paths <path1> <path2><path3>


And <path1> is the location of a single file I don’t have permission to open
And <path2> is the location of a single empty file
And <path3> is the location of a single file that causes an I/O Error
When I execute the songplayer program
Then the program should output its first line of the output message to the stderr “Error in playing
song(s). 3 song(s) with an error found:
And the program should output its second line of the output message to the stderr “<path1> -
<errorID>” Where <errorID> is the error message for <path1>’s <errorID>
And the program should output its second line of the output message to the stderr “<path2> -
<errorID>” Where <errorID> is the error message for <path2>’s <errorID>
And the program should output its second line of the output message to the stderr “<path3> -
<errorID>” Where <errorID> is the error message for <path3>’s <errorID>
And the program should end with the exit code for <path1>’s <errorID>

path1 errorID path2 errorID path3 errorID Valid


Operating
System
C:\users\an E4 C:\users\an E6 C:\users\an E3 Windows 7 –
exampleus exampleus exampleus 10
er\songfiles er\songfiles er\songfiles
\invalidson \invalidson \invalidson
g3.txt g4.txt g5.txt
/home/stud E4 /home/stud E6 /home/stud E3 Linux
ent/songfiles ent/songfiles ent/songfiles (Modern
/invalidsong /invalidsong /invalidsong Linux)
1.txt 2.txt 3.txt
Figure 14 Scenario that covers the remaining errors

This scenario covers the errors identified not covered by other scenarios.

Simon Roadknight 18
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

T1 –Data Modelling

Kinds of data identified


• Arguments
o Argument
• File path
• Song file
o Patterns
§ Song Instruction Patterns
• Beep pattern
• Delay pattern
§ Whitespace pattern
• Song
o Song instructions
§ Beep
• Frequency
• Length of time
§ Delay
• Length of time
• Output message
o Total song play time
§ Song play time
• Error
o Error values
§ Invalid amount of arguments
§ Invalid file path
§ Error when performing I/O on the file
§ Invalid file permissions
§ Invalid song instruction in file
§ Empty file found
o Error message
o Exit code
• Exit code

From the gherkin specification this is the data I have identified that we need to model, this will allow
us to read song files, parse songs, play songs, display to the output the total song play time and
finally, output any errors we found with the song files along the way.

Simon Roadknight 19
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Argument


What are we representing and why?
We are representing the input from the user, which can be anything types on the keyboard. Initial
inputs from the command line are treated as strings as the input can be anything that can be typed
via a keyboard. We can use the type alias of Argument, so that the input has more meaning when
being handled in the program.

How can it be specified mathematically?


This means that we can mathematically define Argument to be equal to the set of string.

𝑙𝑒𝑡 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡 = 𝑠𝑡𝑟𝑖𝑛𝑔

Implementing it in F#
In F# we can use type aliases, that allow us to define a new name for a type that already exists
(Wlaschin, 2012). This allows for useful naming in our code, reducing cognitive overload and serving
as living documentation.

We can define the Argument type alias in F# like this:

type Argument = string

Simon Roadknight 20
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Arguments


What are we representing and why?
We are representing the collection of inputs that come into the program which is a string array
(Contributors to the Microsoft .NET Documentation, 2021a). We have already made the type alias of
Argument for each string in the collection, we can also define the collection itself as Arguments as it
has more semantic meaning than string array (string []).

How can it be specified mathematically?


This can be mathematically defined as Arguments being equal to the set of sequence of Argument as
the set can be any sequence of Argument.

𝑙𝑒𝑡 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑠 = 𝑠𝑒𝑞 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡

Implementing it in F#
We can create our type alias for string [] by using our previously defined Argument type, we will
have Arguments which is an Argument [].

type Arguments = Argument []

Simon Roadknight 21
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: File Path


What are we representing and why?
We are representing valid Windows and Linux file paths, as from our specifications we know that the
systems administrator can be on Windows and/or Linux. We want to be able to validate that a file
path can be valid, before checking if it contains a song file, and giving more meaning to the string,
than the type string or Argument.

Use of regular expressions


I will be using regular expressions as a means of searching for patterns within the lines of text we
read in. To use regular expressions, we will define sequences of characters that that specify a search
a pattern (Contributors to the Microsoft .NET Documentation, 2022). I have chosen to use regular
expressions as the text we are processing follow simple patterns those regular expressions can
match; we can further use the power of regular expressions to extract the data we want from the
string we have matched.

How can it be specified mathematically?


Using regular expressions, we can define the constants for Windows and Linux file paths.

𝑊𝑖𝑛𝑑𝑜𝑤𝑠𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: string
𝑊𝑖𝑛𝑑𝑜𝑤𝑠𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ = "^[𝑎 − 𝑧𝐴 − 𝑍]:\\[\\\𝑆| ∗\𝑆]? .∗ $"

𝐿𝑖𝑛𝑢𝑥𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: string
𝐿𝑖𝑛𝑢𝑥𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ = "^(/[^/ ] ∗) +/? $"
FilePath can be defined as the set of all WindowsFilePath and LinuxFilePath defined above.
𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: ℙ string
𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ = {𝑥: |
𝑠𝑡𝑟𝑖𝑛𝑔 𝑥 = 𝑊𝑖𝑛𝑑𝑜𝑤𝑠𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ ∨ 𝑥 = 𝐿𝑖𝑛𝑢𝑥𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ}

Implementing it in F#
type FilePath = private FilePath of string

Here we have defined a type called FilePath that provides a single discriminated union case
‘FilePath’, which is a subset of sequence of string. We have used the ‘private’ access modifier to
prevent FilePath values being constructed from sequences of strings that do not meet our
constraints. The only way to create a FilePath is through the constructor that is in the same module
the type is defines, this will stand true for the rest of our constrained types.

Simon Roadknight 22
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Song file Patterns


What are we representing and why?
We need to be able to represent the patterns that can be seen in the valid song examples we have
been given. We can see two song instruction patterns which are beep and delay, as well as
whitespace or blank lines. To have a valid song file each line must be one of these patterns, to verify
that this is the case, I will specify regular expression that can be used to match each pattern
identified.

How can it be specified mathematically?


From our valid song examples the beep pattern always has the pattern of

: 𝑏𝑒𝑒𝑝 𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦 =< 𝑛𝑢𝑚1 > 𝑙𝑒𝑛𝑔𝑡ℎ =< 𝑛𝑢𝑚2 > 𝑚𝑠;

Using regular expression, we can define the beep pattern to be

𝑏𝑒𝑒𝑝𝑃𝑎𝑡𝑡𝑒𝑟𝑛: string
𝑏𝑒𝑒𝑝𝑃𝑎𝑡𝑡𝑒𝑟𝑛 = "^\𝑠 ∗: 𝑏𝑒𝑒𝑝\𝑠 + 𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦 = (\𝑑+)\𝑠 + 𝑙𝑒𝑛𝑔𝑡ℎ = (\𝑑+)𝑚𝑠;\𝑠 ∗ $"

Figure 15 beepPattern Regex Graph

Implementing it in F#
We can define the beepPattern as the string:
"^\s*:beep\s+frequency=(\d+)\s+length=(\d+)ms;?\s*$"

let beepPattern = "^\s*:beep\s+frequency=(\d+)\s+length=(\d+)ms;?\s*$"

Simon Roadknight 23
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

How can it be specified mathematically?


From our valid song examples, the beep pattern always has the pattern of

∶ 𝑑𝑒𝑙𝑎𝑦 < 𝑛𝑢𝑚1 > 𝑚𝑠;

OR

: 𝑑𝑒𝑙𝑎𝑦 < 𝑛𝑢𝑚1 > 𝑚𝑠


The semi-colon at the end is optional, as we can see in the valid song examples given.

Using regular expression, we can define the beep pattern to be

𝑑𝑒𝑙𝑎𝑦𝑃𝑎𝑡𝑡𝑒𝑟𝑛: string
𝑑𝑒𝑙𝑎𝑦𝑃𝑎𝑡𝑡𝑒𝑟𝑛 = "^\𝑠 ∗: 𝑑𝑒𝑙𝑎𝑦\𝑠 + (\𝑑+)𝑚𝑠; ?\𝑠 ∗ $"

Figure 16 delayPattern Regex graph

Implementing it in F#
We can define delayPattern as the string: "^\s*:delay\s+(\d+)ms;?\s*$"

let delayPattern = "^\s*:delay\s+(\d+)ms;?\s*$"

How can it be specified mathematically?


Using regular expression, we can define out whitespace and blank lines to be
𝑤ℎ𝑖𝑡𝑒𝑠𝑝𝑎𝑐𝑒𝑃𝑎𝑡𝑡𝑒𝑟𝑛: string
𝑤ℎ𝑖𝑡𝑒𝑠𝑝𝑎𝑐𝑒𝑃𝑎𝑡𝑡𝑒𝑟𝑛 = "^\𝑠 ∗ $"

Figure 17 whitespacePattern Regex graph

Implementing it in F#
We can define whitespacePattern as the string: "^\s*$"

let whitespacePattern = "^\s*$"

Simon Roadknight 24
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Song file


What are we representing and why?
We are representing a text file that only contains lines that match our song file patterns of
beepPattern, delayPattern, and whitespacePattern. The valid example song files we were given
contain only these lines. We want to represent a song file so that we can determine if we should try
parsing the sequence of strings into a song or not. Having the sequence of string have the type of
SongFile gives more semantic meaning to the data.

How can it be specified mathematically?


Using our defined patterns, we can define SongFile as the sequence of string that are valid
beepPattern, delayPattern, or whitespacePattern.

𝑙𝑒𝑡 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: ℙ 𝑠𝑒𝑞 (𝑠𝑡𝑟𝑖𝑛𝑔)


𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 = {𝑠𝑒𝑞 (𝑥: 𝑠𝑡𝑟𝑖𝑛𝑔 | 𝑥 = 𝑏𝑒𝑒𝑝𝑃𝑎𝑡𝑡𝑒𝑟𝑛 ∨ 𝑥 = 𝑑𝑒𝑙𝑎𝑦𝑃𝑎𝑡𝑡𝑒𝑟𝑛 ∨ 𝑥 = 𝑤ℎ𝑖𝑡𝑒𝑠𝑝𝑎𝑐𝑒𝑃𝑎𝑡𝑡𝑒𝑟𝑛)}

Implementing it in F#
type SongFile = private SongFile of seq<string>

Simon Roadknight 25
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Frequency


What are we representing and why?
Frequency is the speed of the vibration, the higher the speed the higher the pitch, the lower the
speed the lower the pitch. Frequency is measured as the number of waves that occur in one second,
and the unit of frequency measurement is Hertz (Hz) (How Music Works, n.d) we are representing
the set of valid Hz for a frequency. We want to be able to play a sound that is audible to humans, to
do this we must be able to model the frequency of the sound.

How can it be specified mathematically?


Frequency is a subset of the set unit, we have put a constraint on the set Frequency such that the
members of the set Frequency are in the range 31 to 19000 inclusive. The reason I have chosen this
range as both the lower bound and upper bound are respectively around the lowest and highest
frequencies humans can hear.

Figure 18 (Cmglee, 2014)

Simon Roadknight 26
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦𝑀𝑖𝑛 ∶ 𝑢𝑖𝑛𝑡
𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦𝑀𝑖𝑛 = 31

𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦𝑀𝑎𝑥 ∶ 𝑢𝑖𝑛𝑡
𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦𝑀𝑎𝑥 = 19000

𝐹𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦: ℙ uint
𝐹𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦 = {𝑥: 𝑢𝑖𝑛𝑡 | 𝑥 ≥ 𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦𝑀𝑖𝑛 ∧ 𝑥 ≤ 𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦𝑀𝑎𝑥}

Implementing it in F#
We can define Frequency as a single discriminated union case ‘Frequency’, which is a subset of uint.

type Frequency = private Frequency of uint

Simon Roadknight 27
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Length of Time


What are we representing and why?
We are representing a length of time in milliseconds. In the example song files we can see that each
beep and delay instruction have a length specified. We want to be able to represent a length of time
so that we can play a beep or have a pause between notes for a defined amount of time.

How can it be specified mathematically?

𝑡𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ𝑀𝑖𝑛 ∶ 𝑢𝑖𝑛𝑡
𝑡𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ𝑀𝑖𝑛 = 50

𝑡𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ𝑀𝑎𝑥 ∶ 𝑢𝑖𝑛𝑡
𝑡𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ𝑀𝑎𝑥 = 5000

𝑇𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ: ℙ uint
𝑇𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ = {𝑥: 𝑢𝑖𝑛𝑡 |𝑥 ≥ 𝑡𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ𝑀𝑖𝑛 ∧ 𝑥 ≤ 𝑡𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ𝑀𝑎𝑥 }

TimeLength is a subset of the set uint, with a range between 50ms and 5000ms. I have chosen 50ms
as the minimum time length as it gives sometime between a beep can change, and it is a valid
minimum range from the song examples. For the maximum, I didn’t want a beep to be able to play
too long, and there were no delays greater than 5000ms in the example song files.

Implementing it in F#
We can define TimeLength as a single discriminated union case ‘TimeLength’, which is a subset of
uint.

type TimeLength = private TimeLength of uint

Simon Roadknight 28
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Beep


What are we representing and why?
We are representing the beeps that can be seen in our example song files, each beep contains a
frequency and length. We want to be able to represent a beep in our program so that we can parse
the beep pattern into a type that has meaning and can later be played in a song.

How can it be specified mathematically?


Beep is the Cartesian product of all Frequency * TimeLength. This means that all pairs of Frequency
and TimeLength are valid Beep’s.

𝑙𝑒𝑡 𝐵𝑒𝑒𝑝 = 𝐹𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦 × 𝑇𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ

Implementing it in F#
We can define Beep as a single discriminated union case ‘Beep’, which is the set of all Frequency
paired with the set of all TimeLength.

type Beep = private Beep of Frequency * TimeLength

Data model: Delay


What are we representing and why?
We are representing the delays that can be seen in our example song files, each delay contains a
length. We want to be able to represent a delay in our program so that we can parse the delay
pattern into a type that has meaning and can later be played in a song.

How can it be specified mathematically?


𝑙𝑒𝑡 𝐷𝑒𝑙𝑎𝑦 = 𝑇𝑖𝑚𝑒𝐿𝑒𝑛𝑔𝑡ℎ
Delay contains one property, a TimeLength, we can create a Delay that is equal to TimeLength under
the hood?

Implementing it in F#
We can define Delay as a single discriminated union case ‘Delay’, which is the set of all TimeLength.

type Delay = private Delay of TimeLength

Simon Roadknight 29
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Song Instruction


What are we representing and why?
We are representing the set of song instructions that can played by our song player, that is a beep
and delay. We can see in our example songs, that we will need to play beeps and delays back-to-
back and in order. We want to be able to have both instructions be part of the same set, as between
them they make up a song and play both types of instruction.

How can it be specified mathematically?


We can define this as a disjoint union between Beep and Delay, this allows us to know the original
set each instruction was from.

𝑙𝑒𝑡 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 = 𝐵𝑒𝑒𝑝 ⊔ 𝐷𝑒𝑙𝑎𝑦

Implementing it in F#
We can use a discriminated union to create our SongInstruction type. This allows us to have values
for multiple names, each with a different type. (Contributors of the Microsoft .NET documentation
2021b)

type SongInstruction = private


|BeepInstruction of Beep
|DelayInstruction of Delay

Data model: Song


What are we representing and why?
We are representing a song which is an ordered sequence of song instructions, from the valid song
files we can see that that is the case and can’t find any examples that go against it. We want to be
able to represent it so that we know when we have a valid sequence of song instructions that we can
then play. Having it as a Song instead of a sequence of SongInstruction shows it is a complete and
valid song in terms of the input file path.

How can it be specified mathematically?


We can mathematically define Song to be equal to the of sequence of SongInstruction

𝑙𝑒𝑡 𝑆𝑜𝑛𝑔 = 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛

Implementing it in F#
We can implement Song as a sequence of SongInstruction, I have chosen to use sequence as it will
allow F# to choose the data structure which it thinks is best for the situation. I didn’t choose a
specific data structure like an array as we don’t need quick access to any given index at a particular
time.

type Song = seq<SongInstruction>

Simon Roadknight 30
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Song play time


What are we representing and why?
The play time of a song, the total length of time all beeps and delays last. The specification identifies
that user should be displayed the total play time of all the songs, to know the play time of all the
songs we need to be able to get the play time of a single song.

How can it be specified mathematically?


We can mathematically define SongPlayTime to be equal to the set of float.

𝑙𝑒𝑡 𝑆𝑜𝑛𝑔𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒 = 𝑓𝑙𝑜𝑎𝑡

Implementing it in F#
We can define SongPlayTime as a type alias for float.

type SongPlayTime = float

Data model: Output Message


What are we representing and why?
A message to be displayed to the user once the program has been executed to display the success
message. In the specification it is described that the user will want to see the play time of all the
songs. In our program we want to be able to see where output messages occur, and what functions
might have the side effect of printing an output message.

How can it be specified mathematically?


An output message at the minimum can be any possible string, so I have chosen to define it as
equivalent to the set of string.

𝑙𝑒𝑡 𝑂𝑢𝑡𝑝𝑢𝑡𝑀𝑒𝑠𝑠𝑎𝑔𝑒 = 𝑠𝑡𝑟𝑖𝑛𝑔

Implementing it in F#
We can define OutputMessage as a type alias for string.

type OutputMessage = string

Simon Roadknight 31
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Error message


What are we representing and why?
A message to be displayed to the user once the program has been executed to display any error
messages that have arisen as described in the Gherkin specifications. In our program we want to be
able to see where error messages occur, and what functions might have the side effect of printing an
error message.

How can it be specified mathematically?


An error message at the minimum can be any possible string, so I have chosen to define it as
equivalent to the set of string.

𝑙𝑒𝑡 𝐸𝑟𝑟𝑜𝑟𝑀𝑒𝑠𝑠𝑎𝑔𝑒 = 𝑠𝑡𝑟𝑖𝑛𝑔

Implementing it in F#
We can define ErrorMessage as a type alias for string.

type ErrorMessage = string

Simon Roadknight 32
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Error / Song error


What are we representing and why?
We are representing any error that can occur in the program. From our gherkin specification we
have gathered a list of possible errors, and these need to be handled. We want to represent each
song error that may occur so we can attach error messages and exit codes to them.

How can it be specified mathematically?


We can mathematically define SongError as the set of all possible song errors in our program.

𝑙𝑒𝑡 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 = {
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐴𝑟𝑔𝐴𝑚𝑜𝑢𝑛𝑡, 𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ, 𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝑆𝑜𝑛𝑔,
𝐼𝑂𝐸𝑟𝑟𝑜𝑟𝑂𝑝𝑒𝑛𝑖𝑛𝑔𝐹𝑖𝑙𝑒, 𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑒𝑟𝑚𝑖𝑠𝑠𝑖𝑜𝑛,
𝐸𝑚𝑝𝑡𝑦𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝐹𝑖𝑙𝑒𝑁𝑜𝑡𝐹𝑜𝑢𝑛𝑑
}

Implementing it in F#
We can define it as a discriminated union in F# with each error being a union case.

type SongError =
|InvalidArgAmount
|InvalidFilePath
|InvalidSong
|IOErrorOpeningFile
|InvalidFilePermission
|EmptySongFile
|FileNotFound

Simon Roadknight 33
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Data model: Exit code


What are we representing and why?
We are representing if the program exited with success or failure, when our program ends we want
must provide an exit code, our main function as seen earlier takes in a string [] and returns an int.
We want to be able to exit the program with meaningful exit codes, so that users, and even more so
ours who are system administrators, can use it troubleshoot, and know what went wrong.

How can it be specified mathematically?


We can define Success to be equal to 0 and the errors to be equal to an int (exit code) that is
meaningful (Contributors to the Microsoft .NET documentation, 2021c).

We can then define ExitCode to be the set of all the errors and Success.
𝑆𝑢𝑐𝑐𝑒𝑠𝑠 ∶ 𝑖𝑛𝑡
𝑆𝑢𝑐𝑐𝑒𝑠𝑠 = 0
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐴𝑟𝑔𝐴𝑚𝑜𝑢𝑛𝑡: 𝑖𝑛𝑡
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐴𝑟𝑔𝐴𝑚𝑜𝑢𝑛𝑡 = 1
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: 𝑖𝑛𝑡
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ = 160
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: 𝑖𝑛𝑡
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 = 13
𝐼𝑂𝐸𝑟𝑟𝑜𝑟𝑂𝑝𝑒𝑛𝑖𝑛𝑔𝐹𝑖𝑙𝑒: 𝑖𝑛𝑡
𝐼𝑂𝐸𝑟𝑟𝑜𝑟𝑂𝑝𝑒𝑛𝑖𝑛𝑔𝐹𝑖𝑙𝑒 = 4
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑒𝑟𝑚𝑖𝑠𝑠𝑖𝑜𝑛: 𝑖𝑛𝑡
𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑒𝑟𝑚𝑖𝑠𝑠𝑖𝑜𝑛 = 5
𝐸𝑚𝑝𝑡𝑦𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: 𝑖𝑛𝑡
𝐸𝑚𝑝𝑡𝑦𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 = 13
𝐹𝑖𝑙𝑒𝑁𝑜𝑡𝐹𝑜𝑢𝑛𝑑: 𝑖𝑛𝑡
𝐹𝑖𝑙𝑒𝑁𝑜𝑡𝐹𝑜𝑢𝑛𝑑 = 2
𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒 ∶ ℙ 𝑖𝑛𝑡
𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒 = {𝑆𝑢𝑐𝑐𝑒𝑠𝑠, 𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐴𝑔𝐴𝑚𝑜𝑢𝑛𝑡, 𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ, 𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝑆𝑜𝑛𝑔𝑓𝑖𝑙𝑒,
𝐼𝑂𝐸𝑟𝑟𝑜𝑟𝑂𝑝𝑒𝑛𝑖𝑛𝑔𝐹𝑖𝑙𝑒, 𝐼𝑛𝑣𝑎𝑙𝑖𝑑𝐹𝑖𝑙𝑒𝑃𝑒𝑟𝑚𝑖𝑠𝑠𝑖𝑜𝑛, 𝐸𝑚𝑝𝑡𝑦𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝐹𝑖𝑙𝑒𝑁𝑜𝑡𝐹𝑜𝑢𝑛𝑑}

Implementing it in F#
We can use the Enumeration type in F# to assign labels to specific values. As we know the exit codes
won’t be changing during the execution of the process, we are happy to assign the values at the type
level.

type ExitCode = Success = 0 | InvalidArgAmount = 1 | InvalidFilePath = 160 | InvalidSongFile = 13


|IOErrorOpeningFile = 4 | InvalidFilePermission = 5
| EmptySongFile = 13 | FileNotFound = 2

Simon Roadknight 34
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

T1 - Behaviour steps and function specification


Behaviour step: Playing a single song, and multiple songs holistic view
Now that we know what data we have in our program, we can start to look at what behaviours need
to be implemented to carry out each feature.

The first feature is to play a single song file, looking at the Gherkin specifications and data modelling,
these are the initial behaviour steps I came up with.

𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡 ⇸ FilePath

𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: 𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ ⇸ 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒

𝑔𝑒𝑡𝑆𝑜𝑛𝑔: 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 ⇸ 𝑆𝑜𝑛𝑔

𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝐻𝑎𝑛𝑑𝑙𝑒𝑟: 𝑆𝑜𝑛𝑔 → 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒

𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒. 𝑣𝑎𝑙𝑢𝑒: 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒 → 𝑖𝑛𝑡

Simon Roadknight 35
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

We can represent this as a data-transformation graph, where the nodes represent our data types,
and the edges represent functions. This allows us to see how each specific function takes us from
one part of our data model to another. (Cooper, 2022a)

𝑑𝑎𝑡𝑎𝑇𝑓𝑃𝑖𝑝𝑒𝑙𝑖𝑛𝑒𝑆𝑖𝑛𝑔𝑙𝑒𝑆𝑜𝑛𝑔𝐸𝑥𝑎𝑚𝑝𝑙𝑒: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑑𝑎𝑡𝑎𝑇𝑓𝑃𝑖𝑒𝑝𝑒𝑙𝑖𝑛𝑒𝐴𝑟𝑔𝑇𝑜𝑆𝑜𝑛𝑔 = {(𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡, 𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ), (𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ, 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒),
(𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝑆𝑜𝑛𝑔), (𝑆𝑜𝑛𝑔, 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒)}

Figure 19 Single song initial behaviour steps Data Transformation Graph

We can also represent this as a task-transition graph where the nodes represent individual functions,
and the edge represent the input / output data. This allows us to see the flow of data in our program
between each function. This can help use model our inputs and outputs for function in our program.
(Cooper, 2022b)

𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑆𝑖𝑛𝑔𝑙𝑒𝑆𝑜𝑛𝑔𝐸𝑥𝑎𝑚𝑝𝑙𝑒: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑃𝑖𝑝𝑒𝑙𝑖𝑛𝑒𝐴𝑟𝑔𝑇𝑜𝑆𝑜𝑛𝑔 = {(𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ, 𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒), (𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝑔𝑒𝑡𝑆𝑜𝑛𝑔),
(𝑔𝑒𝑡𝑆𝑜𝑛𝑔, 𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝐻𝑎𝑛𝑑𝑙𝑒𝑟), (𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝐻𝑎𝑛𝑑𝑙𝑒𝑟, 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒. 𝑣𝑎𝑙𝑢𝑒)}

Figure 20 Playing a single


song initial behaviour
steps Task transition
graph

Simon Roadknight 36
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Partial and total functions


The functions in the initial behaviour steps are partial functions, meaning not every item in the
source set has a mapping to an item in the target set. This can cause errors, exceptions and
unexpected behaviours.

In F# if we know we are going to have a partial function, here are types we can use to turn the
partial function into a total function, dealing with those items in the source set that do not have a
mapping to the target set. One of these types is the result type (Contributors to the Microsoft .NET
documentation, 2021d), which allows us to return an Ok value if the input has a mapping to the
target set, or an Error if not.

We will use the idea of the Result type when constructing our behaviour steps for playing both a
single and multiple songs.

I have not included the unwrapping of the Result type in my graphs, for this I use Result.bind
unless specified otherwise (FSharp, n.d (a)) Where a graph shows a Result Type as input the
function, but a different type in the maths, assume it has been unwrapped.

𝑐ℎ𝑒𝑐𝑘𝐴𝑟𝑔𝐴𝑚𝑜𝑢𝑛𝑡: 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑠 → Result < Arguments, SongError >

𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝑠𝑀𝑎𝑝𝑝𝑒𝑟: 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑠, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 > →


Result < seq Arguments ∗ Result < Song, SongError ≫, SongError >

𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝑠𝐻𝑎𝑛𝑑𝑙𝑒𝑟: Result < seq Arguments ∗ Result < Song, SongError ≫, SongError >
→ ExitCode

𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒. 𝑣𝑎𝑙𝑢𝑒: 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒 → int

Simon Roadknight 37
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

𝑑𝑎𝑡𝑎𝑇𝑓𝑀𝑎𝑖𝑛: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑑𝑎𝑡𝑎𝑇𝑓𝑀𝑎𝑖𝑛 = {
(𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑠, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑠, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >)
(𝑅𝑒𝑠𝑢𝑙𝑡 < 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑠, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 < 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡 × 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 ≫, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >),
(𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 < 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡 × 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 ≫, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >, 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒),
(𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒, 𝐼𝑛𝑡)}

Figure 21 Program behaviour steps - Data transformation graph

𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑀𝑎𝑖𝑛: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑀𝑎𝑖𝑛 = {(𝑐ℎ𝑒𝑐𝑘𝐴𝑟𝑔𝐴𝑚𝑜𝑢𝑛𝑡, 𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝑠𝑀𝑎𝑝𝑝𝑒𝑟),
(𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝑠𝑀𝑎𝑝𝑝𝑒𝑟, 𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝑠𝐻𝑎𝑛𝑑𝑙𝑒𝑟),
(𝑠𝑜𝑛𝑔𝑅𝑒𝑠𝑢𝑙𝑡𝑠𝐻𝑎𝑛𝑑𝑙𝑒𝑟, 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒. 𝑣𝑎𝑙𝑢𝑒)
}

Figure 22 Program
behaviour steps - Task
transition graph

Simon Roadknight 38
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Behaviour step: Argument to File path


What are we representing and why?
We are representing the relationship between Argument and FilePath, specifically the function that
takes in an Argument as its source set and has the target set of FilePath. However, not all elements
of Argument are mapped to an output, for example “C://\\” would not have a mapping to the target
set, making this a partial function. We can use the Result type to totalise the function and return a
useful error should the file path not be valid. We need to be able to represent the relationship
between an argument and a file path.

How can it be specified mathematically?

𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡 ⇸ FilePath

𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡 → Result < FilePath, SongError >

𝑚𝑎𝑡𝑐ℎ𝐹𝑝𝑃𝑎𝑡𝑡𝑒𝑟𝑛: 𝑠𝑡𝑟𝑖𝑛𝑔 → Result < string, SongError >

𝑡𝑜𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

𝑑𝑎𝑡𝑎𝑇𝑓𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: ℙ(𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑑𝑎𝑡𝑎𝑇𝑓𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ = {
(𝑠𝑡𝑟𝑖𝑛𝑔, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >),
(𝑠𝑡𝑟𝑖𝑛𝑔, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >)
}

Figure 24 Argument to FilePath data transformation graph

𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ: ℙ(𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ = {
(𝑚𝑎𝑡𝑐ℎ𝐹𝑝𝑃𝑎𝑡𝑡𝑒𝑟𝑛, 𝑡𝑜𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ)
}

Figure 23 Argument
to FilePath task
transition graph

Simon Roadknight 39
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Behaviour step: File path to song file


What are we representing and why?
We are representing the relationship between FilePath and SongFile, specifically the function that
takes in a FilePath as its source set and has the target set of SongFile. However, not all valid file
paths are valid song files, making this a partial function. We want to be able to represent the
relationship between a file path and a song file.

How can it be specified mathematically?

𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: 𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ ⇸ 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒

𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: 𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

We can see the symbol declarations for the partial and total functions for the relationship between
FilePath and SongFile.

𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ. 𝑣𝑎𝑙𝑢𝑒: 𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ → 𝑠𝑡𝑟𝑖𝑛𝑔

𝑐ℎ𝑒𝑐𝑘𝐼𝑓𝐹𝑖𝑙𝑒𝐸𝑥𝑖𝑠𝑡𝑠: 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

𝑟𝑒𝑎𝑑𝐹𝑖𝑙𝑒𝐶𝑜𝑛𝑡𝑒𝑛𝑡: 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

𝑖𝑠𝐹𝑖𝑙𝑒𝐸𝑚𝑝𝑡𝑦: 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

𝑖𝑠𝑉𝑎𝑙𝑖𝑑𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

𝑡𝑜𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

Simon Roadknight 40
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

𝑑𝑎𝑡𝑎𝑇𝑓𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑑𝑎𝑡𝑎𝑇𝑓𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 = {
(𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ, 𝑠𝑡𝑟𝑖𝑛𝑔),
(𝑠𝑡𝑟𝑖𝑛𝑔, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >),
(𝑠𝑡𝑟𝑖𝑛𝑔, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟),
(𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >),
(𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >),
(𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >)
}

Figure 25 FilePath to SongFile data transformation graph

Simon Roadknight 41
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 = {
(𝐹𝑖𝑙𝑒𝑃𝑎𝑡ℎ. 𝑣𝑎𝑙𝑢𝑒, 𝑐ℎ𝑒𝑐𝑘𝐼𝑓𝐹𝑖𝑙𝑒𝐸𝑥𝑖𝑠𝑡𝑠),
(𝑐ℎ𝑒𝑐𝑘𝐼𝑓𝐹𝑖𝑙𝑒𝐸𝑥𝑖𝑠𝑡𝑠, 𝑟𝑒𝑎𝑑𝐹𝑖𝑙𝑒𝐶𝑜𝑛𝑡𝑒𝑛𝑡 ),
(𝑟𝑒𝑎𝑑𝐹𝑖𝑙𝑒𝐶𝑜𝑛𝑡𝑒𝑛𝑡, 𝑖𝑠𝐹𝑖𝑙𝑒𝐸𝑚𝑝𝑡𝑦),
(𝑖𝑠𝐹𝑖𝑙𝑒𝐸𝑚𝑝𝑡𝑦, 𝑖𝑠𝑉𝑎𝑙𝑖𝑑𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒),
(𝑖𝑠𝑉𝑎𝑙𝑖𝑑𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝑡𝑜𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒)
}

Figure 26 FilePath to SongFile task


transition graph

Simon Roadknight 42
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Behaviour step: Song file to song


What are we representing and why?
We are representing the relationship between SongFile and Song, specifically the function that takes
in a SongFile as its source set and has the target set of Song. Not all valid song files are valid songs,
making this a partial function. We need to be able to represent the relationship between SongFile
and Song as we want to be able to transform a SongFile which is a sequence of string to a Song,
which is a sequence of Song Instructions

How can it be specified mathematically?

𝑔𝑒𝑡𝑆𝑜𝑛𝑔: 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 ⇸ 𝑆𝑜𝑛𝑔

𝑔𝑒𝑡𝑆𝑜𝑛𝑔: 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

We can see the symbol declarations for the partial and total functions for the relationship between
SongFile and Song.

𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒. 𝑣𝑎𝑙𝑢𝑒: 𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒 → 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔

𝑟𝑒𝑚𝑜𝑣𝑒𝑊ℎ𝑖𝑡𝑒𝑠𝑝𝑎𝑐𝑒: 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔

𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑂𝑝𝑡𝑠: 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔 → 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 𝑜𝑝𝑡𝑖𝑜𝑛

𝑖𝑠𝑆𝑜𝑛𝑔𝑉𝑎𝑙𝑖𝑑: 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 𝑜𝑝𝑡𝑖𝑜𝑛 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

𝑖𝑠𝐸𝑚𝑝𝑡𝑦: 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

𝑡𝑜𝑆𝑜𝑛𝑔: 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 → 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >

Simon Roadknight 43
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

𝑑𝑎𝑡𝑎𝑇𝑓𝑔𝑒𝑡𝑆𝑜𝑛𝑔: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑑𝑎𝑡𝑎𝑇𝑓𝑔𝑒𝑡𝑆𝑜𝑛𝑔 = {
(𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒, 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔),
(𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔),
(𝑠𝑒𝑞 𝑠𝑡𝑟𝑖𝑛𝑔, 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 𝑜𝑝𝑡𝑖𝑜𝑛),
(𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 𝑜𝑝𝑡𝑖𝑜𝑛, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >),
(𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >),
(𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >)
}

Figure 27 SongFile to Song data transformation graph

𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝑆𝑜𝑛𝑔: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝑆𝑜𝑛𝑔 = {
(𝑆𝑜𝑛𝑔𝐹𝑖𝑙𝑒. 𝑣𝑎𝑙𝑢𝑒, 𝑟𝑒𝑚𝑜𝑣𝑒𝑊ℎ𝑖𝑡𝑒𝑠𝑝𝑎𝑐𝑒),
(𝑟𝑒𝑚𝑜𝑣𝑒𝑊ℎ𝑖𝑡𝑒𝑠𝑝𝑎𝑐𝑒, 𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑂𝑝𝑡𝑠),
(𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑂𝑝𝑡𝑠, 𝑖𝑠𝑆𝑜𝑛𝑔𝑉𝑎𝑙𝑖𝑑),
(𝑖𝑠𝑆𝑜𝑛𝑔𝑉𝑎𝑙𝑖𝑑, 𝑖𝑠𝐸𝑚𝑝𝑡𝑦),
(𝑖𝑠𝐸𝑚𝑝𝑡𝑦, 𝑡𝑜𝑆𝑜𝑛𝑔)
}

Figure 28 SongFile to
Song task transition
graph

Simon Roadknight 44
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Behaviour step: Song results handler


What are we representing and why?
We are representing between a sequence of Result<seq<Argument * Result<Song,SongError>>,
SongError> and an ExitCode. We want to be able to generate and print a successful output message
or an error message, and then return an ExitCode of the correct value.

How can it be specified mathematically?

𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑠𝑒𝑞 < 𝐴𝑟𝑔𝑢𝑚𝑒𝑛𝑡 ∗ 𝑅𝑒𝑠𝑢𝑙𝑡 < 𝑆𝑜𝑛𝑔, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 >>, 𝑆𝑜𝑛𝑔𝐸𝑟𝑟𝑜𝑟 > → 𝐸𝑥𝑖𝑡𝐶𝑜𝑑𝑒

Simon Roadknight 45
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Behaviour step: Getting the play time of a song


What are we representing and why?
The relation between a Song and SongPlayTime, it was in the specification that the program should
be able to output the total play time of one or more songs. We need to have the functions in place
that can take us from a Song to the play time of a song. This will be used in the calculation of the
total play time.

How can it be specified mathematically?

𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑆𝑒𝑐𝑜𝑛𝑑𝑠: 𝑆𝑜𝑛𝑔 → 𝑆𝑜𝑛𝑔𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒

The function getPlayTimeInSeconds requires these behaviour steps:

𝑣𝑎𝑙𝑢𝑒: 𝑆𝑜𝑛𝑔 → 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛

𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑀𝑠: 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 → 𝑠𝑒𝑞 𝑖𝑛𝑡

𝑐𝑜𝑛𝑣𝑒𝑟𝑡𝑇𝑜𝐹𝑙𝑜𝑎𝑡𝑠: 𝑠𝑒𝑞 𝑖𝑛𝑡 → 𝑠𝑒𝑞 𝑓𝑙𝑜𝑎𝑡

𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑀𝑠: 𝑠𝑒𝑞 𝑓𝑙𝑜𝑎𝑡 → 𝑓𝑙𝑜𝑎𝑡

𝑐𝑜𝑛𝑣𝑒𝑟𝑡𝑀𝑠𝑇𝑜𝑆: 𝑓𝑙𝑜𝑎𝑡 → 𝑓𝑙𝑜𝑎𝑡

𝑟𝑜𝑢𝑛𝑑𝑇𝑜𝑇𝑤𝑜𝐷𝑖𝑔: 𝑓𝑙𝑜𝑎𝑡 → 𝑓𝑙𝑜𝑎𝑡

Simon Roadknight 46
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

𝑑𝑎𝑡𝑎𝑇𝑓𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑆𝑒𝑐𝑜𝑛𝑑𝑠: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑑𝑎𝑡𝑎𝑇𝑓𝑃𝑖𝑝𝑒𝑙𝑖𝑛𝑒𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑆𝑒𝑐𝑜𝑛𝑑𝑠 = {
(𝑆𝑜𝑛𝑔, 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛),
(𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑠𝑒𝑞 𝑖𝑛𝑡),
(𝑠𝑒𝑞 𝑖𝑛𝑡, 𝑠𝑒𝑞 𝑓𝑙𝑜𝑎𝑡 ),
(𝑠𝑒𝑞 𝑓𝑙𝑜𝑎𝑡, 𝑓𝑙𝑜𝑎𝑡),
(𝑓𝑙𝑜𝑎𝑡, 𝑓𝑙𝑜𝑎𝑡),
(𝑓𝑙𝑜𝑎𝑡, 𝑓𝑙𝑜𝑎𝑡)
}

Figure 29 Getting the play time of a song data transformation graph

𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑆𝑒𝑐𝑜𝑛𝑑𝑠: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑆𝑒𝑐𝑜𝑛𝑑𝑠 = {
(𝑣𝑎𝑙𝑢𝑒, 𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑀𝑠),
(𝑔𝑒𝑡𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑀𝑠, 𝑐𝑜𝑛𝑣𝑒𝑟𝑡𝑇𝑜𝐹𝑙𝑜𝑎𝑡𝑠),
(𝑐𝑜𝑛𝑣𝑒𝑟𝑡𝑇𝑜𝐹𝑙𝑜𝑎𝑡𝑠, 𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑀𝑠),
(𝑔𝑒𝑡𝑃𝑙𝑎𝑦𝑇𝑖𝑚𝑒𝐼𝑛𝑀𝑠, 𝑐𝑜𝑛𝑣𝑒𝑟𝑡𝑀𝑠𝑇𝑜𝑆),
(𝑐𝑜𝑛𝑣𝑒𝑟𝑡𝑀𝑠𝑇𝑜𝑆, 𝑟𝑜𝑢𝑛𝑑𝑇𝑜𝑇𝑤𝑜𝐷𝑖𝑔)
}

Figure 30 Getting the play time of a


song task transition graph

Simon Roadknight 47
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Behaviour step: Playing a song


What are we representing and why?
The relationship between a Song and having each instruction in that song be played through the
speakers in the form of a beep, or a pause in the beep through a delay. The main features of our
song player are to be able to play one song and multiple songs. We need a way to transform from a
Song to audio output, we do not care about the return value of this function, so we have used the F#
unit type, which can be thought of as being like Null or Void in other languages.

How can it be specified mathematically?

𝑝𝑙𝑎𝑦𝑆𝑜𝑛𝑔: 𝑆𝑜𝑛𝑔 → 𝑢𝑛𝑖𝑡

The function playSong requires these behaviour steps:

𝑣𝑎𝑙𝑢𝑒: 𝑆𝑜𝑛𝑔 → 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛

𝑝𝑙𝑎𝑦𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠: 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛 → 𝑢𝑛𝑖𝑡

Simon Roadknight 48
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

𝑑𝑎𝑡𝑎𝑇𝑓𝑝𝑙𝑎𝑦𝑆𝑜𝑛𝑔: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑑𝑎𝑡𝑎𝑇𝑓𝑝𝑙𝑎𝑦𝑆𝑜𝑛𝑔 = {
(𝑆𝑜𝑛𝑔, 𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛),
(𝑠𝑒𝑞 𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛, 𝑢𝑛𝑖𝑡)
}

Figure 31 Playing a song data transformation graph

𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑝𝑙𝑎𝑦𝑆𝑜𝑛𝑔: ℙ (𝑁𝑜𝑑𝑒 × 𝑁𝑜𝑑𝑒)


𝑡𝑎𝑠𝑘𝑇𝑟𝑛𝑠𝑝𝑙𝑎𝑦𝑆𝑜𝑛𝑔 = {
(𝑣𝑎𝑙𝑢𝑒, 𝑝𝑙𝑎𝑦𝑆𝑜𝑛𝑔𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠)
}

Figure 32 Playing a song task transformation graph

Simon Roadknight 49
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

T1 - Reflection

I am happy with the overall design and planning process of the SongPlayer program. My user stories
are well defined and leave very little room for ambiguity, my data modelling works very well for the
creation of song instructions, and the song itself, however it does fall either side of the construction
of a song or multiple songs, such as the output and error message modelling, this had a knock-on
effect when defining my behaviour steps. My behaviour steps go through all major functionality of
the program, accompanied by data transformation and task transition graphs, as well as function
relations. I would like to improve on the mathematics of the functions, especially the constraining
predicates. Looking forward at the rest of the work completed, the importance of strong data
modelling and feature behaviours has become even more apparent, this is something I will take with
me when I do my final year project as it will most likely be a software development project on a
much larger scale than this.

Simon Roadknight 50
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

T2 - Implementation

We can use the previous defined Gherkin specifications, data modelling, and behaviour steps to start
implementing our programming.

Implementation of data models and behaviour steps for the data

Implementation: Argument and Arguments type aliases


Implementation for Argument and Arguments as defined in the data modelling process.
module Input =
type Argument = string
type Arguments = string []
Figure 33 Creation of Input module and Argument and Arguments type aliases

Simon Roadknight 51
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: FilePath type creation and construction behaviour steps


Creating the FilePath type which is a single discriminated union case.
module FilePath =
type FilePath = private FilePath of string
Figure 34 FilePath module creating and FilePath type

Defining the regular expressions to be used to match Windows and Linux file paths, in this
implementation the program will always match for both, regardless of the Operating System (OS).

Creating the Regex objects with the regular expressions discussed in the data modelling. Defining
two active patterns, one for the Windows file pattern, and one for Linux. I chose to use active
patterns so I could use them in the pattern match expression inside of matchFpPattern. Defining the
function takes in the string and matches it with the active patterns, if there is a match it is a valid file
path, otherwise we return an error.

let windowsFilePathPattern = Regex(@"^[a-zA-Z]:\\[\\\S|*\S]?.*$")


let linuxFilePathPattern = Regex("^(/[^/ ]*)+/?$")
// Link to source for both patterns in source code

let private (|WindowsFpPattern|_|) (str:string) =


match (windowsFilePathPattern.IsMatch str) with
|true -> Some(str)
|false -> None

let private (|LinuxFpPattern|_|) (str:string) =


match (linuxFilePathPattern.IsMatch str) with
|true -> Some(str)
|false -> None

let private matchFpPattern (fp:string) =


match fp with
|WindowsFpPattern fp -> Ok fp
|LinuxFpPattern fp -> Ok fp
|_ -> Error SongError.InvalidFilePath
Figure 35 Defining Regex patterns for Windows and Linux file paths

This is the constructor function for FilePath where we can pass an Argument in and have an Ok
FilePath returned or an Error SongError.
let getFilePath (argument:Input.Argument) = // Argument -> Result<FilePath,
SongError>
argument
|> matchFpPattern
|> Result.bind (FilePath >> Ok)
Figure 36 getFilePath pipeline

We can pass in a FilePath to value, and it will deconstruct is it using pattern matching.
(discriminated_unions). I will refer to these as deconstruction functions throughout the logbook.
let value (FilePath fp) : Input.Argument = fp // FilePath -> Argument
Figure 37 FilePath deconstruction

Simon Roadknight 52
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Song instruction patterns


Implementing the song file patterns inside a module that is accessible to other modules so the code
can be reusable where needed.

Creating the Regex objects for the beep, delay, and whitespace patter, and adding the active
patterns for each of them that we can use in our pattern match expressions. I chose to it this way as
it makes the pattern matches later cleaner and readable to anyone viewing the code.
module SongInstructionPatterns =
let beepPattern = Regex("^\s*:beep\s+frequency=(\d+)\s+length=(\d+)ms;?\s*$")
let delayPattern = Regex("^\s*:delay\s+(\d+)ms;?\s*$")
let whitespacePattern = Regex("^\s*$")
let (|BeepPattern|_|) (str:string) =
match (beepPattern.IsMatch str) with
|true -> Some(str)
|false -> None

let (|DelayPattern|_|) (str:string) =


match (delayPattern.IsMatch str) with
|true -> Some(str)
|false -> None

let (|WhitespacePattern|_|) (str:string) =


match (whitespacePattern.IsMatch str) with
|true -> Some(str)
|false -> None
Figure 38 Defining Regex patterns for the valid patterns in a song file

I have created two functions for extracting the data from the regular expressions, one for the beeps,
and one for the delays. To extract the correct data from the Beeps and Delays I have chosen to use
capture groups, which when a pattern is matched, such as the beep pattern, it’ll also return the data
in the capture groups, which for a Beep would be the Frequency and TimeLength.
let extractBeepValues (str:string) = // string -> string * string
let groupValues = beepPattern.Match(str).Groups
groupValues.Item(1).Value, groupValues.Item(2).Value

let extractDelayValues (str:string) = // string -> string


delayPattern.Match(str).Groups.Item(1).Value
Figure 39 Function to extract the values required from Beep and Delay instructions

Simon Roadknight 53
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: SongFile type creation and construction behaviour steps

Creating the SongFile type which is a single discriminated union case.


module SongFile =
type SongFile = private SongFile of seq<string>
Figure 40 SonFile Module and Type

Creating the functions outlined in the behaviour steps for the relationship of FilePath to SongFile.

1. We check that the file exists, returning an Ok string if it does or an Error FileNotFound if not
2. We read the contents of the file using F# built in file I/O, I chose to use this route as it opens
and closes the file, and from the example songs, the songs are short enough to not worry
about memory concerns. When performing I/O there are an array of things that can go
wrong, I have decided to catch any exceptions, and instead return a useful error to the user.
3. We check to see if the file we read in was empty, if it was, we return an Error EmptySongFile,
otherwise we return an Ok seq<string>
4. We then check each string in the sequence to see if it matches the song patters we
previously defined, if all of the strings have a match then we return an Ok seq<string>
otherwise we return an Error InvalidSong

let private checkIfFileExists filePath = // string -> Result<string, SongError>


if (filePath |> System.IO.File.Exists)
then filePath |> Ok
else Error SongError.FileNotFound
let private readFileContent filePath = // string -> Result<seq<string>, SongError>
try
System.IO.File.ReadAllLines filePath
|> Array.toSeq
|> Ok
with
| :? System.IO.IOException ->
Error SongError.IOErrorOpeningFile
| :? System.UnauthorizedAccessException ->
Error SongError.InvalidFilePermission
| :? System.Security.SecurityException ->
Error SongError.InvalidFilePermission
let private checkIfFileIsEmpty (fileContents:seq<string>) = // seq<string> ->
Result<seq<string>, SongError>
if fileContents |> Seq.isEmpty
then Error SongError.EmptySongFile
else fileContents |> Ok

let private isValidSongInstruction (str:string) = // string -> bool


match str with
|SongInstructionPatterns.BeepPattern songInstruction ->
true
|SongInstructionPatterns.DelayPattern songInstruction ->
true
|SongInstructionPatterns.WhitespacePattern songInstruction ->
true
|_ ->
false

Simon Roadknight 54
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks
let private isValidSongFile (file:seq<string>) = // seq<string> ->
Result<seq<string>, SongError>
if file |> Seq.forall isValidSongInstruction
then file |> Ok
else Error SongError. InvalidSong
Figure 41 Functions that make up the getSongFile pipeline

Constructor function for SongFile.


let getSongFile (filePath:FilePath.FilePath) = //FilePath -> Result<SongFile, SongError>
filePath
|> FilePath.value
|> checkIfFileExists
|> Result.bind readFileContent
|> Result.bind checkIfFileIsEmpty
|> Result.bind isValidSongFile
|> Result.bind (SongFile >> Ok)
Figure 42 getSongFile pipeline

Deconstruction function for SongFile. Used to extract seq<string> from the SongFile.
let value (SongFile sf) = sf // SongFile -> seq<string>
Figure 43 SongFile destructor

Simon Roadknight 55
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Frequency type creation and construction behaviour steps


Creating the module for Frequency and the type which is a single discriminated union case. In my
initial data modelling and planning stages I decide on Frequency being of the type of uint, during
implementation this changed to int as the built in function that will be making our beep sound,
expects two int values, one for the frequency and one for the duration (Microsoft, n.d)
module Frequency =
type Frequency = private Frequency of int
Figure 44 Frequency module and type

1. We define the minFrequency as 37 and maxFrequency 19000 to set up the constraints for
our Frequency when it is being constructed. The minFrequency has slightly changed from
our initial data modelling, this is due to constraints on Console.Beep that specified anything
below 37 will throw an exception.
2. Create the function that takes in an int and return a Some int if the int is in the range
specified, otherwise it returns a None. I have chosen to use the Option type to represent if a
Frequency is valid or not, this was decided when I chose what errors would be returned to
the user, and they didn’t go as low as describing what instruction or part of the instruction
was invalid.

let private minFrequency = 37


let private maxFrequency = 19000
let private isValidFrequency frequency = // int -> int option
if frequency >= minFrequency && frequency <= maxFrequency
then Some frequency
else None
Figure 45 Defining min and max range for frequency and the function to check a frequency has a valid range

Constructor function for Frequency. This returns Some Frequency if it is valid, or a None if it isn’t.
let constructFrequency frequency = // int -> Frequency option
frequency
|> isValidFrequency
|> Option.bind (Frequency >> Some)
Figure 46 Pipeline for constructing a Frequency

Deconstruction function for Frequency.


let value (Frequency f) = f // Frequency -> int
Figure 47 Frequency destructor

Simon Roadknight 56
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: TimeLength type creation and construction behaviour steps

Creating the TimeLength type. During implementation I changed the type of TimeLength from uint to
int, as it is used in Console.Beep like Frequency, as well as Threading.Sleep.
module TimeLength =
type TimeLength = private TimeLength of int
Figure 48 TimeLength module and type

1. We define the timeLengthMin as 50 and the timeLengthMax as decided upon in the data
modelling stages.
2. We check that the int being used to construct the TimeLength is within range.
let private timeLengthMin = 50
let private timeLengthMax = 5000
let private isValidLength timeLength = // int -> int option
if timeLength >= timeLengthMin && timeLength <= timeLengthMax
then Some timeLength
else None
Figure 49 Defining the min and max rangefor TimeLength and creating a function to check whether a value is in range

Constructor function for TimeLength.


let constructTimeLength timeLength = // int -> TimeLength option
timeLength
|> isValidLength
|> Option.bind (TimeLength >> Some)
Figure 50 Pipeline for constructing a TimeLength

Deconstruction function for TimeLength.


let value (TimeLength tl) = tl // TimeLength -> int
Figure 51 TimeLength destructor

Simon Roadknight 57
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Beep type creation and construction behaviour steps

Creating the module for Beep and the type which is a single discriminated union case.
module Beep =
type Beep = private Beep of Frequency.Frequency * TimeLength.TimeLength
Figure 52 Beep Module and type

We can use the constructBeep function to create a Beep, it takes in a pair of strings. It will then:

1. Check if the strings can be parsed to int32


2. If they can both be parsed, then the first item in the tuple will be passed into
constructFrequencyt and the second item into constructTimeLength, if they are both
successful, we return Some Beep, otherwise we return None. The option type was chosen as
my error handling didn’t return specific details at this level.

let constructBeep (frequency:string, timeLength:string) = // string * string -> Beep


option
let freqTpl, lengthTpl =
System.Int32.TryParse frequency, System.Int32.TryParse timeLength
match freqTpl, lengthTpl with
|(true, x), (true, y) ->
(Frequency.constructFrequency x, TimeLength.constructTimeLength y)
|> function
|Some frequency, Some timeLength ->
Beep(frequency, timeLength) |> Some
|_ -> None
|_ -> None
Figure 53 Function for constructing a Beep

Deconstruction function for Beep.


let value (Beep(frequency, timeLength)) = frequency, timeLength
// Beep -> Frequency * TimeLength
Figure 54 Beep destructor

Simon Roadknight 58
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Delay type creation and construction behaviour steps


Creating the module for Delay and the type which is a single discriminated union case.

module Delay =
type Delay = private Delay of TimeLength.TimeLength
Figure 55 Delay module and type

The constructDelay function take in a single string as an argument, it then tries to parse it to an
Int32, if it can parse successfully, we will try to construct a TimeLength. We then either return Some
Delay or None.
let constructDelay (timeLength:string) = // string -> Delay option
let (parsed, x) = timeLength |> System.Int32.TryParse
if parsed
then TimeLength.constructTimeLength x
|> function
|Some timeLength -> Delay(timeLength) |> Some
|None -> None
else None
Figure 56 Function for constructing a Delay

Deconstruction function for Beep


let value (Delay d) = d // Delay -> TimeLength
Figure 57 Delay destructor

Simon Roadknight 59
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Song instruction

Creating the SongInstruction discriminated union. In our data modelling we have the set of
SongInstruction that contains both Beep and Delay types, luckily for us F# gives us an easy way to do
that in the form of discriminated unions, which allows us to have values that can be of more than
one type.
module SongInstruction =
type SongInstruction = private |BeepInstruction of Beep.Beep
|DelayInstruction of Delay.Delay
Figure 58 SongInstruction module and type

The constructSongInstruction function has the source set of string and the target set of
SongInstruction Option. The function will perform a pattern match on the string using the song file
patterns defined earlier, if it matches a Beep or Delay instruction pattern, it will call the
constructBeepInstruction or constructDelayInstruction respectively, the return value of the function
is Some SongInstruction or None.

// string -> SongInstruction Option


let constructSongInstruction (songInstruction:string) =
match songInstruction with
|SongInstructionPatterns.BeepPattern songInstruction ->
constructBeepInstruction(SongInstructionPatterns.extractBeepValues songInstruction)
|SongInstructionPatterns.DelayPattern songInstruction ->
constructDelayInstruction(SongInstructionPatterns.extractDelayValues songInstruction)
|_ -> None
// string * string -> SongInstruction option
let private constructBeepInstruction (frequency:string, timeLength:string) =
(frequency, timeLength)
|> Beep.constructBeep
|> Option.bind (BeepInstruction >> Some)

// string -> SongInstruction option


let private constructDelayInstruction (timeLength:string) =
timeLength
|> Delay.constructDelay
|> Option.bind (DelayInstruction >> Some)
Figure 59 Pattern matching for a Beep or Delay pattern and constructing the SongInstruction

Simon Roadknight 60
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Song
Creating the Song type which is a single discriminated union case.
module Song =
type Song = private Song of seq<SongInstruction.SongInstruction>
Figure 60 Song module and type

Once we have a valid SongFile, we can pass it into get song which will do the follow:

1. Get the seq<string> from the SongFile use the SongFile.value


2. Use the built-in filter function on the sequence so the new sequence contains only lines that
match the beep and delay patterns
3. It will then pass the newly created sequence into getSongInstructionOpts, which will apply
the constructSongInstruction to each element in the sequence
4. We then check that no elements in the sequence are equal to None, if they are we will
return an Error InvalidSong, otherwise we will return an Ok seq<SongInstruction>, we can
get the value out of all the Some(x) by using the choose function (FSharp, n.d (b))
5. Now that we have removed the whitespace and parsed our lines, we can make sure that the
song file is not empty, if it is we return an Error EmptySongFile
6. We then unpack the result if it is an Ok seq<SongInstruction> and pass it into an Ok Song,
otherwise we will return whichever Error was triggered previously.

Figure
let 61 getSong pipeline
getSong (songFile:SongFile.SongFile) = // SongFile -> Result<Song, SongError>
songFile
|> SongFile.value
|> removeWhitespace
|> getSongInstructionsOpts
|> isSongValid
|> Result.bind isEmpty
|> Result.bind (Song >> Ok)

let private removeWhitespace (strs:seq<string>) = // seq<string> -> seq<string>


strs
|> Seq.filter (fun x -> Regex.IsMatch(x, @"^\s*$") |> not)
let private getSongInstructionsOpts (strs:seq<string>) = // seq<string> ->
seq<SongInstruction option>
strs
|> Seq.map SongInstruction.constructSongInstruction
// seq<SongInstruction option> -> Result<seq<SongInstruction>, SongError>
let private isSongValid (songInstructionsOpt:seq<SongInstruction.SongInstruction
option>) =
if songInstructionsOpt |> Seq.contains None
then Error SongError. InvalidSong
else songInstructionsOpt |> Seq.choose (fun x -> x) |> Ok
let private isEmpty (songInstructions:seq<SongInstruction.SongInstruction>) = //
seq<SongInstruction> -> Result<seq SongInstruction, SongError>
if songInstructions |> Seq.isEmpty
then Error SongError. EmptySongFile
else songInstructions |> Ok
Figure 62 Defining functions for validating a song to be used in the getSong pipeline

Deconstruction function for song.


let value (Song s) = s // Song -> seq<SongInstruction>

Simon Roadknight 61
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Errors

Creating the module for SongError and the type SongError, which contains union cases for any of the
errors that can occur during the program. This allows us to create a particular Error of SongError,
and then use that SongError in pattern matching to get an error message or exit code.
module SongError =
type SongError =
|InvalidArgAmount
|InvalidFilePath
| InvalidSong
|IOErrorOpeningFile
|InvalidFilePermission
|EmptySongFile
|FileNotFound
Figure 63 SongError module and defining the SongError type

The getErrorMessage function takes in a SongError as its source set, and return a string, which is an
error message relating to the input SongError.
let getErrorMessage =
function
|InvalidArgAmount -> "ERROR: No input arguments specified. Try specifying a
song file (e.g., C:\users\anexampleuser\songfiles\song1.txt)."
|InvalidFilePath -> "ERROR: Invalid file path (Please check the file path
was entered correctly and try again)."
| InvalidSong -> "ERROR: Invalid song instruction found in file (e.g.,
invalid :beep or :delay instruction)."
|IOErrorOpeningFile -> "ERROR: IO Error when trying to open the file."
|InvalidFilePermission -> "ERROR: Invalid file permissions (Please check the
file permissions for the file and try again)."
|EmptySongFile -> "ERROR: File contains no song instructions (Please check
the correct file path was specified)."
|FileNotFound -> "ERROR: File not found (Please check the file path was
entered correctly)."
Figure 64 Mapping each SongError to a string with a meaningful message for the error

Simon Roadknight 62
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Error output message


Creating the module for ErrorMessageHandling, and type alias of ErrorMessage for string.

module ErrorMessageHandling =
type ErrorMessage = string
Figure 65 ErrorMessageHandling module and type

This errorOutput function takes in a seq<Argument * SongError> and returns an ErrorMessage,


which is a string consisting of the argument, and the reason for error.
seq<Argument * SongError> -> ErrorMessage
let errorOutput (failureSongs:Input.Argument * SongError.SongError) : ErrorMessage =
sprintf "%s - %s\n" (fst failureSongs) (snd failureSongs |>
SongError.getErrorMessage)
Figure 66 Generating an error message for a specific input that had an error

Implementation: Exit code

Creating the module for ExitCode, and an enumeration type for ExitCode . We can have labels for
our exit codes that we decided upon in our Gherkin syntax.
module ExitCode =
type ExitCode = Success = 0 | InvalidArgAmount = 1 | InvalidFilePath = 160
| InvalidSong = 13 |IOErrorOpeningFile = 4
| InvalidFilePermission = 5 | EmptySongFile = 13
| FileNotFound = 2
Figure 67 ExitCode module and enum type

If we have an error, we want to be able to get the correct exit code, we are able use pattern
matching to match the SongError and return an ExitCode. This is a total function, each SongError has
a mapping to an ExitCode.
// SongError -> ExitCode
let getSongErrorExitCode (songError:SongError.SongError) =
match songError with
|SongError.InvalidArgAmount -> ExitCode.InvalidArgAmount
|SongError.InvalidFilePath -> ExitCode.InvalidFilePath
|SongError. InvalidSong -> ExitCode. InvalidSong
|SongError.IOErrorOpeningFile -> ExitCode.IOErrorOpeningFile
|SongError.InvalidFilePermission -> ExitCode.InvalidFilePermission
|SongError.EmptySongFile -> ExitCode.EmptySongFile
|SongError.FileNotFound -> ExitCode.FileNotFound
Figure 68 Mapping each SongError to an ExitCode

Finally, we need a way to be able to get the associated int from our Enum type.
let value (e:ExitCode) = int e // ExitCode -> int
Figure 69 getting int from ExitCodeFigure 70 SongPlayTime type

Simon Roadknight 63
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Song play time

Creating the type SongPlayTime which is a single discriminated union case.


type SongPlayTime = float

The behaviour steps for getting songs play time is:

1. Getting the seq<SongInstruction> from a Song using the value function in the Song module.
2. Getting each individual instructions play time by using the map function and applying the
getSongInstructionPlayTime function on each element in the seq<SongInstruction>
a. This will perform a pattern match on the SongInstruction and then call the
getBeepPlayTime and getDelayPlayTime depending on what type of instruction it is.
3. We have a sequence of int, but to display the play time of the songs as per the Gherkin
specification and data modelling we want to convert to a float
4. We then use the reduce function on the sequence of int with the (+) operation to get the
total play of the song in milliseconds
5. We convert milliseconds to seconds by dividing by 1000.
6. We then round to two decimal places. (If this number is 56.085 it will round to the nearest
even number, -> 56.08.)

// seq<SongInstruction> -> seq<int>


let private getSongInstructionsPlayTime songInstructions =
songInstructions
|> Seq.map SongInstruction.getSongInstructionPlayTime
// SongInstruction -> int (function is called by getSongInstructionsPlayTime)
let getSongInstructionPlayTime (songInstruction:SongInstruction) =
match songInstruction with
|BeepInstruction(songInstruction) -> Beep.getBeepPlayTime songInstruction
|DelayInstruction(songInstruction) -> Delay.getDelayPlayTime songInstruction
// Beep -> int (function is called by getSongInstructionPlayTime)
let getBeepPlayTime (beep:Beep) =
beep
|> value
|> snd
|> TimeLength.value
// Delay -> int (function is called by getSongInstructionPlayTime)
let getDelayPlayTime (delay:Delay) =
delay
|> value
|> TimeLength.value
let private convertToFloats songInstructionsPlayTimeInt = // seq<int> -> seq<float>
songInstructionsPlayTimeInt
|> Seq.map float
let private getPlayTimeInMs songInstructionsPlayTimeFloat = // seq<float> -> float
songInstructionsPlayTimeFloat
|> Seq.reduce (+)
let private convertMsToS (playTimeMs:float) = // float -> float
playTimeMs / 1000.0
let private roundToTwoDig (playTimeS:float) = // float -> float
System.Math.Round(playTimeS, 2)
Figure 71 Functions used for getting and calculating songs play time

Simon Roadknight 64
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Constructor function for SongPlayTime. As we are dealing with a song as input for the SongPlayTime,
we know what we are dealing with and shouldn’t run into any errors. However, should something
change in the way Song is constructed, it could impact getPlayTimeInSeconds.
let getPlayTimeInSeconds (song:Song) : SongPlayTime = // Song -> SongPlayTime
song
|> value
|> getSongInstructionsPlayTime
|> convertToFloats
|> getPlayTimeInMs
|> convertMsToS
|> roundToTwoDig
Figure 72 Pipeline for getPlayTimeInSeconds

Simon Roadknight 65
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Playing a song

With our constructed song we want to be able to play it. The first step in this is defining a function
that takes in a song and gets the sequence of SongInstruction that it contains.

let playSong (song:Song) : unit =


song
|> value
|> playSongInstructions
System.Threading.Thread.Sleep(3000)
Figure 73 Pipeline for playing a song

Using playSongInstructions with the source set of the seq<SongInstruction> we can iterate over each
instruction using Seq.iter and play that instruction.

let playSongInstructions (songInstructions:seq<SongInstruction.SongInstruction>) :


unit =
songInstructions
|> Seq.iter SongInstruction.playInstruction
Figure 74 Play each song instruction

To play each instruction, we must know if it is a BeepInstruction, or


DelayInstruction. To do this we can pattern match the SongInstruction and play a Beep or Delay
accordingly. The function is a total function as every item in the source set maps to the target set.

let playInstruction (songInstruction:SongInstruction) = // SongInstruction - unit


match songInstruction with
|BeepInstruction(beep) ->
beep |> Beep.playBeep
|DelayInstruction(delay) ->
delay |> Delay.playDelay
Figure 75 Function to play an individual song instruction

The playBeep function takes in a Beep and returns a unit, it extracts the Frequency and TimeLength
from the Beep, and then we get the int from Frequency and TimeLength, and pass it into
Console.Beep.

let playBeep beep = // Beep -> unit


let (x,y) = beep |> value
System.Console.Beep(x |> Frequency.value, y |> TimeLength.value)
Figure 76 Function to play a beep

To play a delay instruction we can extract the TimeLength value from the Delay, and then extract the
int from TimeLength, and pass it into Thread.Sleep.

let playDelay delay = // Delay -> unit


let timeLength = delay |> value
System.Threading.Thread.Sleep(timeLength |> TimeLength.value)
Figure 77 Function to play a delay

Simon Roadknight 66
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Output message

Creating the module for OutputMessageHandling, and type alias of OutputMessage for string.
module OutputMessageHandling =
type OutputMessage = string
Figure 78 OutputMessageHandling module

This generates the initial output message displaying how many songs are playing, and the total
length of all songs to be played, we can calculate this by creating a list of all the individual song play
times and using the reduce function to add them all together.
// seq<Argument * Song> -> OutputMessage
let successOutput (successSongs:seq<Input.Argument * Song.Song>) : OutputMessage =
let numberOfSongs =
successSongs |> Seq.length
let totalPlayTime =
successSongs
|> Seq.map snd
|> Seq.map Song.getPlayTimeInSeconds
|> Seq.reduce (+)
sprintf "Now playing %i song(s) with a total play time of %gs." numberOfSongs totalPlayTime
Figure 79 Generating a success output to display the number of songs to be played and the play time of all songs combined

Simon Roadknight 67
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation – Tying it all together

Implementation: What happens at the entry point of the program

For the SongPlayer program I have tried to use the concepts from Railway Oriented Programming
(Wlaschin, n.d) handling the errors at the end, instead of splitting off and throwing exceptions where
they may crop up.

We can see the main entry point follows the behaviour steps we agreed upon.
[<EntryPoint>]
let main (argv:Input.Arguments) = // string[] -> int
argv
|> checkArgAmount
|> songResultsMapper
|> songResultsHandler
|> ExitCode.value
Figure 80 Entry point of the SongPlayer program

The checkArgAmount function takes in Arguments, and returns a Result<Arguments, SongError>, it


will be an Ok Arguments if there is at least one arguments specified otherwise it will be an Error
InvalidArgAmount.
let checkArgAmount (args:Input.Arguments) = // Arguments ->
Result<Arguments,SongError>
if Array.length args >= 1
then Ok args
else Error SongError.InvalidArgAmount
Figure 81 Function to check if at least one Argument has been specified

songResultsMapper take in the result we got from checkArgAmount, and then passes that into
Result.Map along with the getSongResults function.
let songResultsMapper (args:Result<Input.Arguments, SongError.SongError>) =
let returnVal = Result.map getSongResults args
returnVal
Figure 82 Result mapper

Simon Roadknight 68
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Argument to Song pipeline


This pipeline will take in an Argument and go through each of the behaviour steps previously
discussed, validating the FilePath, validating that it’s a SongFile, and finally parsing it to a Song. All
along the way we can make these total functions through the use of the Result type.

Finally, it returns the Argument passed in, along with the result of it being passed through the
pipeline.

// Argument -> Argument * Result<Song,SongError>


let getSongResult arg =
let songResult =
arg
|> FilePath.getFilePath
|> Result.bind SongFile.getSongFile
|> Result.bind Song.getSong

arg, songResult
Figure 83 Pipeline to transfrom an arg to a Song

Simon Roadknight 69
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Implementation: Song handler


The songResultsHandler has the source set of Result<seq<Argument * Result<Song,SongError>>,
SongError> and has the target set of ExitCode.

First it will check if there was an Ok result for the number of arguments, if there wasn’t it will return
an Error InvalidArgAmount, otherwise it will then create two collections, one for any successful
songs that have been parsed, and one for any songs that were not successfully parsed. If there were
unsuccessful songs, we generate the ErrorMessage for each one and output it to the stderr, and
then we get the ExitCode for the first error in the sequence. If all songs were parsed successfully, we
generate the output message containing the number of songs to be played, and the play time of all
the songs and print it to the stdout. We then play each song in order, printing to the stdout each
time a new song begins, and a message letting the user know when all songs have played.

// Result<seq<Argument * Result<Song,SongError>>, SongError> -> ExitCode


let songResultsHandler (pipelineResults:Result<seq<Input.Argument *
Result<Song.Song, SongError.SongError>>, SongError.SongError>) =
match pipelineResults with
|Ok songResults ->
let successSongs = songResults |> getSuccessSongs
let failureSongs = songResults |> getFailureSongs
if Seq.isEmpty failureSongs
then
(successSongs |> OutputMessageHandling.successOutput) |> printfn
"%s"
successSongs |> playSongs
let finishedMessage:OutputMessageHandling.OutputMessage = "Finished
playing all songs."
printfn "%s" finishedMessage
ExitCode.ExitCode.Success
else
let errorMessage =
let (firstErrorLine:ErrorMessageHandling.ErrorMessage) =
(failureSongs |> Seq.length) |> sprintf "Error in playing
song(s). %i song(s) with an error found:\n"
let buildErrorMessage =
failureSongs
|> Seq.map ErrorMessageHandling.errorOutput
|> Seq.fold (+) firstErrorLine
buildErrorMessage
errorMessage |> eprintfn "%s"
Seq.head failureSongs |> snd |> ExitCode.getSongErrorExitCode
|Error err ->
err |> SongError.getErrorMessage |> eprintfn "%s"
err |> ExitCode.getSongErrorExitCode
Figure 84 Function to handle the input of all attmpted Argument to Song transofrmations

Simon Roadknight 70
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

let getSuccessSongs x =
x
|> Seq.choose (fun songResult ->
match songResult with
|(arg, Result.Ok ok) -> Some (arg, ok)
|(arg, Result.Error _) -> None)

let getFailureSongs x =
x
|> Seq.choose (fun songResult ->
match songResult with
|(arg, Result.Ok _) -> None
|(arg, Result.Error err) -> Some (arg, err))
Figure 85 returns a sequence of songs that were succesfully parsed.

let playSongs x =
x
|> Seq.iter (fun (argument:Input.Argument, song:Song.Song) -> printfn "Now
playing: %s" argument; Song.playSong song)
Figure 86 Plays all songs that input

T2 - Reflection

I am happy with the implementation in respect to the gherkin specifications, data modelling and
behaviour steps, however when implementing the program, it was much clearer to implement the
data with a better-defined data model, and the behaviour steps which were involved or focused on
those data models. This is shown in the main pipeline of the programs, the steps involved creating a
song and the creation of the song itself, are clear and concise, whereas the implementation
surrounding that could be better formed, for example the songResultsHandler and the initial data
that is passed into it.

Simon Roadknight 71
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

T3 – Testing
In this section we will be testing our implementation from the perspectives of a user, architect, and
coder so that we can make sure our implementation is fit for purpose, as described in the Gherkin
specifications, it should not encounter errors that will make it halt or crash, and that each piece of
code is doing what we expect.

This is important to do before we let a user the program, for usability, and security concerns. Going
through this process can limit any errors or problems that the program may have.

We will be using a variety of testing methods to accomplish this, from the domains of manual and
automated testing.

Manual testing
What is manual testing?
For our initial testing of the program, we will be using manual testing which focuses on the features
and behaviours from the user’s perspective and interacting with the program itself. Manual testing
allows us to do this by creating a set of test cases with defined input, execution steps, and output
(https://www.guru99.com/test-case.html). We can then use the program following the set of
manual tests to see:

1. That the program behaves as we planned in our Gherkin specifications:


a. That it produces the correct output when there is valid input
b. That it produces the correct errors when there is invalid input
c. There are no incidents where undefined behaviour occurs
2. The usability of the program, that what we have come up with and created is sufficient
to the original specification, and a joy to use.

Why do we want to us it?


We want to be able to see that the software is function as intended, both from our Gherkin
specifications and from the original specification given to use, so that we can identify any
insufficiencies in our Gherkin specification.

How can we use it?


To carry out the manual testing we will create a set of test cases, will use the Gherkin specifications
agreed upon previously for the basis of my tests. Once the set of test cases are created, I will run the
program as if I were a user and comparing the expected output to the actual output.

Simon Roadknight 72
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks
Table 3 Table of input files for manual testing

File Path File Description Song play time


(ms)
C:\users\anexampleuser\songfiles\song1.txt Valid file path to a valid 56085
song file
C:\users\anexampleuser\songfiles\song2.txt Valid file path to a valid 121000
song file
C:\users\anexampleuser\songfiles\song3.txt Valid file path to a valid 55325
song file
C:\users\anexampleuser\songfiles\song4.txt Valid file path to a valid 32650
song file
C:\users\anexampleuser\songfiles\invalidsong1.txt Valid file path to an N/A
invalid song file
(contains invalid song
instruction(s))
C:\users\anexampleuser\songfiles\invalidsong2.txt Valid file path to an N/A
invalid song file (file
does not exist)
C:\users\anexampleuser\songfiles\invalidsong3.txt Valid file path to an N/A
invalid song file (Invalid
file permissions)
C:\users\anexampleuser\songfiles\invalidsong4.txt Valid file path to an N/A
invalid song file (Empty
song file)
C:\users\anexampleuser\songfiles\invalidsong5.txt Valid file path to an N/A
invalid song file (Forced
I/O Error)
C://\\ Invalid file path N/A
C://\\2 Invalid file path N/A

Simon Roadknight 73
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Below are the set of test cases, each containing an ID, the actions being carried, the input to be able
to carry out these actions, the expected output and expected exit code.

Table 4 SongPlayer manual test table

TestID Manual test being carried Input to program Expected output to the Exp.
out user Exit
Code
1 Execute the song player C:\users\anexampleus Now playing 1 song(s) with a 0
program using a valid path er\songfiles\song1.txt total play time of 56.08s.
argument to a single valid Now playing:
song file C:\users\anexampleuser\so
ngfiles\song1.txt
Finished playing all songs.
2 Execute the song player No input ERROR: No input arguments 1
program without specifying specified. Try specifying a
a file path song file (e.g.,
C:\users\anexampleuser\so
ngfiles\song1.txt).
3 Execute the song player C:\users\anexampleus Error in playing song(s). 1 13
program using a valid file er\songfiles\invalidson song(s) with an error found:
path argument to a single g1.txt C:\users\anexampleuser\so
invalid song file ngfiles\invalidsong1.txt –
ERROR: Invalid song
instruction found in file.
(e.g., invalid :beep or :delay
instruction)

4 Execute the song player C://\\ Error in playing song(s). 1 160


program using an invalid file song(s) with an error found:
path argument C://\\ - ERROR: Invalid file
path (Please check the file
path was entered correctly
and try again).
5 Execute the song player C:\users\anexampleus Now playing 2 song(s) with a 0
program using two valid file er\songfiles\song1.txt total play time of 177.08s.
path arguments to two valid C:\users\anexampleus Now playing:
song files er\songfiles\song2.txt C:\users\anexampleuser\so
ngfiles\song1.txt
Now playing:
C:\users\anexampleuser\so
ngfiles\song2.txt
Finished playing all songs.
6 Execute the song player C:\users\anexampleus Error in playing song(s). 2 13
program using two valid file er\songfiles\invalidson song(s) with an error found:
path arguments to two g1.txt C:\users\anexam C:\users\anexampleuser
invalid song files pleuser\songfiles\invali \songfiles\invalidsong1.txt -
dsong2.txt ERROR: Invalid song
instruction found in file
(e.g., invalid :beep or :delay
instruction).
C:\users\anexampleuser
\songfiles\invalidsong2.txt -
ERROR: File not found

Simon Roadknight 74
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

(Please check the file path


was entered correctly and
try again).
7 Execute the song player C:\users\anexampleus Error in playing song(s). 1 13
program using one valid file er\songfiles\song1.txt song(s) with an error found:
path argument to one valid C:\users\anexampleus C:\users\anexampleuser
song file and one valid file er\songfiles\invalidson \songfiles\invalidsong1.txt -
path argument to one g1.txt ERROR: Invalid song
invalid song file instruction found in file
(e.g., invalid :beep or :delay
instruction).
8 Execute the song player C://\\ Error in playing song(s). 2 160
program using two invalid C://\\2 song(s) with an error found:
file path arguments C://\\ - ERROR: Invalid file
path (Please check the file
path was entered correctly
and try again).
C://\\2 - ERROR: Invalid file
path.
9 Execute the song player C:\users\anexampleus Error in playing song(s). 1 160
program using one valid er\songfiles\song1.txt song(s) with an error found:
path argument to one valid C://\\ C://\\ - ERROR: Invalid file
song file and one invalid file path (Please check the file
path argument path was entered correctly
and try again).
10 Execute the song player C:\users\anexampleus Now playing 3 song(s) with a 0
program using three valid er\songfiles\song2.txt total play time of 208.97s.
file path arguments to three C:\users\anexampleus Now playing:
valid song files er\songfiles\song3.txt C:\users\anexampleuser\so
C:\users\anexampleus ngfiles\song2.txt
er\songfiles\song4.txt C:\users\anexampleuser\so
ngfiles\song3.txt
C:\users\anexampleuser\so
ngfiles\song4.txt
Finished playing all songs.
11 Execute the song player C:\users\anexampleus Error in playing song(s). 2 2
program using a mixture of er\songfiles\song1.txt song(s) with an error found:
valid and invalid file path C:\users\anexampleus C:\users\anexampleuser
arguments to a mixture of er\songfiles\invalidson \songfiles\invalidsong1.txt -
valid and invalid song files g1.txt ERROR: Invalid song
C://\\ instruction found in file
(e.g., invalid :beep or :delay
instruction).
C:\users\anexampleuser
\songfiles\invalidsong2.txt -
ERROR: File not found
(Please check the file path
was entered correctly and
try again).

12 Execute the song player C:\users\anexample Error in playing song(s). 3 5


program using three valid user\songfiles\invali song(s) with an error found:
file paths, one with invalid dsong3.txt C:\users\anexampleuser\
file permission, one which is C:\users\anexample songfiles\invalidsong3.txt
an empty file and one which - ERROR: Invalid file
user\songfiles\invali
causes an I/O Error permissions (Please check
dsong4.txt

Simon Roadknight 75
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

C:\users\anexample the file permissions for


user\songfiles\invali the file and try again).
dsong5.txt C:\users\anexampleuser\
songfiles\invalidsong4.txt
- ERROR: File contains
no song instructions
(Please check the correct
file path was specified).
C:\users\anexampleuser\
songfiles\invalidsong5.txt
- ERROR: IO Error when
trying to open the file.

Test 1

Figure 87 Manual test case 1 input arguments

Figure 88 Manual test case 1 output

Simon Roadknight 76
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Test 2

Figure 89 Manual test case 2 input arguments

Figure 90 Manual test case 2 output

Simon Roadknight 77
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Test 3

Figure 91 Manual test case 3 input arguments

Figure 92 Manual test case 3 output

Test 4

Figure 93 Manual test case 4 input arguments

Simon Roadknight 78
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Figure 94 Manual test case 4 output

Test 5

Figure 95 Manual test case 5 input arguments

Figure 96 Manual test case 5 output

Simon Roadknight 79
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Test 6

Figure 97 Manual test case 6 input arguments

Figure 98 Manual test case 6 output

Test 7

Figure 99 Manual test case 7 input arguments

Simon Roadknight 80
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Figure 100 Manual test case 7 output

Test 8

Figure 101 Manual test case 8 input arguments

Figure 102 Manual test case 8 output

Simon Roadknight 81
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Test 9

Figure 103 Manual test case 9 input arguments

Figure 104 Manual test case 9 output

Test 10

Figure 105 Manual test case 10 input arguments

Simon Roadknight 82
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Figure 106 Manual test case 10 output

Test 11

Figure 107 Manual test case 11 input arguments

Figure 108 Manual test case 11 output

Simon Roadknight 83
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Test 12

Figure 109 Manual test case 12 input arguments

Figure 110 Manual test case 12 output

Simon Roadknight 84
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Automated testing
What is automated testing?
Automated tests let us test our code and program programmatically, that is to write pieces of
software that we can execute to test our program (https://www.guru99.com/automation-
testing.html), be those specific functions, larger parts of the program, or the whole program. We
want to be able to run many tests at once with varying input and output, automated testing allows
for this in a way that manual testing does not.

Why do we want to use it?


We want to use automated testing for various reasons such as:

1. If we have automated tests written for a specific function, if we were to make changes to
that function, we would be able to run the already written test to make sure that we haven’t
broken the function, and that the test case still passes.
2. To be able to test as many scenarios as possible at one time, that is to be able to provide
many inputs to expected outputs and run them all at once.
3. We can use them while developing our software, we can choose to write tests before hand
and then write our program, when we have completed individual functions, test for those
functions should now pass instead of fail. This is known as Test-Driven Development
(https://www.guru99.com/test-driven-development.html).

Like manual testing, this allows us to confirm our program is working as intended, however, on a
much larger scale and at a more granular level. As we can test specific functions, we are able to look
back at our data modelling, behaviour steps and function specifications, and see any insufficiencies
in our execution, or iterate on what we previously thought was the intended goal.

How do we use it?


Automated testing isn’t a one size fits all, there are different ways of automating tests, and different
kinds of test we would like to automate, such as a specific function, or a behaviour of the program.
We will be using automated testing in the following ways:

1. Unit-testing allows us to test specific functions, and larger parts of our code by supplying an
input and expected out.
2. Property-based testing will also allow us to test specific functions, and larger parts of our
code, instead of providing it with specific input, we are able to generate the input based on
the properties it has, meaning instead of only testing on a few specific values we can
potentially test on thousands and millions.
3. Automated behavioural tests, these will be based on our gherkin specifications agreed upon
earlier. It will allow us to carry out tests such as our manual test suite, but in an automated
way.

Simon Roadknight 85
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Unit-testing
The first kind of automated testing we will use is unit testing, this is taking parts of our code and
testing them individually, we will be applying parts of the code to a known input, with a known
expected output. This will let us know if any individual parts of the code are not working as intended.

To carry out the unit tests we will look back at our behaviour steps, and function definitions, and
pick out small enough functions that when we do run the tests, should they fail it will be a small
enough piece of code to be able to debug and retest.

With that in mind I have decided to unit test the following functionality as each of these pieces are
self-contained enough should they not pass any unit tests written for them, I will have sufficient
information to start debugging the issue.

1. Constructing a beep
2. Constructing a delay
3. Constructing a file path (Validating the file path can be a file path)
4. Constructing a song file
5. Construct song
6. Get song play time

testID Input Action Expected Results


1 (“500”, “500”) constructBeep Some Beep (Frequency 500,
TimeLength 500)
2 (“37”, “500”) constructBeep Some Beep (Frequency 37,
TimeLength 500)
3 (“19000”, “250”) constructBeep Some Beep (Frequency 19000,
TimeLength 500)
4 (“36”, “750”) constructBeep None
5 (“19001”, “500”) constructBeep None
6 “500” constructDelay Some Delay (TimeLength 500)
7 “50” constructDelay Some Delay (TimeLength 50)
8 “5000” constructDelay Some Delay (TimeLength 5000)
9 “49” constructDelay None
10 “5001” constructDelay None
11 “C:\users\anexampleuser\so getFilePath Ok FilePath
ngfiles\song1.txt” “C:\users\anexampleuser\songfiles\
song1.txt”
12 C://\\ getFilePath Error InvalidFilePath
13 “C:\users\anexampleuser\so getFilePath Ok
ngfiles\song1.txt” getSongFile SongFile
(seq
[":beep frequency=660
length=100ms;"; ":delay 150ms;";
":beep frequency=660
length=100ms;"; ":delay 300ms;";
...])

Simon Roadknight 86
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

14 “C:\users\anexampleuser\so getFilePath Error FileNotFound


ngfiles\invalidsong2.txt” getSongFile
15 “C:\users\anexampleuser\so getFilePath Error InvalidFilePermission
ngfiles\invalidsong3.txt “ getSongFile
16 “C:\users\anexampleuser\so getFilePath Ok
ngfiles\song1.txt” getSongFile Song
getSong (seq
[BeepInstruction (Beep
(Frequency 660, TimeLength 100));
DelayInstruction (Delay
(TimeLength 150));
BeepInstruction (Beep
(Frequency 660, TimeLength 100));
DelayInstruction (Delay
(TimeLength 300)); ...])
17 “C:\users\anexampleuser\so getFilePath Error InvalidSong
ngfiles\invalidsong1.txt” getSongFile
getSong
18 “C:\users\anexampleuser\so getFilePath Error EmptySongFile
ngfiles\invalidsong4.txt” getSongFile
getSong
19 “C:\users\anexampleuser\so getFilePath 56.08
ngfiles\song1.txt” getSongFile
getSong
getPlayTimeInSec
onds
20 “C:\users\anexampleuser\so getFilePath 121.0
ngfiles\song2.txt” getSongFile
getSong
getPlayTimeInSec
onds

When trying to unit-test with I ran into issues with testing as my types were private. I was able to
slowly extract the values, but this didn’t seem like the right way to go. What I wanted to do was be
able to match on something like this:

Some Beep (Frequency 500, TimeLength 500)

However, as I don’t have access to the Beep, Frequency or TimeLength from my unit test it didn’t
appear I could do that, I believe I could have written some helper code in the modules, but I wasn’t
sure whether it was best practice to do so.

Simon Roadknight 87
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Property-bad testing
Property based testing is similar to unit testing, in that we are testing particular functions with
known inputs and expected outputs, however, unlike the unit-testing we have previously done, we
don’t have to define specific inputs for our tests, instead we can provide specification in the form of
properties of which our functions should satisfy, we can then test that property holds when running
a large number of randomly generate test cases.

Automated behavioural tests


As discussed, when writing our Gherkin specifications, Gherkin specifications are often used in a BDD
environment, putting a focus on feature behaviours, we are also able to test in a BDD style.

To carry out BDD style testing, there are frameworks available for F# and .NET as a whole such as
TickSpec, these allow us to the write the tests in the Given, When, Then format that we used for our
Gherkin specification. Allowing for us to use the specifications decided upon previously as the basis
for our testing.

T1 - Reflection
For the testing part of the logbook, I am happy with manual testing carried out, and my
understanding of what testing is, why it is used, and the different types of testing. I am happy with
the test suites I have come up with both the manual tests and the unit tests. Going forward I will
have the consideration of testing when writing my code, making it so that my code is easier to test
with the techniques that I have learned about.

Simon Roadknight 88
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

T4: Understanding and exploring the F# Koans exercises as a software


project (T4-2)

I have chosen to look at the F# Koans exercise project. It is not necessarily a project that has been
written and documented for other programmers to work on and contribute towards, a lot of the
documentation lends itself to how to use the program, which is a great place to start with any
project you want to contribute to, understanding how it works from the user’s perspective. On
project which are actively looking for contributors existing programming documentation can possibly
help the project in a few ways

1. Increase the quality of code that is being written, especially if the documentation includes defined style
guides.
2. Improve the quality of commit messages is there is a standardised style.
3. Reduce overhead of the same questions being asked
4. Allows people to onboard to the project quicker if there is documentation which explains any API’s the
project may contain.
5. Increase the likelihood of having people want to contribute to the project.

The F# Koans uses version control in the form of Git. There are other version control systems, but
fundamentally they are used to keep source code in a repository that can keep track of the changes
that developers make to the source code and keeping a history of previous versions. We can see that
from the F# Koans project, multiple people are able to contribute to the same project, be it
contributing code, fixing typing mistakes, adding documentation, or requestion a feature.

We will look at some of the commands that allow for multiple users to work on the same project.

Table 5 Git definitions

Term Discussion of Term


Clone One of the first things you may want to do when looking to contribute to
a project is to get hold of the source code on your local machine and run
the program. To get the content of the repository on your local machine
you can use the clone command (Git, n.d (a)). As a user of F# Koans I can
clone the project to practice my F#.
Commit Once you have cloned the repository and made some changes you are
able to record these changes by using the commit command (Git, n.d
(b))). When using the commit command, you can input a message to
document the changes. Commit will only record these changes locally. If I
decided I want to add a new topic to F# Koans, after I implement it I
might want to record the changes I have made, should I want to push it
later. This allows people to work on the project on their local machine,
making changes as they like without affecting others, allowing multiple
people can all work on the same project at once.
Pull You have been working on your branch for a while now and have realise
that you are behind the remote the remote repository, you are able to
use the pull command to fetch those changes and integrate them with
any changes you have made on your local branch. You can also use pull

Simon Roadknight 89
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

to integrate one repository with another. (Git, n.d (c)). I could have left it
some time since adding my new topic, and realised I am behind the
remote repository, I could pull the changes and have it integrated with
my local version of the F# Koans. If multiple people are working on a
project, someone could finish one part of the code, push it to the
repository, and another person could then pull that code, integrate it
with their own and use the newly pulled parts of the project should they
need to.
Push You are finally ready to share the work that you have put into
implementing that new feature, or fixing an issue, here is where you are
able to push your local repository to the remote repository (Atlassian,
n.d). I am now able to use push to send my changes to the remote
repository, where people can enjoy the new topic, I added. This allows
people to share the changes they have made, which allows for
collaboration.
Merge Is taking two branches and putting them together – Git has its own
algorithm for doing this, what it does is try to find the base commit
where both branches match, then merges on that if there are no
conflicts, if there are conflicts, the process can be done manually. This
could be the case if someone had the idea to add the same topic as me to
the F# Koans project with an identical name, there will be a merge
conflict. It is very inevitable that two people will eventually alter the
same piece of code, or a piece of code someone else relying on for their
commit, this can be managed either by Git itself if the problem is trivial,
or often by a senior member of staff should it be more complex
Branch Branch allows you to create a separate version of the project that differs
from the main repository. The use case for this can be for different
features that are being worked on, different teams, and different
versions. In a business there may be several features being developed at
once, each feature can have its own branch until it is ready to be merged,
this allows for collaboration on a smaller scale for features, without
interrupting the main branch constantly.
Pull request If you have made changes to a project and you don’t have write access,
you can submit a pull request to ask the maintainer of the Git repository
to review code they want to merge. If I wanted to upload my new F#
Koans to the default branch ‘fsharp’ I would have to submit a pull request
and have it reviewed. This is what enables open source without the
threat of any code being pushed to any project, the ability for
contributors to make pull requests to a project, this means all types of
people, from users who want improved documentation, to someone
adding a new feature, or fixing a bug they found. This along with the
other tools allows for a multi-person software development project to be
managed easier than it otherwise may be.

Simon Roadknight 90
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Explore and discuss the commit history of F# Koans

Looking through the commit history of the F# Koans project, I have discovered
‘commit 78aa77df3f48dfb986fa8973b91296205c739cf4’ titles initial rev of F# branch to be the first
major commit in the project. The change was committed by ChrisMarinos. Prior to this commit, the
git repository only had a few files and no code, the commit changed 18 files, with 10,454 additions
and no deletions. It added the initial functionality to the F# Koans as we see it today.

There was a new Koan added in ‘commit d7e0541c7a2f23a7f1f0e7addaf31695f9bf5878’, it


introduced a Koan which teaches about the difference between floats and ints. The changes were,
adding 18 new lines of code to the AboutLet.fs file, which includes the [<Koan>] attribute, and
binding x to the literal 20, and y to the literal 20.0 and calling GetType() on each one. Followed by
two assert statements, asserting x to be of the type int, and y to be of the type float. There was also
comments added surrounding the above. The commit was pushed by ChrisMarinos.

Issue #51 caught my eye as it seemed to span several issues including Issue #15, there was an issue
with NUnit and how ‘AssertEquality’ defines being equal. Issue #15 was from 2012, and Issue #51
which dealt with it was from 2017. The initial issue #15 was raised by ChrisMarinos, the fix/Issue #51
was raised by dsyme, the changed enforced that types are checks before allowing the Koan to be run
from what I can understand.

Pull request #94, made changes so that netcore 3.1 was targeted as it was the new LTS and removed
an older unsupported netcore target. This pull request was made by cartermp in 2020.

Simon Roadknight 91
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Reference List

Knight, A. (2017) BDD 101: Writing Good Gherkin. automationpanda [blog] 30 January. Available at:
https://automationpanda.com/2017/01/30/bdd-101-writing-good-gherkin/ [Accessed 22 May
2022].

Wlaschin, S. (2012) Type abbreviations. fsharpforfunandprofit [blog] 03 June. Available at:


https://fsharpforfunandprofit.com/posts/type-abbreviations/ [Accessed 22 May 2022].

Contributors to the Microsoft .NET documentation (2021) Console Applications. Available at:
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/functions/entry-point
[Accessed 23 May 2022].

Contributors to the Microsoft .NET documentation (2022) Regular Expression Language – Quick
Reference. Available at: https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-
expression-language-quick-reference [Accessed 23 May 2022].

How Music Works (n.d) Amplitude and Frequency. Available at :


https://www.howmusicworks.org/103/Sound-and-Music/Amplitude-and-Frequency [Accessed 21
May 2022].

Cmglee (2014) Animal Hearing Frequency Range. Available at:


https://en.wikipedia.org/wiki/Hearing_range#/media/File:Animal_hearing_frequency_range.svg
[Accessed 21 May 2022].

Contributors to the Microsoft .NET documentation (2021b) Discriminated Unions. Available at:
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions
[Accessed 21 May 2022].

Contributors to the Microsoft documentation (2021c) System Error Codes (0-499). Available at:
https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- [Accessed 21
May 2022].

Cooper, E. (2022a) The Structure of Programs: Some lenses to view software through [Part 5]. [pdf]
Available at:
https://moodle.bcu.ac.uk/pluginfile.php/8531492/mod_resource/content/2/PlanningProgramsWee
k10_1.pdf [Accessed 23 May 2022].

Simon Roadknight 92
21126080
CMP5361 Computer Mathematics and Declarative Programming UG2
Software Creation Tasks

Cooper, E. (2022b) The Structure of Programs: Some lenses to view software through [Part 5]. [pdf]
Available at:
https://moodle.bcu.ac.uk/pluginfile.php/8531492/mod_resource/content/2/PlanningProgramsWee
k10_1.pdf [Accessed 23 May 2022].

Contributors to the Microsoft .NET documentation (2021d) Results. Available at:


https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/results [Accessed 22 May
2022].

FSharp (n. d (a)) Result Module. Available at: https://fsharp.github.io/fsharp-core-


docs/reference/fsharp-core-resultmodule.html [Accessed 21 May 2022].

Microsoft (n. d) Console.Beep() Method. Available at: https://docs.microsoft.com/en-


us/dotnet/api/system.console.beep?view=net-6.0 [Accessed 21 May 2022].

FSharp (n. d (b)) Seq Module. Available at: https://fsharp.github.io/fsharp-core-


docs/reference/fsharp-collections-seqmodule.html [Accessed 21 May 2022].

Scott Wlaschin, Railway Oriented Programming fsharpforfunandprofit [blog] n.d. Available at:
https://fsharpforfunandprofit.com/rop/ [Accessed 22 May 2022].

Git (n. d (a)) git-clone. Available at: https://git-scm.com/docs/git-clone [Accessed 21 May 2022].

Git (n. d (b)) git-commit. Available at: https://git-scm.com/docs/git-commit [Accessed 21 May 2022].

Git (n. d (c)) git-pull. Available at: https://git-scm.com/docs/git-pull [Accessed 21 May 2022].

Atlassian (n. d) git push. Available at: https://www.atlassian.com/git/tutorials/syncing/git-push


[Accessed 21 May 2022]

Simon Roadknight 93
21126080

You might also like