Showing posts with label Haskell Modules. Show all posts
Showing posts with label Haskell Modules. Show all posts

Tuesday, April 9, 2013

HASKELL MODULES DATA HIDING

HASKELL MODULES DATA HIDING

REVISED: Monday, February 12, 2024




Haskell Data Hiding.

I. HASKELL DATA MODULES

You are reading "Haskell Modules Data Hiding", the second half of the "Haskell Data Modules" tutorial.


The two parts of this tutorial are a very brief outline of "Haskell Programming Tutorial 6 - Modules" by Sankha Mukherjee. 

A. SKELETON

STEP 1: We start off with the following file containing the vector functions:

module MyVectorsF where

(·) :: (Float, Float, Float) -> (Float, Float, Float) -> Float
(·) (x1, x2, x3)(y1, y2, y3) =  x1*y1+x2*y2+x3*y3

(×) :: (Float, Float, Float) -> (Float, Float, Float) -> (Float, Float, Float)
(×) (x1, x2, x3)(y1, y2, y3) =  (x2*y3-x3*y2, x3*y1-x1*y3, x1*y2-x2*y1)

1. Create a new data type.

data Vec = V3DCart Float Float Float
        deriving (Show)

We use the data keyword to define a new data type. The part before the = denotes the data type, which is Vec. The parts after the = are value constructors. They specify the different values that this data type can have. V3DCart is a value constructor that has three fields, parameters, which take floats. Value constructors are actually functions that return a value of a data type. The value constructor V3DCart returns a value of data type Vec. Adding deriving (Show) at the end makes our Vec data type part of the Show type class so Haskell knows how to get the string representation of our value and then Haskell prints that out to the terminal. Value constructors are just functions that take the fields as parameters and return a value of some data type as a result. Not exporting the value constructors of a data type makes them more abstract in such a way that we hide their implementation. Also, whoever uses our module can not pattern match against the value constructors.

2. Create accessor function.

make3DCV :: Float -> Float -> Float -> Vec
make3DCV a b c = V3DCart a b c

Type constructors can take types as parameters to produce new types. The a b c shown above are type parameters. Because there are type parameters involved, we call make3DCV a type constructor. Type parameters are useful because we can make different types with them depending on what kind of types we want contained in our data type. If our data type acts as some kind of box container, as does Vec, it is good to use type parameters. The parts after the = are value constructors. Value constructors specify the different values that make3DCV can have. V3DCart is a value constructor that has three fields, a b c type parameters.  Value constructors are actually functions that return a value of a data type. The value constructor V3DCart returns a value of data type Vec. 

3. Create a new type class.

class VectorC a where
        (·) :: a -> a -> Float
        (×) :: a -> a -> a

We use the classs keyword to define a new type class, which is VectorCType classes are like interfaces. We do not make data from type classes. The a after VectorC is the data type variable and it means that a will play the role of the data type that we will soon be making when we make an instance of VectorC. It does not have to be called a, it does not even have to be one letter, it just has to be a lowercase word. Then, we define several functions; e.g., (·) and (×). It is not mandatory to implement the function bodies themselves, we just have to specify the type declarations for the functions. 

4. Create instances of the new type class.

instance VectorC  Vec where
        (·) (V3DCart x1 x2 x3) (V3DCart y1 y2 y3) = x1*y1+x2*y2+x3*y3
        (×) (V3DCart x1 x2 x3) (V3DCart y1 y2 y3) = (V3DCart (x2*y3-x3*y2) (x3*y1-x1*y3) (x1*y2-x2*y1))

We make an instance of VectorC by using the instance keyword. So class is for defining new type classes and instance is for making our data types instances of type classes. When we were defining VectorC,  we wrote class VectorC a where and we said that a plays the role of whichever data type will be made an instance later on. We can see that clearly here, because when we are making an instance, we write instance VectorC  Vec where. We replace the a with the actual data type Vec.

In summary, first, we  create a new data type Vec. Second, we create accessor functions make3DCV. Third, we think about what the Vec data type can act like and create type classes VectorC, that can behave in that way. Fourth, the functions of our new data type Vec,  are made instances of that type class VectorC. Notice, the behavior of type classes is achieved by defining functions or just type declarations that we then implement. When we say that a data type is an instance of a type class, we mean that we can use the functions (·) and (×), that the type class defines with that data type.

STEP 2: We start off with the following file containing the main function:

import MyVectorsF

main = do
    print $ (2,4,5) · (0,1,0)
    print $ (2,4,5) × (0,1,0)

1. Call imported accessor functions.

    let a = make3DCV 2 4 5
    let b = make3DCV 0 2 0

2. Print new output.

    print $ a · b
    print $ a × b

B. FLESH ON BONES

1. SAVE

Save has two steps. "Save Step 1" saves the vector functions. "Save Step 2" saves the main function.

SAVE STEP 1

Use your editor to save  MyVectorsF.hs which is the final draft of vector dot product, and cross product, function signatures and definitions:

module MyVectorsF (make3DCV, VectorC(..)) where   -- Export accessor functions and everything in the class.

class VectorC a where   -- Creates a new "type class" VectorC, which takes any generic data type a.

    (·) :: a -> a -> Float    -- Generic function signatures which take any data type a.
    (×) :: a -> a -> a

data Vec = V3DCart Float Float Float   -- Creates a new "data type" Vec.
    deriving (Show)

make3DCV :: Float -> Float -> Float -> Vec   -- Accessor function returns data type Vec.

make3DCV a b c = V3DCart a b c

instance VectorC Vec where   -- Instances of the "type class" must agree with function signatures.

    (·) (V3DCart x1 x2 x3) (V3DCart y1 y2 y3) = x1*y1+x2*y2+x3*y3
    (×) (V3DCart x1 x2 x3) (V3DCart y1 y2 y3) = (V3DCart (x2*y3-x3*y2) (x3*y1-x1*y3) (x1*y2-x2*y1))

SAVE STEP 2

Use your editor to save MyVetorsM.hs which is the final draft of the main function:

import MyVectorsF

main = do

    let a = make3DCV 2 4 5   -- Calls imported accessor function.
    let b = make3DCV 0 2 0

    print $ a · b

    print $ a × b

2. LOAD

Load has two steps. "Load Step 1" loads the vector functions. "Load Step 2" loads the main function.

LOAD STEP 1

As shown below, load the above MyVectorsF.hs file into GHCi:

Prelude>  :load MyVectorsF
[1 of 1] Compiling MyVectorsF      ( MyVectorsF.hs, interpreted )
Ok, modules loaded: MyVectorsF.
Prelude>  

LOAD STEP 2

As shown below, load the above MyVectorsM.hs file into GHCi:

Prelude>  :load MyVectorsM
[1 of 2] Compiling MyVectorsF       ( MyVectorsF.hs, interpreted )
[2 of 2] Compiling Main             ( MyVectorsM.hs, interpreted )
Ok, modules loaded: Main, MyVectorsF.
Prelude>  

Now that we have loaded Main, we can run functions in GHCi that were defined in Main.

3. RUN

As shown below, run the function main, in GHCi:

Prelude>  main
8.0
V3DCart (-10.0) 0.0 4.0
Prelude>  

If we want to see the type of the accessor function make3DCV we can do the following:

Prelude>  :load MyVectorsF
Ok, modules loaded: MyVectorsF.
Prelude>

Prelude>  :type make3DCV
make3DCV :: Float -> Float -> Float -> MyVectorsF.Vec
Prelude>

It says, if you want a vector, give me three Floats. However, if you want to see the type of MyVectorsF.Vec watch what happens. It says I am not going to tell you what a vector is.

Prelude>  :type MyVectorsF2.Vec
<interactive>:1:1: Not in scope: data constructor `MyVectorsF2.Vec'
Prelude> 

The representation of MyVectorsF.Vec is hidden from you, the user. 

4. COMPILE 

As shown below, compile MyVectorsM.hs in GHC:

Prelude>  :! ghc --make "*MyVectorsM"
[1 of 2] Compiling MyVectorsF       ( MyVectorsF.hs, MyVectorsF.o )
[2 of 2] Compiling Main                  ( MyVectorsM.hs, MyVectorsM.o )
Linking MyVectorsM.exe ...
Prelude>  

The compile is shown so you know it compiles without error.

C. COMMENTS

Use the program shown above as an example. Rewrite the example and make it your own program.

Repeat 1. thru 3. above until your new program works the way you want it to work. Each time you make changes in your editor to your program, make sure you save the file and load it in GHCi.

II. CONCLUSION

In this tutorial, you have been introduced to the advantages of using "Haskell Modules Data Hiding."

III. REFERENCE

Haskell Programming by Sankha Mukherjee

Haskell Tutorial 6 - Modules




Monday, April 8, 2013

HASKELL MODULES NO DATA HIDING

HASKELL MODULES NO DATA HIDING

REVISED: Monday, February 12, 2024




Haskell Modules No Data Hiding.

I. HASKELL DATA MODULES

You are reading "Haskell Modules No Data Hiding",  the first half of the "Haskell Data Modules" tutorial.

"Haskell Modules Data Hiding"  is the second half.

The two parts of this tutorial are a very brief outline of "Haskell Programming Tutorial 6 - Modules" by Sankha Mukherjee.

A. SKELETON

We start off with the following file containing both the main function and the vector functions:

main = do
       print $ (2,4,5) · (0,1,0)
       print $ (2,4,5) × (0,1,0)

(·) :: (Float, Float, Float) -> (Float, Float, Float) -> Float
(·) (x1, x2, x3)(y1, y2, y3) =  x1*y1+x2*y2+x3*y3

(×) :: (Float, Float, Float) -> (Float, Float, Float) -> (Float, Float, Float)

(×) (x1, x2, x3)(y1, y2, y3) =  (x2*y3-x3*y2, x3*y1-x1*y3, x1*y2-x2*y1)

What happens if we need these vector functions in hundreds of other programs? One solution would be to copy past them into each of the other programs. 

However, what happens if we want to change a function; for example, changing from a three element tuple to a two element tuple? Changes might have to be made to hundreds of files that were copy pasted.

A good programming maintenance technique is to use modules.

One module for each group of related functions.

One module containing the main function.

B. FLESH ON BONES

1. SAVE

Save has two steps. "Save Step1" saves the module containing the vector functions. "Save Step 2" saves the main function.

SAVE STEP 1

Use your editor to save  MyVectorsF.hs the vector dot product, and vector cross product, function signatures and definitions. Notice both of these functions are tied to a particular data type; i.e., Float. Also notice each vector function assumes a "three element tuple". What do you do when you need the dot product or cross product of a vector which has less tuple elements or more tuple elements? That question will be answered in "Haskell Data Hiding".

module MyVectorsF where

(·) :: (Float, Float, Float) -> (Float, Float, Float) -> Float

(·) (x1, x2, x3)(y1, y2, y3) =  x1*y1+x2*y2+x3*y3

(×) :: (Float, Float, Float) -> (Float, Float, Float) -> (Float, Float, Float)

(×) (x1, x2, x3)(y1, y2, y3) =  (x2*y3-x3*y2, x3*y1-x1*y3, x1*y2-x2*y1)

SAVE STEP 2

Use your editor to save MyVetorsM.hs the main function. Notice by importing MyVectorsF you do not have to copy paste the vector functions into the main function.

import MyVectorsF

main = do
    print $ (2,4,5) · (0,1,0)
    print $ (2,4,5) × (0,1,0)

2. LOAD

Load has two steps. "Load Step 1" loads the vector functions. "Load Step 2" loads the main function.

LOAD STEP 1

As shown below, load the above MyVectorsF.hs file into GHCi:

Prelude>  :load MyVectorsF
[1 of 1] Compiling MyVectorsF      ( MyVectorsF.hs, interpreted )
Ok, modules loaded: MyVectorsF.
Prelude>  

LOAD STEP 2

As shown below, load the above MyVectorsM.hs file into GHCi:

Prelude>  :load MyVectorsM
[1 of 2] Compiling MyVectorsF       ( MyVectorsF.hs, interpreted )
[2 of 2] Compiling Main             ( MyVectorsM.hs, interpreted )
Ok, modules loaded: Main, MyVectorsF.
Prelude>  

Now that we have loaded Main, we can run functions in GHCi that were defined in Main.

3. RUN

As shown below, run the main function, in GHCi:

Prelude>  main
4.0
(-5.0,0.0,2.0)
Prelude>  

4. COMPILE 

As shown below, compile MyVectorsM.hs in GHC:

Prelude>  :! ghc --make "*MyVectorsM"
[1 of 2] Compiling MyVectorsF       ( MyVectorsF.hs, MyVectorsF.o )
[2 of 2] Compiling Main             ( MyVectorsM.hs, MyVectorsM.o )
Linking MyVectorsM.exe ...
Prelude>  

The compile is shown so you know it compiles without error.

C. COMMENTS

Use the program shown above as an example. Rewrite the example and make it your own program.

Repeat 1. thru 3. above until your new program works the way you want it to work. Each time you make changes in your editor to your program, make sure you save the file and load it in GHCi.

II. CONCLUSION

You have just received an introduced to "Haskell Modules No Data Hiding", the first half of the "Haskell Data Modules" tutorial.   "Haskell Modules Data Hiding" is the second half of "Haskell Data Modules".

III. REFERENCE

Haskell Programming by Sankha Mukherjee

Haskell Tutorial 6 - Modules



Tuesday, February 5, 2013

HASKELL MODULES

HASKELL MODULES



REVISED: Tuesday, October 22, 2024




Haskell Modules.

I. HASKELL MODULES

Modules are a software design technique that allows you to group together related code.

A. HASKELL MAIN MODULE

A Haskell program is a collection of modules, one of which, by convention, must be called Main and must export the value main.

Module names are alphanumeric and must begin with a capital letter. For most modules, the module name must match the file name which must also begin with a capital letter. If it does not, GHCi might not be able to find it.

An I/O action will be performed when we give a module name or file name a name of Main and then run that Main module name or file name. For example:

Prelude> :load Main
[1 of 1] Compiling Main ( main.hs, interpreted ), modules loaded: Main.
Prelude>

B. IMPORTING HASKELL MODULES

Modules can be imported into GHCi using the keyword import as shown below:

Prelude> import System.Environment
Prelude>

Prelude> import System.IO
Prelude>

Prelude> import Test.QuickCheck
Prelude>

Prelude> :show imports
import Prelude   -- always implicitly available
import System.Environment
import System.IO
import Test.QuickCheck
Prelude>

Modules can also be imported into GHCi using :module + as shown below:

Prelude> :module +Text.Printf 
Prelude>

Prelude> :show imports
import Prelude   -- implicit
import System.Environment
import System.IO
import Text.Printf
import Test.QuickCheck
Prelude>

After a module has been imported you can use :browse to browse all of its function type signatures. For example:

Prelude> :browse Text.Printf
data FieldFormat
  = FieldFormat {fmtWidth :: Maybe Int,
                 fmtPrecision :: Maybe Int,
                 fmtAdjust :: Maybe FormatAdjustment,
                 fmtSign :: Maybe FormatSign,
                 fmtAlternate :: Bool,
                 fmtModifiers :: String,
                 fmtChar :: Char}
type FieldFormatter = FieldFormat -> ShowS
data FormatAdjustment = LeftAdjust | ZeroPad
data FormatParse
  = FormatParse {fpModifiers :: String,
                 fpChar :: Char,
                 fpRest :: String}
data FormatSign = SignPlus | SignSpace
class HPrintfType t where
  Text.Printf.hspr :: Handle -> String -> [Text.Printf.UPrintf] -> t
class IsChar c where
  toChar :: c -> Char
  fromChar :: Char -> c
type ModifierParser = String -> FormatParse
class PrintfArg a where
  formatArg :: a -> FieldFormatter
  parseFormat :: a -> ModifierParser
class PrintfType t where
  Text.Printf.spr :: String -> [Text.Printf.UPrintf] -> t
errorBadArgument :: a
errorBadFormat :: Char -> a
errorMissingArgument :: a
errorShortFormat :: a
formatChar :: Char -> FieldFormatter
formatInt :: (Integral a, Bounded a) => a -> FieldFormatter
formatInteger :: Integer -> FieldFormatter
formatRealFloat :: RealFloat a => a -> FieldFormatter
formatString :: IsChar a => [a] -> FieldFormatter
hPrintf :: HPrintfType r => Handle -> String -> r
perror :: String -> a
printf :: PrintfType r => String -> r
vFmt :: Char -> FieldFormat -> FieldFormat
Prelude> 

C. UNLOADING HASKELL MODULES

Modules can also be unload from GHCi by using :module - as shown below:

Prelude> :module -System.IO
Prelude>

Prelude> :show imports
import Prelude   -- implicit
import System.Environment
import Test.QuickCheck
import Text.Printf
Prelude>

II. MODULE SKELETON

-- MODULE DECLARATION
module Main where
-- Main is the name of module declaration, always capitalize the module name's first letter.

-- IMPORTS
import Data.List ()
import System.IO ()

-- MAIN PROGRAM
main :: IO ()
main = putStrLn "Hello, world!"  -- Notice main starts with lowercase m

-- FUNCTIONS

Any import directives must appear in a group at the beginning of a module. They must appear after the module declaration shown above, but before all other code. We cannot, for example, scatter them throughout a source file.

III. WRITING YOUR FIRST HASKELL MODULE

If we want to do some serious programming, it is very likely that our program will increase in size. An overview of a lengthy file is a difficult task. Therefore, long projects are divided into parts, containing pieces of a program that belong together. These parts are called modules.

A Haskell module can be typed into a text file that you can edit. You can use any text editor with an understanding of code and indentation; e.g., Notepad++, to type a Haskell module or as previously mentioned Visual Studio Code.

First, "Copy Paste" the following module example into your text editor and "File Save As" Factorial.hs into your working directory.

-- My first Haskell module!
module Factorial where

factorial :: (Eq a, Num a) => a -> a
factorial 0 = 1                                  -- Base case.
factorial n = n * factorial (n-1)         -- Recursive case.

The factorial type signature shown above defines a generic type signature which creates a function that works for several types. The compiler will infer the actual types using type inference.

Second, load Factorial.hs into GHCi as follows:

Prelude> :load Factorial
[1 of 1] Compiling Factorial        ( Factorial.hs, interpreted )
Ok, modules loaded: Factorial.
Prelude>

Third, call the factorial function as follows:

Prelude> factorial 3     -- Notice that the f in factorial is lowercase!
6
it :: (Num a, Eq a) => a
Prelude>

Fourth, let's look at what happens when you execute factorial 3:

3 isn't 0, so we calculate the factorial of 2
     2 isn't 0, so we calculate the factorial of 1
          1 isn't 0, so we calculate the factorial of 0
               0 is 0, so we return 1.
          To complete the calculation for factorial 3, we multiply the current number, 1, by the   factorial of 0, which is 1, obtaining 1 = (1 × 1).
     To complete the calculation for factorial 3, we multiply the current number, 2, by the factorial of 1, which is 1, obtaining 2 = (2 × 1 × 1).
To complete the calculation for factorial 3, we multiply the current number, 3, by the factorial of 2, which is 2, obtaining 6 = (3 × 2 × 1 × 1).


Recursion is analogous to the concept of induction in mathematics.

We can see how the result of the recursive call is calculated first, then combined using multiplication.

Factorial takes a single non-negative integer as an argument, finds all the positive integers less than or equal to "n", and multiplies them all together.

The factorial of any number is just that number multiplied by the factorial of the number one less than it. There is one exception to this. If we ask for the factorial of 0, we do not want to multiply 0 by the factorial of -1. In fact, we just say the factorial of 0 is 1. We define it to be so. So, 0 is the base case for the recursion. When we get to 0 we can immediately say that the answer is 1, without using recursion.

3*2*1*1 = 6   -- The last 1 is the factorial of zero.

For modularization of our own definitions; put exactly one module in a single file. Give the same names to files and modules. Thus the example above is saved as Factorial.hs.

Since the module name must begin with a capital letter, the file name must also start with a capital letter.

A. SAVING TO ANY DIRECTORY OR FOLDER

You can save Factorial.hs anywhere you like, but if you save it somewhere other than your working directory you will need to change to that directory in GHCi:

Prelude> :cd dir
Prelude>

Where dir is the directory (or folder) in which you saved Factorial.hs.

To load a Haskell module into GHCi, use the import command:

Prelude> import Factorial 
Prelude>

Congratulations, you have just written your first Haskell module!

IV. COMBINING HASKELL FILES INTO PROJECTS

Each module must import all modules containing functions used within its definitions. Usually, some functions will be shared among all modules. In this case, each module must import the module where the function is defined. Modularization is beneficial in functional programming for several reasons. Program units get smaller and easier to manage. Control over connections among various modules is transparent for future use and error detecting. A number of files can be loaded in a convenient way by loading the project file only.

V. CONCLUSION

In this tutorial, you have been introduced to the importance of using Haskell modules when you code your programs.

VI. REFERENCES

Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.

Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.

Goerzen, J. & O'Sullivan, B. &  Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.

Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.

Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.

Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.