This example highlights the difference between imperative and functional programming. The imperative programming solution is based on an accumulator

Similar documents
PROGRAMMING IN HASKELL. Chapter 2 - First Steps

CS 11 Haskell track: lecture 1

Haskell: Lists. CS F331 Programming Languages CSCE A331 Programming Language Concepts Lecture Slides Friday, February 24, Glenn G.

Haske k ll An introduction to Functional functional programming using Haskell Purely Lazy Example: QuickSort in Java Example: QuickSort in Haskell

An introduction introduction to functional functional programming programming using usin Haskell

Shell CSCE 314 TAMU. Haskell Functions

Introduction. chapter Functions

CS 360: Programming Languages Lecture 10: Introduction to Haskell

CSCE 314 TAMU Fall CSCE 314: Programming Languages Dr. Flemming Andersen. Haskell Basics

CSCE 314 TAMU Fall CSCE 314: Programming Languages Dr. Flemming Andersen. Haskell Functions

Lecture 19: Functions, Types and Data Structures in Haskell

Programming with Math and Logic

6.001 Notes: Section 8.1

CS 320: Concepts of Programming Languages

CSCE 314 Programming Languages

n n Try tutorial on front page to get started! n spring13/ n Stack Overflow!

COP4020 Programming Languages. Functional Programming Prof. Robert van Engelen

Haskell Overview II (2A) Young Won Lim 8/9/16

Programming Languages Third Edition

CS 360: Programming Languages Lecture 12: More Haskell

Solution sheet 1. Introduction. Exercise 1 - Types of values. Exercise 2 - Constructors

Haskell Introduction Lists Other Structures Data Structures. Haskell Introduction. Mark Snyder

Intro to Haskell Notes: Part 5

Advanced Topics in Programming Languages Lecture 2 - Introduction to Haskell

1. true / false By a compiler we mean a program that translates to code that will run natively on some machine.

6.001 Notes: Section 6.1

Harvard School of Engineering and Applied Sciences CS 152: Programming Languages

Type Systems, Type Inference, and Polymorphism

CS 440: Programming Languages and Translators, Spring 2019 Mon

Programming Paradigms

Chapter 11 :: Functional Languages

CSC312 Principles of Programming Languages : Functional Programming Language. Copyright 2006 The McGraw-Hill Companies, Inc.

G Programming Languages - Fall 2012

Maciej Sobieraj. Lecture 1

Data Types The ML Type System

Lecture 05 I/O statements Printf, Scanf Simple statements, Compound statements

Functional Programming Languages (FPL)

Programming Languages Third Edition. Chapter 9 Control I Expressions and Statements

COS 320. Compiling Techniques

Recursion and Induction: Haskell; Primitive Data Types; Writing Function Definitions

Lambda Calculus see notes on Lambda Calculus

1.3. Conditional expressions To express case distinctions like

Polymorphic lambda calculus Princ. of Progr. Languages (and Extended ) The University of Birmingham. c Uday Reddy

Typed Racket: Racket with Static Types

PROGRAMMING IN HASKELL. Chapter 2 - First Steps

CSc 372. Comparative Programming Languages. 8 : Haskell Function Examples. Department of Computer Science University of Arizona

Lists. Michael P. Fourman. February 2, 2010

Scheme Tutorial. Introduction. The Structure of Scheme Programs. Syntax

1007 Imperative Programming Part II

SML A F unctional Functional Language Language Lecture 19

Logical Methods in... using Haskell Getting Started

Our Strategy for Learning Fortran 90

Handout 9: Imperative Programs and State

Full file at

SOFTWARE ENGINEERING DESIGN I

CPS 506 Comparative Programming Languages. Programming Language Paradigm

COP4020 Programming Assignment 1 - Spring 2011

Introduction to Haskell

Haskell 98 in short! CPSC 449 Principles of Programming Languages

CSc 372 Comparative Programming Languages

Semantics of programming languages

Haskell Scripts. Yan Huang

Control Structures. Lecture 4 COP 3014 Fall September 18, 2017

It is better to have 100 functions operate one one data structure, than 10 functions on 10 data structures. A. Perlis

A general introduction to Functional Programming using Haskell

Thoughts on Assignment 4 Haskell: Flow of Control

CPS122 Lecture: From Python to Java last revised January 4, Objectives:

A Gentle Introduction to Haskell 98

Lecture Programming in C++ PART 1. By Assistant Professor Dr. Ali Kattan

Programming in C++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

Topic 1: Introduction

Processadors de Llenguatge II. Functional Paradigm. Pratt A.7 Robert Harper s SML tutorial (Sec II)

Slide 1 CS 170 Java Programming 1 The Switch Duration: 00:00:46 Advance mode: Auto

CIS 194: Homework 4. Due Wednesday, February 18, What is a Number?

The Typed Racket Guide

Chapter 15. Functional Programming Languages

Control Flow. COMS W1007 Introduction to Computer Science. Christopher Conway 3 June 2003

Control Structures in Java if-else and switch

Lecture 2: SML Basics

CSCI-GA Scripting Languages

Functional Programming

Functional Programming in Haskell Prof. Madhavan Mukund and S. P. Suresh Chennai Mathematical Institute

Control Structures in Java if-else and switch

Summer 2017 Discussion 10: July 25, Introduction. 2 Primitives and Define

3. Functional Programming. Oscar Nierstrasz

A First Look at ML. Chapter Five Modern Programming Languages, 2nd ed. 1

Defining Functions. CSc 372. Comparative Programming Languages. 5 : Haskell Function Definitions. Department of Computer Science University of Arizona

Functional Programming

Expressions and Casting. Data Manipulation. Simple Program 11/5/2013

Topics Covered Thus Far CMSC 330: Organization of Programming Languages

CS 6110 S14 Lecture 1 Introduction 24 January 2014

Functional Languages. Hwansoo Han

Haskell Types, Classes, and Functions, Currying, and Polymorphism

Tail Calls. CMSC 330: Organization of Programming Languages. Tail Recursion. Tail Recursion (cont d) Names and Binding. Tail Recursion (cont d)

Functional Programming. Big Picture. Design of Programming Languages

Haskell Overview II (2A) Young Won Lim 8/23/16

Expressions and Casting

CS152: Programming Languages. Lecture 11 STLC Extensions and Related Topics. Dan Grossman Spring 2011

Types and Type Inference

Introduction to the Lambda Calculus

Transcription:

1

2

This example highlights the difference between imperative and functional programming. The imperative programming solution is based on an accumulator (total) and a counter (i); it works by assigning them successive values until the computation is completed. The programmer has to detail, step by step, how the computer is to proceed to obtain the result. Even in such a small example, there are several chances for error and the meaning of the program is not obvious. In contrast the Haskell version consists of applying a function (sum) over a range of integers [1..n]. The meaning is obvious (once one knows the language) and the opportunity for errors are few. 3

4

You will need access to Haskell to do the exercises and homework for the course. If you have a personal computer, Haskell is available for download for Windows, the Mac and Linux; it is also installed on the server: login.cpp.edu, at the prompt use the command GHCi. All those implement the Glasgow Haskell Compiler, which includes an interactive loop where you can type Haskell expressions to be evaluated. You can also type Haskell code in a text file (usually with the.hs extension) and load them. The most effective way to learn Haskell is to use both: write functions in a text file using an editor, load the file (aka module) at the Haskell prompt and then type expressions interactively to test the code. To submit homework for this course I ll ask you to email me the.hs file with your code. 5

When you start Haskell it loads a default environment, called Prelude, which includes a library of commonly used functions. You can find a good description of the Haskell prelude from http://users.dimi.uniud.it/~marco.comini/students/documentation/haskell-prelude- Tour-A4.pdf. At the Prelude prompt you can enter Haskell expressions to be evaluated when you hit the Enter key. Go ahead, start Haskell and enter the expressions above and ensure you get the same result. Most of the operators above should be familiar, with the possible exception of ++, which is the concatenation operator. 6

The simplest haskell declaration is an equation. Using your favorite text editor, go ahead and create a file named digits.hs and enter the following content: one = 1 two = 2 etc. until nine = 9 Remember that Haskell is case sensitive and that the first letter of the names of values must be lowercase. Save the file and at the haskell command prompt type: :load digits.hs If you make a mistake and Haskell fails to load the module you can fix it in the text editor and use the reload command (:r) to retry. Once the file has been successfully loaded it will be considered as the default Main module. At this point the prompt will change to *Main> and you can type commands such as: one two one+two Go ahead and try it. 7

Haskell differentiates between two kinds of entities: Values and Types. Values are the result of expressions. They are first class entities in the language that means that they can be subject of calculations, they can be passed as arguments to functions and can be returned from functions. In Haskell, as in all the common programming languages, numbers, characters, strings, Booleans are values, but, less usual, Functions are also considered as values, hence the term Functional Programming Language. Values can be given a name, and the name of values must start with a lowercase letter and include letters, digits and the single quote character. Haskell is a strongly, statically typed language. That means that all values have a type and the types are calculated statically by the compiler (i.e. without executing the program). Types can be polymorphic, that means that a value can be considered of multiple types at the same time. However all types are part of a hierarchy and each value has exactly one Principal Type. For example the value 1 can be considered as an integer or a real number, but its Principal Type is Num (i.e. it s a numeric type and can be applied numeric operations). We use type variables to express polymorphic types. Type variables start with a lowercase letter to distinguish them from type names. For example, as we ll see later the type expression [a] denotes a list of any type. 8

Haskell supports 6 basic types. Five of them, Bool, Char, Int, Float and Double are the usual types common to most programming languages. The unusual one is Integer which adjusts the number of digits to accommodate any integral number, for example Haskell will happily calculate 1234 to the 567 power (1234^567) and give the result with its 1753 digits; try it. One notable absent is the String type. A string in Haskell is nothing more than a list of characters as we shall see. 9

As mentioned above Haskell is a strongly typed language. Unlike Java and other languages of the C / Algol family explicit typing is largely optional in Haskell. It can be useful, however to specify the type of a value, as a comment or a debugging feature. To specify the type of an element you follow it by a double colon and the type name. Go ahead and try the examples above. In the top group the type of the values is stated and Haskell validates the type before printing the result. In the bottom group Haskell is asked for the type of two values and replies with a type expression. 10

The List type is arguably the second most important and widely used type in Haskell, the first being Function type. A list is a sequence of elements of the same type, separated by comas and surrounded by square brackets. List can be of arbitrary length. The first example above is a list of 5 integers, the second is a list of 3 Bool, the third is a list of 4 Float and the fourth a list of 6 Char; note that the Haskell does not make any difference between a list of Char written using the bracket notation and a list of Char written using the String notation; the String notation is obviously easier to read. The last example shows that lists must be homogeneous: mixing types in a list causes an error. Lists can be of infinite length, for example the list [1..] represents all the integers in order. If you try it at the Prelude prompt you will have to interrupt Haskell otherwise it will continue until it runs out of resources. The type of a list is denoted by the type of the elements of the list surrounded by square brackets. For example [Char] denotes a list of characters and [Integer] denotes a list of integers. As we ve seen before [a] denotes a list of any type, and [[a]] denotes a list of lists of any type. The empty list, i.e. the list that contains no element is denoted [] and is of type [a]. The lists as written above are a convenient notation, however fundamentally lists are constructed using the cons operator, written using a single colon. For example the first list above is the result of the expression: 1 : (2 : (3 : (4 : (5 : [])))) or since : is right-associative 1:2:3:4:5:[] Lists can also be concatenated using the operator ++: [1, 2, 3]++[4] = [1, 2, 3, 4] 11

A Tuple is a finite sequence of elements separated by comas and enclosed in parenthesis. Unlike lists, the elements of a Tuple can be of different types. The type of a tuple includes the type of it s elements. The tuples ('a', 1) and (3, 'c') are not of the same type; try typing :t ('a', 1) and :t (3, 'c'). Tuples can contain Lists and Lists can contain Tuples at any arbitrary length, however, all Tuples within a list must be of the same type as shown in the last example. Note also that while two lists of different length will be of the same type as long as their elements are of the same type (e.g. [1..] and [1] are both of type list of integers, even though the former is infinite while the later has only one element). Tuples can also be polymorphic, for example (a, b) denotes the type of a pair of values any type, while (a, a) denotes a pair where both elements have the same type. 12

As in Object Oriented languages, Classes are the basis for polymorphism, but the comparison does not go much deeper than that. In Haskell a class does not hold any data. This diagram is taken from the Haskell 2010 Report. It indicates the class containment, aka inheritance. Warning: the arrows point in the reverse direction when compared to UML class diagrams. At the top are the EQ and the Show classes. The Eq class denotes types that have an equality operation defined upon them (i.e. the operators equal == and not equal /=). This class includes all the types except Functions and related types such as IO and types that include functions (e.g. lists of functions). The exclusion is based on theoretical grounds: in general the equality between functions is un-decidable. The Show class includes all the types that can be printed, in the standard prelude, the types included in the Show class are the same as the types included in the Eq class, it is however possible to define custom types that are in one class and not the other. Similarly the Num class includes the same types as the Real class, but Complex numbers, for example, belong to the Num class but not the Real class because they are not orderable. For a complete descriptions of the Haskell classes please refer to the Haskell report http://www.haskell.org/onlinereport/index.html. 13

19

Haskell is a Functional Programming Language, so it s no wonder functions are the most important type in Haskell. Functions in Haskell are similar to mathematical functions: they associate elements of a set (the domain) to elements of another set (the range). The type of a function is denoted by an arrow that separates the type of the parameter on the left and the type of the result on the right. The most general type for a function would be a -> b, but no defined functions has this type. Challenge yourself, try to define a function of type a -> b; no, not the identity function, its type is a -> a. 20

Functions in Haskell are considered as values. As such they are first class entities and can be manipulated. - Included in other types like Lists and Tuples. - Passed as argument to other functions. - Returned as result from other functions. In fact the syntax for declaring a figurative constant and a nullary function (a function that takes no argument) is the same. (Remember the definition of one, two, three etc in Lecture 1). 21

To declare a function you simply state one or more equations that specify how the returned value of the function is inferred from the arguments. The function first here is defined by a single equation, multi-equation definitions use a method called pattern matching which we ll see later. When declaring a function it is a good practice to first declare its type. This is not a requirement of the language: the compiler will be fully able to infer the type in all but the rarest cases. Explicitly declaring the type serves three important purposes: - First it forces you to think through your intention - Second it serves as documentation for whoever will read the code, including you - Third it allows the compiler to warn you if your actual function code does not match your intended type, most of the times this will be the indication of a bug. As an example take the definition of the function first above. If you accidentally code first (x, y) = y (as I actually did) the compiler will tell you something is wrong. Go ahead and try it, copy the two lines above to a.hs file and replace the = x by =y and try to load the module, see what happens. 22

To ensure that the required functions are available, it is often necessary to restrict a parameter to types that belong to one or several classes. For example, in order for a function to use the + operation on its argument, that argument needs to be of a type on which the plus operation is defined, in the standard prelude, that would be the Num class. The definition of type constraints is called a context. Multiple constraints can be specified in a single context by surrounding them with parenthesis and separating them with comas. For example the context (Eq a, Num a) =>. states that the type a be in both the Eq and Num classes. 23

There is no special symbol to apply a function to its argument, simply write the function name followed by the argument, separated by white space. For example take the digits.hs file created last time and add the declaration of the inc function from the preceding slide. You can then do the interaction above. It has been argued that because function application is the most common operation in Haskell it has the simplest syntactical form. Function application associates to the left so inc (inc 5) returns 7 while inc inc 5 would return an error since the function inc cannot be applied to itself. Note that while inc cannot be applied to itself due to its type, the function ident defined below can, do you see why? ident x = x. 24

It can be argued that no Haskell function has more than one parameter. In the form 1 above the parameter is a pair of numbers. This resembles the syntax of most common programming languages, but the underpinning semantics is different. The preferred form in Haskell is form 2: Currying. In this form the function add' takes a single argument (the x) and returns a function (call it addx) that takes a single argument (the y) and adds x to it. This is reflected by the type of add' as shown above. And because the -> operator of types associates to the right so the type of add' can also be written a -> a -> a. Currying is more than just a syntactic gimmick; it has profound implications. For example one can now define the function inc as add' 1. Go ahead a add the two definitions above to the file digits.hs, reload it and ask for the type of add' 1 Notice that it is the same as the type of inc. Indeed the equation that defines inc can be replaced by: inc = add' 1 Go head do it and see that is works exactly as inc. Notice that you no longer have to state the parameter of inc: add' 1 is already a function that takes a number and adds 1 to it; inc is just another name for it. 25

The infix notation is the traditional and most intuitive way of writing expressions, as in the usual arithmetic formulae. However, infix operators, do not lend themselves to partial application. To overcome this difficulty Haskell allows infix operators to be used in a prefix notation (the usual function notation). Go ahead and try (+) 1 4 at the prompt and GHCi will happily give the result 5. While the expression 1 + is syntactically incorrect, the expression (+) 1 is correct and denotes a function that adds 1 to its argument (i.e. the now familiar inc function). Haskell goes one step further and allows us to move the 1 inside the parenthesis and hence specify it as either the left or right side operand of +; this process is called sectioning. In the case of the addition, which is commutative, this does not make much of a difference, but for division it does. Namely the functions (1+) and (+1) give the same result while the functions (1/) and (/1) will give different results. Incidentally the minus sign being a prefix as well as an infix the type of the expression (-1) is considered to be Num and not a function. 26

Conversely to sectioning, functions of two parameters can also be used as infix operators, this is sometime convenient to make code more readable. For example the integer division is defined as a function named div. It can be used as a function, as in the first example, or infix as in the second example by surrounding it with back quotes. 27

Haskell implements lazy evaluation, that means that expressions are only evaluated to the extent necessary to produce a result. This allows the use of infinite structures like a list of all the natural integers, noted [1..]. If you typ e [1..] at the GHCi prompt it will respond with a list that goes on and on until forcibly terminated (e.g. Ctrl-c). This again may sound like a gimmick, but it is useful in many circumstances as we ll see during the course. One of the consequences of lazy evaluation is that functions in Haskell are non-strict, that means that a function may return a value even when one or more of its arguments is a non-terminating expression. For example take the infinite sequence of numbers [1..], or the (infinitely) recursive function bot: bot = bot In most languages using either as the parameter to a function will cause the function to run forever. In Haskell the function will only diverge (i.e. non-terminate) if the parameter is completely evaluated. For example, we ve seen the function first :: (a, b) -> a first (x, y) = x First will happily return 1 when applied to (1, bot) but will diverge when applied to (bot, 1). The standard prelude includes a function head that returns the first element of a list and last that returns the last one. The expression first [1..] will return 1 without any problem while last [1..] will send GHCi thinking for a very looooong time. For another example take the function infinity defined as infinity = 1/0 This is a perfectly valid definition and it will not hurt the program until it s value is needed, then an error will occur. It is often necessary to reason about non-termination in program; for that we use the symbol bottom:. Evaluating the function bot above will fail to terminate and is said to return bottom. Finally, it is sometimes possible for a program to detect divergence, such as taking the head of an empty list or dividing by 0. In those cases it is better to generate an error than to let the program run forever. The function error is useful in those cases. It takes a string as a parameter and all good implementations of Haskell will print this message as a diagnostic help. For example when the function head that returns the first element of a list is applied to an empty list, there is no reasonable value for it to return since it does not even know the type of the expected element, such function can be defined as: head :: [a] -> a head [] = error "Taking the head of an empty list" head (x:_) = x 28

Patterns play an important role in functional languages, and in particular for function definitions. Oftentimes the definition of a function involves multiple cases. Recursive functions, for example, typically require the definition of a base case and a recursive case. There are several ways to express those in Haskell, but the easiest to read and hence the most commonly used is pattern matching. The function len in the example above returns the number of elements of a list. It consists of a base case (the empty list) and a recursive case. Patterns are not first class entities; there is a limited set of patterns and they can be used in a limited set of syntactic contexts. Function definition is one of them; we ll see others later in the course. Patterns contain a mixture of structural information, constants and variables. Because all instances could be bound to different values, the same variable name cannot be repeated in a pattern. Patterns are matched against values, for example the pattern on the left side of an equation will be matched against the actual argument(s) when the function is called. Attempting to match a pattern can have one of three results: it may fail in which case the next pattern in the set is attempted; it may succeed, returning a binding for each variable in the pattern; or it may diverge that means non terminate or generate an error. Pattern matching proceeds from top to bottom and left to right. When matching patterns, Haskell only evaluates as much of the expression as necessary to decide success or failure (but may still generate an error in the process). For example the function tk above (it is the equivalent of the standard prelude function take) returns the first n elements of a list, or the entire list if it has fewer elements. Try to apply it as follows and you may be surprised by the behavior. tk 0, [bot] tk 1, [bot] tk 1, [1, bot] 29

The most common types of patterns are: - Literal Matching: in the example above [] matches only the empty list. - Named Variable matching: a named variable matched against any value always succeeds and binds the variable to the value. In the examples above the variable xs is bound to the tail of a non-empty list. - Wildcard or unnamed variable matching always succeeds and no binding is done. In the example, the _ in _:xs is a wildcard and matches the first element of a non-empty list. - Constructor patterns, for example cons (:) is a list constructor, hence the pattern above matches any list built from this constructor, i.e. it has a head and a (possibly empty) tail. (We ll see other constructors when we study custom data types later in the course). - List Patterns have the form [p 1, p 2, p n ] where each p i is itself a pattern. Such a pattern matches a list of exactly n elements and where all the p i patterns successfully match the corresponding element. - Tuple Patterns are similar to List Patterns, but are denoted by parenthesis instead of brackets and apply to tuples instead of lists. Note that the (controversial) n+k pattern (where n is a variable and k is a positive integer literal) has been removed from the language. It used to matche a value v if v >= k, resulting in the binding of n to v - k, it fails otherwise; I mention it here for completeness=: it has been removed from the 2010 standard. 30

Patterns can be useful also in calculations, such is the purpose of the case expression. In fact function definition can be expressed using case. For example the function take from the previous slide can also be defined as follows: take' :: Int -> [a] -> [a] take' n xs = case (n, xs) of { (0, _) -> []; (_, []) -> []; (n, (x:xs)) -> x: take' (n-1) xs} The usual conditional expression is also part of the language. Note that it can be defined using the case statement. Also the else clause is not optional, thus eliminating the need for brackets and the issue of dangling elses. 31

A pattern can analyze the structure and type of an expression, but not its semantics. For example a relationship between the two elements of a pair (such as them being equal or in a certain order) cannot be expressed by a pattern, e.g. the pattern (x, x) is invalid (in part because equality is not always decidable). Guard expressions can be used to overcome this difficulty. A guard is a Boolean expression (and not a pattern). In the first example above x is a pattern that will match any value, the guards will decide which branch to take. You may have noticed that in the definition of tk before nothing would prevent applying take to a negative number and the result would be to return the entire string. Indeed nothing in the pattern can enforce n to be >= 0. Guard solves this problem as in the definition above. 32

Haskell offers an alternative to curly braces and semicolons: layout rules. Most of the time we use program layout to convey scope information (as when we indent the statements inside an if block in Java) without any help form the compiler and hence no guarantee that the layout indeed reflects the program structure. Some IDE alleviate the problem by automatically laying out the program, but this is not fool proof. In Haskell, if you don t use braces then the compiler will enforce that all the lines that belong to the same lexical context start exactly on the same column. So the definition of tk from the previous slide can be laid-out as shown here, without any braces and it is indeed easier to read. Layout is a very nice but sometime treacherous feature; one column off left or right cause weird syntax error messages, but nevertheless it is a largely used and widely appreciated feature. 33

Lambda expressions is an alternate way to define functions. The notation for lambda expression derives from the Lambda Calculus. The lambda calculus was introduced by Alonzo Church in the 1930s as part of an investigation into the foundations of mathematics. The Lambda Calculus is the inspiration and the mathematical foundation for functional programming languages such as Haskell. Lambda expressions are nameless functions. They can be used wherever a named function can be used. They are convenient to reason about programs and give formal meaning to other language constructs, but they are mostly useful when defining functions that return a function; they also avoid having to give a name and defining elsewhere a function that is used only once, thus making the program easier to write and to read. 34

Let expressions are useful whenever a nested set of bindings is required. In this, admittedly artificial, example the names a, b, c and d refer to values bound outside the let, and the programmer has found it convenient to define a value y and a function f to simplify the expression returned by the let, namely (c+a*b)/(a*b) + (d+a*b)/(a*b). Let expressions are particularly useful when a complex expression contains the same sub-expression repeated several times, it allows a name to be given locally to the sub-expression and avoids repetition. Where clauses are useful to create a set of bindings over several guarded equations. In the example above the expression x*x would have to be repeated for each guard and possibly in the returned values, and it would not be obvious to the reader that x is always squared. The where clause allows a name to be given to the value x*x and makes the program easier to read and maintain. 35