Exploring Languages
with Interpreters
and Functional Programming
Chapter 5

H. Conrad Cunningham

045April 2022

Copyright (C) 2016, 2017, 2018, 2022, H. Conrad Cunningham
Professor of Computer and Information Science
University of Mississippi
214 Weir Hall
P.O. Box 1848
University, MS 38677
(662) 915-7396 (dept. office)

Browser Advisory: The HTML version of this textbook requires a browser that supports the display of MathML. A good choice as of April 2022 is a recent version of Firefox from Mozilla.

5 Types

5.1 Chapter Introduction

The goals of this chapter are to:

5.2 Type System Concepts

The term type tends to be used in many different ways in programming languages. What is a type?

The chapter on object-based paradigms discusses the concept of type in the context of object-oriented languages. This chapter first examines the concept more generally and then examines Haskell’s builtin types.

5.2.1 Types and subtypes

Conceptually, a type is a set of values (i.e., possible states or objects) and a set of operations defined on the values in that set.

Similarly, a type S is (a behavioral) subtype of type T if the set of values of type S is a “subset” of the values in set T an set of operations of type S is a “superset” of the operations of type T. That is, we can safely substitute elements of subtype S for elements of type T because S’s operations behave the “same” as T’s operations.

This is known as the Liskov Substitution Principle [11,19].

Consider a type representing all furniture and a type representing all chairs. In general, we consider the set of chairs to be a subset of the set of furniture. A chair should have all the general characteristics of furniture, but it may have additional characteristics specific to chairs.

If we can perform an operation on furniture in general, we should be able to perform the same operation on a chair under the same circumstances and get the same result. Of course, there may be additional operations we can perform on chairs that are not applicable to furniture in general.

Thus the type of all chairs is a subtype of the type of all furniture according to the Liskov Substitution Principle.

5.2.2 Constants, variables, and expressions

Now consider the types of the basic program elements.

A constant has whatever types it is defined to have in the context in which it is used. For example, the constant symbol 1 might represent an integer, a real number, a complex number, a single bit, etc., depending upon the context.

A variable has whatever types its value has in a particular context and at a particular time during execution. The type may be constrained by a declaration of the variable.

An expression has whatever types its evaluation yields based on the types of the variables, constants, and operations from which it is constructed.

5.2.3 Static and dynamic

In a statically typed language, the types of a variable or expression can be determined from the program source code and checked at “compile time” (i.e., during the syntactic and semantic processing in the front-end of a language processor). Such languages may require at least some of the types of variables or expressions to be declared explicitly, while others may be inferred implicitly from the context.

Java, Scala, and Haskell are examples of statically typed languages.

In a dynamically typed language, the specific types of a variable or expression cannot be determined at “compile time” but can be checked at runtime.

Lisp, Python, JavaScript, and Lua are examples of dynamically typed languages.

Of course, most languages use a mixture of static and dynamic typing. For example, Java objects defined within an inheritance hierarchy must be bound dynamically to the appropriate operations at runtime. Also Java objects declared of type Object (the root class of all user-defined classes) often require explicit runtime checks or coercions.

5.2.4 Nominal and structural

In a language with nominal typing, the type of value is based on the type name assigned when the value is created. Two values have the same type if they have the same type name. A type S is a subtype of type T only if S is explicitly declared to be a subtype of T.

For example, Java is primarily a nominally typed language. It assigns types to an object based on the name of the class from which the object is instantiated and the superclasses extended and interfaces implemented by that class.

However, Java does not guarantee that subtypes satisfy the Liskov Substitution Principle. For example, a subclass might not implement an operation in a manner that is compatible with the superclass. (The behavior of subclass objects are this different from the behavior of superclass objects.) Ensuring that Java subclasses preserve the Substitution Principle is considered good programming practice in most circumstances.

In a language with structural typing, the type of a value is based on the structure of the value. Two values have the same type if they have the “same” structure; that is, they have the same public data attributes and operations and these are themselves of compatible types.

In structurally typed languages, a type S is a subtype of type T only if S has all the public data values and operations of type T and the data values and operations are themselves of compatible types. Subtype S may have additional data values and operations not in T.

Haskell is primarily a structurally typed language.

5.2.5 Polymorphic operations

Polymorphism refers to the property of having “many shapes”. In programming languages, we are primarily interested in how polymorphic function names (or operator symbols) are associated with implementations of the functions (or operations).

In general, two primary kinds of polymorphism exist in programming languages:

  1. Ad hoc polymorphism, in which the same function name (or operator symbol) can denote different implementations depending upon how it is used in an expression. That is, the implementation invoked depends upon the types of function’s arguments and return value.

    There are two subkinds of ad hoc polymorphism.

    1. Overloading refers to ad hoc polymorphism in which the language’s compiler or interpreter determines the appropriate implementation to invoke using information from the context. In statically typed languages, overloaded names and symbols can usually be bound to the intended implementation at compile time based on the declared types of the entities. They exhibit early binding.

      Consider the language Java. It overloads a few operator symbols, such as using the + symbol for both addition of numbers and concatenation of strings. Java also overloads calls of functions defined with the same name but different signatures (patterns of parameter types and return value). Java does not support user-defined operator overloading; C++ does.

      Haskell’s type class mechanism, which we examine in a later chapter, implements overloading polymorphism in Haskell. There are similar mechanisms in other languages such as Scala and Rust.

    2. Subtyping (also known as subtype polymorphism or inclusion polymorphism) refers to ad hoc polymorphism in which the appropriate implementation is determined by searching a hierarchy of types. The function may be defined in a supertype and redefined (overridden) in subtypes. Beginning with the actual types of the data involved, the program searches up the type hierarchy to find the appropriate implementation to invoke. This usually occurs at runtime, so this exhibits late binding.

      The object-oriented programming community often refers to inheritance-based subtype polymorphism as simply polymorphism. This the polymorphism associated with the class structure in Java.

      Haskell does not support subtyping. Its type classes do support class extension, which enables one class to inherit the properties of another. However, Haskell’s classes are not types.

  2. Parametric polymorphism, in which the same implementation can be used for many different types. In most cases, the function (or class) implementation is stated in terms of one or more type parameters. In statically typed languages, this binding can usually be done at compile time (i.e., exhibiting early binding).

    The object-oriented programming (e.g., Java) community often calls this type of polymorphism generics or generic programming.

    The functional programming (e.g., Haskell) community often calls this simply polymorphism.

5.2.6 Polymorphic variables

A polymorphic variable is a variable that can “hold” values of different types during program execution.

For example, a variable in a dynamically typed language (e.g., Python) is polymorphic. It can potentially “hold” any value. The variable takes on the type of whatever value it “holds” at a particular point during execution.

Also, a variable in a nominally and statically typed, object-oriented language (e.g., Java) is polymorphic. It can “hold” a value its declared type or of any of the subtypes of that type. The variable is declared with a static type; its value has a dynamic type.

A variable that is a parameter of a (parametrically) polymorphic function is polymorphic. It may be bound to different types on different calls of the function.

5.3 Basic Haskell Types

The type system is an important part of Haskell; the compiler or interpreter uses the type information to detect errors in expressions and function definitions. To each expression Haskell assigns a type that describes the kind of value represented by the expression.

Haskell has both built-in types (defined in the language or its standard libraries) and facilities for defining new types. In the following we discuss the primary built-in types. As we have seen, a Haskell type name begins with a capital letter.

In this textbook, we sometimes refer to the types Int, Float, Double, Bool, and Char as being primitive because they likely have direct support in the host processor’s hardware.

5.3.1 Integers: Int and Integer

The Int data type is usually an integer data type supported directly by the host processor (e.g., 32- or 64-bits on most current processors), but it is guaranteed to have the range of at least a 30-bit, two’s complement integer.

The type Integer is an unbounded precision integer type. Unlike Int, host processors usually do not support this type directly. The Haskell library or runtime system typically supports this type in software.

Haskell integers support the usual literal formats (i.e., constants) and typical operations:

  • Infix binary operators such as + (addition), - (subtraction), * (multiplication), and ^ (exponentiation)

  • Infix binary comparison operators such as == (equality of values), /= (inequality of values), <, <=, >, and >=

  • Unary operator - (negation)

For integer division, Haskell provides two-argument functions:

  • div such that div m n returns the integral quotient truncated toward negative infinity from dividing m by n

  • quot such that quot m n returns the integral quotient truncated toward 0 from dividing m bem n

  • mod (i.e., modulo) and rem (i.e., remainder) such that

        (div m n) * n + mod m n == m
        (quot m n)* n + rem m n == m 

To make these definitions more concrete, consider the following examples. Note that the result of mod has the same sign as the divisor and rem has the same sign as the dividend.

    div  7 3  == 2 
    quot 7 3  == 2 
    mod  7 3  == 1          -- same sign as divisor
    rem  7 3  == 1          -- same sign as dividend
    div  (-7) (-3) == 2 
    quot (-7) (-3) == 2
    mod  (-7) (-3) == (-1)  -- same sign as divisor
    rem  (-7) (-3) == (-1)  -- same sign as dividend
    div  (-7) 3 == (-3) 
    quot (-7) 3 == (-2)
    mod  (-7) 3 == 2        -- same sign as divisor
    rem  (-7) 3 == (-1)     -- same sign as dividend
    div  7 (-3) == (-3) 
    quot 7 (-3) == (-2) 
    mod  7 (-3) == (-2)     -- same sign as divisor
    rem  7 (-3) == 1        -- same sign as dividend

Haskell also provides the useful two-argument functions min and max, which return the minimum and maximum of the two arguments, respectively.

Two-arguments functions such as div, rem, min, and max can be applied in infix form by including the function name between backticks as shown below:

    5 `div` 3   -- yields 1 
    5 `rem` 3   -- yields 2
    5 `min` 3   -- yields 3 
    5 `max` 3   -- yields 5

5.3.2 Floating point numbers: Float and Double

The Float and Double data types are usually the single and double precision floating point numbers supported directly by the host processor.

Haskell floating point literals must include a decimal point; they may be signed or in scientific notation: 3.14159, 2.0, -2.0, 1.0e4, 5.0e-2, -5.0e-2.

Haskell supports the usual operations on floating point numbers. Division is denoted by / as usual.

In addition, Haskell supports the following for converting floating point numbers to and from integers:

  • floor returns the largest integer less than its floating point argument.

  • ceiling returns the smallest integer greater than its floating point argument

  • truncate returns its argumentas an integer truncated toward 0.

  • round returns it argument as an integer rounded away from 0.

  • fromIntegral returns its integer argument as a floating point number in a context where Double or Float is required. It can also return its Integer argument as an Int or vice versa.

5.3.3 Booleans: Bool

The Bool (i.e., Boolean) data type is usually supported directly by the host processor as one or more contiguous bits.

The Bool literals are True and False. Note that these begin with capital letters.

Haskell supports Boolean operations such as && (and), || (or), and not (logical negation).

Functions can match against patterns using the Boolean constants. For example, we could define a function myAnd as follows:

    myAnd :: Bool -> Bool -> Bool
    myAnd True  b = b
    myAnd False _ = False

Above the pattern _ is a wildcard that matches any value but does not bind a value that can be used on the right-hand-side of the definition.

The expressions in Haskell if conditions and guards on function definitions must be Bool-valued expressions. They can include calls to functions that return Bool values.

5.3.4 Characters: Char

The Char data type is usually supported directly by the host processor by one or more contiguous bytes.

Haskell uses Unicode for its character data type. Haskell supports character literals enclosed in single quotes—including both the graphic characters (e.g., ’a’, ’0’, and Z) and special codes entered following the escape character backslash \ (e.g., '\n' for newline, '\t' for horizontal tab, and '\\' for backslash itself).

In addition, a backslash character \ followed by a number generates the corresponding Unicode character code. If the first character following the backslash is o, then the number is in octal representation; if followed by x, then in hexadecimal notation; and otherwise in decimal notation.

For example, the exclamation point character can be represented in any of the following ways: !, '\33', '\o41', '\x21'

5.3.5 Functions: t1 -> t2

If t1 and t2 are types then t1 -> t2 is the type of a function that takes an argument of type t1 and returns a result of type t2.

Function and variable names begin with lowercase letters optionally followed by a sequences of characters each of which is a letter, a digit, an apostrophe (') (sometimes pronounced “prime”), or an underscore (_).

Haskell functions are first-class objects. They can be arguments or results of other functions or be components of data structures. Multi-argument functions are curried-–that is, treated as if they take their arguments one at a time.

For example, consider the integer addition operation (+). (Surrounding the binary operator symbol with parentheses refers to the corresponding function.) In mathematics, we normally consider addition as an operation that takes a pair of integers and yields an integer result, which would have the type signature

    (+) :: (Int,Int) -> Int

In Haskell, we give the addition operation the type

    (+) :: Int -> (Int -> Int) 

or just

    (+) :: Int -> Int -> Int

since Haskell binds -> from the right.

Thus (+) is a one argument function that takes some Int argument and returns a function of type Int -> Int. Hence, the expression ((+) 5) denotes a function that takes one argument and returns that argument plus 5.

We sometimes speak of this (+) operation as being partially applied (i.e., to one argument instead of two).

This process of replacing a structured argument by a sequence of simpler ones is called currying, named after American logician Haskell B. Curry who first described it.

The Haskell library, called the standard prelude (or just Prelude), contains a wide range of predefined functions including the usual arithmetic, relational, and Boolean operations. Some of these operations are predefined as infix operations.

5.3.6 Tuples: (t1,t2,...,tn)

If t1, t2, \cdots, tn are types, where n is finite and n >= 2, then is a type consisting of n-tuples where the various components have the type given for that position.

Each element in a tuple may have different types. The number of elements in a tuple is fixed.

Examples of tuple values with their types include the following:

    ('a',1) :: (Char,Int)
    (0.0,0.0,0.0) :: (Double,Double,Double) 
    (('a',False),(3,4)) :: ((Char, Bool), (Int, Int))

We can also define a type synonym using the type declaration and the use the synonym in further declarations as follows:

    type Complex = (Float,Float)
    makeComplex :: Float -> Float -> Complex
    makeComplex r i = (r,i)`

A type synonym does not define a new type, but it introduces an alias for an existing type. We can use Complex in declarations, but it has the same effect as using (Float,Float) expect that Complex provides better documentation of the intent.

5.3.7 Lists: [t]

The primary built-in data structure in Haskell is the list, a sequence of values. All the elements in a list must have the same type. Thus we declare lists with notation such as [t] to denote a list of zero or more elements of type t.

A list literal is a comma-separated sequence of values enclosed between [ and ]. For example, [] is an empty list and [1,2,3] is a list of the first three positive integers in increasing order.

We will look at programming with lists in a later chapter.

5.3.8 Strings: String

In Haskell, a string is just a list of characters. Thus Haskell defines the data type String as a type synonym :

    type String = [Char]

We examine lists and strings in a later chapter, but, because we use strings in a few examples in this subsection, let’s consider them briefly.

A String literal is a sequence of zero or more characters enclosed in double quotes, for example, "Haskell programming".

Strings can contain any graphic character or any special character given as escape code sequence (using backslash). The special escape code \& is used to separate any character sequences that are otherwise ambiguous.

For example, the string literal "Hotty\nToddy!\n" is a string that has two newline characters embedded.

Also the string literal "\12\&3" represents the two-element list ['\12','3'].

The function show returns its argument converted to a String.

Because strings are represented as lists, all of the Prelude functions for manipulating lists also apply to strings. We look at manipulating lists and strings in later chapters of this textbook.

5.3.9 Advanced Types

In later chapters, we examine other important Haskell type concepts such as user-defined algebraic data types and type classes.

5.4 What Next?

In this chapter (5), we examined general type systems concepts and explored Haskell’s builtin types.

For a similar presentation of the types in the Python 3 language, see reference [5].

In Chapters 6 and 7, we examine methods for developing Haskell programs using abstraction. We explore use of top-down stepwise refinement, modular design, and other methods in the context of Haskell.

5.5 Exercises

For each of the following exercises, develop and test a Haskell function or set of functions.

  1. Develop a Haskell function sumSqBig that takes three Double arguments and yields the sum of the squares of the two larger numbers.

    For example, (sumSqBig 2.0 1.0 3.0) yields 13.0.

  2. Develop a Haskell function prodSqSmall that takes three Double arguments and yields the product of the squares of the two smaller numbers.

    For example, (prodSqSmall 2.0 4.0 3.0) yields 36.0.

  3. Develop a Haskell function xor that takes two Booleans and yields the “exclusive-or” of the two values. An exclusive-or operation yields True when exactly one of its arguments is True and yields False otherwise.

  4. Develop a Haskell Boolean function implies that takes two Booleans p and q and yields the Boolean result p \Rightarrow q (i.e., logical implication). That is, if p is True and q is False, then the result is False; otherwise, the result is True.

    Note: This function is sometimes called nand.

  5. Develop a Haskell Boolean function div23n5 that takes an Int and yields True if and only if the integer is divisible by 2 or divisible by 3, but is not divisible by 5.

    For example, (div23n5 4), (div23n5 6), and (div23n5 9) all yield True and (div23n5 5), (div23n5 7), (div23n5 10), (div23n5 15), (div23n5 30) all yield False.

  6. Develop a Haskell function notDiv such that notDiv n d yields True if and only if integer n is not divisible by d.

    For example, (notDiv 10 5) yields False and (notDiv 11 5) yields True.

  7. Develop a Haskell function ccArea that takes the diameters of two concentric circles (i.e., with the same center point) as Double values and yields the area of the space between the circles. That is, compute the area of the larger circle minus the area of the smaller circle. (Hint: Haskell has a builtin constant pi.)

    For example, (ccArea 2.0 4.0) yields approximately 9.42477796.

  8. Develop a Haskell function mult that takes two natural numbers (i.e., nonnegative integers in Int) and yields their product. The function must not use the multiplication (*) or division (div) operators. (Hint: Multiplication can be done by repeated addition.)

  9. Develop a Haskell function addTax that takes two Double values such that addTax c p yield c with a sales tax of p percent added.

    For example, (addTax 2.0 9.0) yields 2.18.

    Also develop a function subTax that is the inverse of addTax. That is, (subTax (addTax c p) p) yields c.

    For example, (subTax 2.18 9.0) = 2.0.

  10. The time of day can be represented by a tuple (hours,minutes,m) where hours and minutes are Int values with 1 <= hours <= 12 and 0 <= minutes <= 59, and where m is either the string value "AM" or "PM".

    Develop a Boolean Haskell function comesBefore that takes two time-of-day tuples and determines whether the first is an earlier time than the second.

  11. A day on the calendar (usual Gregorian calendar [25] used in the USA) can be represented as a tuple with three Int values (month,day,year) where the year is a positive integer, 1 <= month <= 12, and 1 <= day <= days_in_month. Here days_in_month is the number of days in the the given month (i.e., 28, 29, 30, or 31) for the given year.

    Develop a Boolean Haskell function validDate d that takes a date tuple d and yields True if and only if d represents a valid date.

    For example, validDate (8,20,2018) and validDate (2,29,2016) yield True and validDate (2,29,2017) and validDate (0,0,0) yield False.

    Note: The Gregorian calendar [25] was introduced by Pope Gregory of the Roman Catholic Church in October 1582. It replaced the Julian calendar system, which had been instituted in the Roman Empire by Julius Caesar in 46 BC. The goal of the change was to align the calendar year with the astronomical year.

    Some countries adopted the Gregorian calendar at that time. Other countries adopted it later. Some countries may never have adopted it officially.

    However, the Gregorian calendar system became the common calendar used worldwide for most civil matters. The proleptic Gregorian calendar [26] extends the calendar backward in time from 1582. The year 1 BC becomes year 0, 2 BC becomes year -1, etc. The proleptic Gregorian calendar underlies the ISO 8601 standard used for dates and times in software systems [27].

  12. Develop a Haskell function roman that takes an Int) in the range from 0 to 3999 (inclusive) and yields the corresponding Roman numeral [28] as a string (using capital letters). The function should halt with an appropriate error messages if the argument is below or above the range. Roman numerals use the symbols shown in Table 5.1 and are combined by addition or subtraction of symbols.

    Table 5.1: Decimal equivalents of Roman numerals.
    Roman == Decimal
    I 1
    V 5
    X 10
    L 50
    C 100
    D 500
    M 1000

    For the purposes of this exercise, we represent the Roman numeral for 0 as the empty string. The Roman numerals for integers 1-20 are I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVII, XIX, and XX. Integers 40, 90, 400, and 900 are XL, XC, CD, and CM.

  13. Develop a Haskell function

        minf :: (Int -> Int) -> Int

    that takes a function g and yields the smallest integer m such that 0 <= m <= 10000000 and g m == 0. It should throw an error if there is no such integer.

5.6 Acknowledgements

In Summer 2016, I adapted and revised the discussion Surveying the Basic Types from chapter 5 of my Notes on Functional Programming with Haskell [4]. In 2017, I incorporated the discussion into Section 2.4 in Chapter 2 Basic Haskell Functional Programming of my 2017 Haskell-based programming languages textbook.

In Spring and Summer 2018, I divided the previous Basic Haskell Functional Programming chapter into four chapters in the 2018 version of the textbook, now titled Exploring Languages with Interpreters and Functional Programming [6]. Previous sections 2.1-2.3 became the basis for new Chapter 4, First Haskell Programs; previous Section 2.4 became Section 5.3 in the new Chapter 5, Types (this chapter); and previous sections 2.5-2.7 were reorganized into new Chapter 6, Procedural Abstraction, and Chapter 7, Data Abstraction.

In Spring 2018, I wrote the general Type System Concepts section as a part of a chapter that discusses the type system of Python 3 [5] to support my use of Python in graduate CSci 658 (Software Language Engineering) course.

In Summer 2018, I revised the section to become Section 5.2 in Chapter 5 of the Fall 2018 version of ELIFP [6]. I also moved the “Kinds of Polymorphism” discussion from the 2017 List Programming chapter to the new subsection “Polymorphic Operations”. This textbook draft supported my Haskell-based offering of the core course CSci 450 (Organization of Programming Languages).

The type concepts discussion draws ideas from various sources:

I retired from the full-time faculty in May 2019. As one of my post-retirement projects, I am continuing work on this textbook. In January 2022, I began refining the existing content, integrating additional separately developed materials, reformatting the document (e.g., using CSS), constructing a bibliography (e.g., using citeproc), and improving the build workflow and use of Pandoc.

In 2022, I also added some discussion on the functions div, quot, mod, rem, fromIntegral, and show because of their usefulness in the exercises in this and later chapters.

I maintain this chapter as text in Pandoc’s dialect of Markdown using embedded LaTeX markup for the mathematical formulas and then translate the document to HTML, PDF, and other forms as needed.

5.7 Terms and Concepts

Type, subtype, Liskov Substitution Principle, types of constants, variables, and expressions, static vs. dynamic types, nominal vs. structural types, polymorphic operations (ad hoc, overloading, subtyping, parametric/generic), early vs. late binding, compile time vs. runtime, polymorphic variables, basic Haskell types (Int, Integer, Bool, Char, functions, tuples, lists, String), type aliases, library (Prelude) functions, proleptic Gregorian calendar system, Roman numerals.

5.8 References

[1]
Richard Bird. 1998. Introduction to functional programming using Haskell (Second ed.). Prentice Hall, Englewood Cliffs, New Jersey, USA.
[2]
Kathryn Heninger Britton, R. Alan Parker, and David L. Parnas. 1981. A procedure for designing abstract interfaces for device interface modules. In Proceedings of the 5th international conference on software engineering, IEEE, San Diego, California, USA, 195–204.
[3]
Timothy Budd. 2000. Understanding object-oriented programming with Java (Updated ed.). Addison-Wesley, Boston, Massachusetts, USA.
[4]
H. Conrad Cunningham. 2014. Notes on functional programming with Haskell. University of Mississippi, Department of Computer and Information Science, University, Mississippi, USA. Retrieved from https://john.cs.olemiss.edu/~hcc/docs/Notes_FP_Haskell/Notes_on_Functional_Programming_with_Haskell.pdf
[5]
H. Conrad Cunningham. 2018. Python 3 reflexive metaprogramming. University of Mississippi, Department of Computer and Information Science, University, Mississippi, USA. Retrieved from https://john.cs.olemiss.edu/~hcc/csci658/notes/PythonMetaprogramming/Py3RefMeta.html
[6]
H. Conrad Cunningham. 2022. Exploring programming languages with interpreters and functional programming (ELIFP). University of Mississippi, Department of Computer and Information Science, University, Mississippi, USA. Retrieved from https://john.cs.olemiss.edu/~hcc/docs/ELIFP/ELIFP.pdf
[7]
Cay S. Horstmann. 1995. Mastering object-oriented design in C++. Wiley, Indianapolis, Indiana, USA.
[8]
Cay S. Horstmann and Gary Cornell. 1999. Core Java 1.2: Volume IFundamentals. Prentice Hall, Englewood Cliffs, New Jersey, USA.
[9]
Paul Hudak. 1989. Conception, evolution, and application of functional programming languages. ACM Computing Surveys 21, 3 (1989), 359–411.
[10]
Roberto Ierusalimschy. 2013. Programming in Lua (Third ed.). Lua.org, Pontifical Catholic University of Rio de Janeiro (PUC-Rio), Brazil.
[11]
Barbara Liskov. 1987. Keynote address—Data abstraction and hierarchy. In Proceedings on object-oriented programming systems, languages, and applications (OOPSLA ’87): addendum, ACM, Orlando, Florida, USA, 17–34.
[12]
Bertrand Meyer. 1997. Object-oriented program construction (Second ed.). Prentice Hall, Englewood Cliffs, New Jersey, USA.
[13]
Martin Odersky, Lex Spoon, and Bill Venners. 2008. Programming in Scala (First ed.). Artima, Inc., Walnut Creek, California, USA.
[14]
David L. Parnas. 1972. On the criteria to be used in decomposing systems into modules. Communications of the ACM 15, 12 (December 1972), 1053–1058.
[15]
David L. Parnas. 1976. On the design and development of program families. IEEE Transactions on Software Engineering SE-2, 1 (1976), 1–9.
[16]
Michael L. Scott. 2015. Programming language pragmatics (Third ed.). Morgan Kaufmann, Waltham, Massachusetts, USA.
[17]
Robert W. Sebesta. 1993. Concepts of programming languages (Second ed.). Benjamin/Cummings, Boston, Massachusetts, USA.
[18]
Simon Thompson. 1996. Haskell: The craft of programming (First ed.). Addison-Wesley, Boston, Massachusetts, USA.
[19]
Wikpedia: The Free Encyclopedia. 2022. Liskov substitution principle. Retrieved from https://en.wikipedia.org/wiki/Liskov_substitution_principle
[20]
Wikpedia: The Free Encyclopedia. 2022. Polymorphism (computer science). Retrieved from https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
[21]
Wikpedia: The Free Encyclopedia. 2022. Function overloading. Retrieved from https://en.wikipedia.org/wiki/Function_overloading
[22]
Wikpedia: The Free Encyclopedia. 2022. Ad hoc polymorphism. Retrieved from https://en.wikipedia.org/wiki/Ad_hoc_polymorphism
[23]
Wikpedia: The Free Encyclopedia. 2022. Parametric polymophism. Retrieved from https://en.wikipedia.org/wiki/Parametric_polymorphism
[24]
Wikpedia: The Free Encyclopedia. 2022. Subtyping. Retrieved from https://en.wikipedia.org/wiki/Subtyping
[25]
Wikpedia: The Free Encyclopedia. 2022. Gregorian calendar. Retrieved from https://en.wikipedia.org/wiki/Gregorian_calendar
[26]
Wikpedia: The Free Encyclopedia. 2022. Proleptic Gregorian calendar. Retrieved from https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar
[27]
Wikpedia: The Free Encyclopedia. 2022. ISO 8601. Retrieved from https://en.wikipedia.org/wiki/ISO_8601
[28]
Wikpedia: The Free Encyclopedia. 2022. Roman numerals. Retrieved from https://en.wikipedia.org/wiki/Roman_numerals