CSci 450/503: Programming Languages

Using Data Abstraction
Rational Numbers

H. Conrad Cunningham

25 September 2017

Acknowledgements: These slides accompany sections 2.6 “Using Data Abstraction” section 2.7 “Modular Design and Programming” from Chapter 2 “Basic Haskell Functional Programming” of “Introduction to Functional Programming Using Haskell”.

Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of September 2017 is a recent version of Firefox from Mozilla.

Code: The Haskell modules for the Rational Arithmetic case study are in files RationalCore.hs, Rational.hs, and RationalDeferGCD.hs.

Using Data Abstraction

Lecture Goals

Module

Goals of Modular Design

  1. Enable programmers to understand the system by focusing on one module at a time (comprehensibility)

  2. Shorten development time by minimizing required communication among groups (independent development)

  3. Make system flexible by limiting number of modules affected by significant changes (changeability)

Information-Hiding Module

Module Interface

Interface:

Abstract Interface for Module

Abstract interface:

Information hiding with abstract interfaces supports software reuse

Criteria for Good Interfaces (1)

Cohesion:
Fit all operations together to support single, coherent purpose
Consistency:
Provide internally consistent set of operations (naming, arguments, return, behavior) – avoid surprises!
Simplicity:
Avoid needless features
No redundancy:
Avoid offering same service in multiple ways

Criteria for Good Interfaces (2)

Atomicity:
Do not combine several operations if needed individually – each primitive, not decomposable into others
Completeness:
Include all primitive operations that make sense for abstraction
Reusability:
Make general for use beyond initial context
Robustness:
Keep interface stable even if implementation changes

Criteria for Good Interfaces (3)

Convenience:
Provide additional operations for user convenience – after careful study

Haskell Module

Rational Numbers

Rational numbers 𝚡𝚢\frac{\texttt{x}}{\texttt{y}} where

Rational Number Data Abstraction

Rational Arithmetic Operations

Assuming data abstraction (Rat, makeRat, numer, denom)

    negRat :: Rat -> Rat
    negRat x = makeRat (- numer x) (denom x)

    addRat, subRat, mulRat, divRat :: Rat -> Rat -> Rat 
    addRat x y = makeRat (numer x * denom y + numer y * denom x) 
                         (denom x * denom y)   -- x + y
    subRat x y = makeRat (numer x * denom y - numer y * denom x) 
                         (denom x * denom y)   -- x - y
    mulRat x y = makeRat (numer x * numer y)
                         (denom x * denom y)   -- x * y
    divRat x y = makeRat (numer x * denom y)
                         (denom x * numer y)   -- x / y

    eqRat :: Rat -> Rat -> Bool 
    eqRat x y = (numer x) * (denom y) == (numer y) * (denom x)

Representing Rational Numbers

Aside: Invariants

Invariants assert what must hold for an “object” to be valid

Invariant:
Logical assertion that always holds for every “object” created by public constructors and manipulated only by public operations
Interface invariant:
Invariant stated in terms of public features and abstract properties of “object”
Implementation (representation) invariant:
Detailed invariant giving required relationships among internal features of implementation of “object”

Implementing Rationals (1)

First implement the constructor

    makeRat :: Int -> Int -> Rat
    makeRat x 0 = error ( "Cannot construct a rational number "
                           ++ showRat (x,0) ++ "\n" ) 
    makeRat 0 _ = (0,1) 
    makeRat x y = (x' `div` d, y' `div` d) 
        where x' = (signum' y) * x 
              y' = abs' y 
              d  = gcd' x' y'

Implementing Rationals (2)

    signum' :: Int -> Int -- similar to signum in Prelude 
    signum' n | n == 0 =  0 
              | n > 0  =  1 
              | n < 0  = -1 

    abs' :: Int -> Int    -- similar to abs in Prelude
    abs' n | n >= 0 =  n
           | n <  0 = -n

    gcd' :: Int -> Int -> Int  -- greatest common divisor, gcd in Prelude
    gcd' x y = gcd'' (abs' x) (abs' y) 
             where gcd'' x 0 = x 
                   gcd'' x y = gcd'' y (x `rem` y)

    showRat :: Rat -> String 
    showRat x = show (numer x) ++ "/" ++ show (denom x) 

Implementing Rationals (3)

Now implement the selectors

    numer, denom :: Rat -> Int
    numer (x,_) = x
    denom (_,y) = y

Modularization

Package has groups of features

  1. public rational arithmetic functions: negRat, addRat, subRat, mulRat, divRat, eqRat

  2. public type Rat, constructor makeRat, selectors numer and denom, string converter showRat

  3. private utility functions used by second group, but just reimplementations of Prelude functions

Module RationalCore

Module Rational (1)

Module Rational (2)

Modularization Critique

Alternative Data Representation

Module RationalDeferGCD (1)

    module RationalDeferGCD
      (Rat, makeRat, numer, denom, showRat)
    where

    type Rat = (Int,Int)

    makeRat :: Int -> Int -> Rat
    makeRat x 0 = error ( "Cannot construct a rational number "
                           ++ showRat (x,0) ++ "\n" ) 
    makeRat 0 y = (0,1) 
    makeRat x y = (x,y)

    numer :: Rat -> Int
    numer (x,y) = x' `div` d
        where x' = (signum' y) * x 
              y' = abs' y 
              d  = gcd' x' y'

    denom :: Rat -> Int
    denom (x,y) = y' `div` d
        where x' = (signum' y) * x 
              y' = abs' y 
              d  = gcd' x' y'

Module RationalDeferGCD (2)

    showRat :: Rat -> String
    showRat x = show (numer x) ++ "/" ++ show (denom x)

Module Dependency Diagram

Interface Invariant

Interface invariant for abstract module “RationalRep” – Haskell modules RationalCore and RationalDeferGCD

For all integers x and nonzero integers y,

    numer (makeRat x y) == x' 
    denom (makeRat x y) == y' 

where y' > 0, x' and y' are relatively prime, 𝚡𝚢=𝚡’𝚢’\frac{\texttt{x}}{\texttt{y}} = \frac{\texttt{x'}}{\texttt{y'}}
and if x' = 0, then y' = 1.

Implementation Invariants (1)

RationalCore

For all integers x and nonzero integers y,

    makeRat x y == (x',y')

where y' > 0, x' and y' are relatively prime, 𝚡𝚢=𝚡’𝚢’\frac{\texttt{x}}{\texttt{y}} = \frac{\texttt{x'}}{\texttt{y'}}
and if x' = 0, then y' = 1.

Implementation Invariants (2)

RationalDeferGCD

For all integers x and nonzero integers y,

    makeRat x y == (x,y)

Other Alternative Representations

Haskell Module System

Key Ideas

Code

The Haskell modules for the Rational Arithmetic case study are in files: