© Stefania Loredana Nita and Marius Mihailescu 2019
Stefania Loredana Nita and Marius MihailescuHaskell Quick Syntax Referencehttps://doi.org/10.1007/978-1-4842-4507-1_2

2. Static Typing

Stefania Loredana Nita1  and Marius Mihailescu1
(1)
Bucharest, Romania
 

In Haskell, the type system is quite detailed. From a theoretical point of view, it comes from typed Lambda calculus, introduced in 1930 by Alfonso Church,1,2 where the types are automatically deducted from the way the objects are processed. Such programming languages are statically typed. More precisely, the processing is enforced by and based on the rules of a mathematical type system.

A type system is a set of rules used in a programming language to organize, build, and handle the types that will be accepted in the language. These rules focus on some important aspects, such as the following:
  • Defining new types

  • Associating types with different language constructs

  • Focusing on type equivalence, which is important to determine when different types are the same

  • Verifying type compatibility, which is useful to check whether the value of a specific type is correctly used in a given processing context

  • Deducing the type of the language when it is not declared, which can be done by applying rules for synthesizing the type of a construct from the types of its components

As you already know, Haskell is a fully static, scoped language, and the top-level variables have static scope. In other words, the whole program module will contain their definition. This provides referential transparency if no side effect will rise. This is an example of a top-level expression:
variable = expression

In this expression, variable has expression as its value.

Currying and Uncurrying

Currying represents the process of transforming a function that has multiple arguments in a tuple with the same type as the arguments into a function that will take just a single argument and will return another function that will accept further arguments, one by one.

The following expression:
g :: x -> (y  -> z)  -- The above expression can be written as g :: x -> y -> z
represents the curried form of the following:
h :: (x, y) -> z
You can convert these two types in any direction with the help of the prelude functions curry and uncurry .
g = curry h
h = uncurry g
If you take a closer look, both forms are equally expressive. They will hold the following:
g x h = h (x,y)

In Haskell, all functions are considered curried. That is, all functions in Haskell are able to take just one argument. In the examples, you will see that this is mostly hidden in the notation.

For example, the first expression defined next will attach the variable plus to a curried function. A curried function is a functional closure. The second expression declared will add factorial to a recursive function, which will compute n!.
plus = a -> ( b -> a + b)
plus :: Int -> Int -> Int
factorial = n -> if n==1 then 1 else n*factorial(n-1)
factorial :: Int -> Int
Observe that the expression produces two values: the value that the expression has and the type that the value has. The type of the function is called signature. For example, the value of plus is a function of a domain in which int represents the range. The type of the expression does not need to be explicitly declared; it is automatically deduced based on the types and the expression’s components. Type deduction involves the verification of the type as a natural task or subtask. As a general conclusion, an n-ary curried function can be defined as p1 p2 ... pn -> expression. The function plus can be declared as follows:
plus = a b -> a + b
plus :: int -> int -> int
This is the short notation of the same thing:
plus a b = a + b
The functions can be used in infix and prefix forms. For example, the application (+) a b is equivalent to the more familiar a + b, and mod a b can be written a 'mod' b. (op) corresponds to the prefixed form of an infix binary operator, while 'op' corresponds to the infixed form of a prefix binary operator.
comp m n = ( a -> m(n a))
comp :: (b -> c) -> (d -> e) -> d -> e
ff = ( a -> a*a) `comp` ( a -> a+a)
ff :: Integer -> Integer
Output:
ff 2
16

In the previous example, the types that can be found in the signature of the composition function comp are not defined as constants. They represent generic types, which are represented by type variables in the format identifier. As you can observe, the function declared, comp, is polymorphic. This means that the arguments the function can take are of any type that obey its signature.

Scoping Variables

The scoping variables represent an extension of Haskell’s type system, which allows free type variables to be reused in the scope of a function. Consider the following function3 as an example:
mkpair1 :: forall a b. a -> b -> (a,b)
mkpair1 aa bb = (ida aa, bb)
    where
      ida :: a -> a -- This refers to a in the function's type signature
      ida = id
mkpair2 :: forall a b. a -> b -> (a,b)
mkpair2 aa bb = (ida aa, bb)
    where
      ida :: b -> b -- Illegal, because refers to b in type signature
      ida = id
mkpair3 :: a -> b -> (a,b)
mkpair3 aa bb = (ida aa, bb)
    where
      ida :: b -> b -- Legal, because b is now a free variable
      ida = id

It is better to avoid scoped type variables because they are not available in all compilers. A solution is available in Haskell 98.4

For example, the following can be interpreted as x 'asTypeOf' y and has the same value as x, but the deduced type says that x and y have the same type.
asTypeOf :: a -> a -> a
asTypeOf a b = a
Let’s look at the following examples for the let declaration:
let {var1 = expr1; var2 = expr2; ..., varn = exprn) in expr
expr where {var1 = expr1; var2 = expr2; ..., varn=exprn}
The scope of vari (where i = 1,n) represents the whole expression that contains the definition of vari. The variable vari is tied to the unevaluated expression expri (keep in mind that the evaluation in Haskell is lazy, as you will see in Chapter 16). The result of expr is represented by the let expression.
is_even = let {is_even n = n == 0 || n > 0 && is_odd(n-1);
is_odd n = n == 1 || n > 1 && is_even(n-1)}
in is_even
is_even:: Integer -> Bool
is_even' = is_even where
{is_even n = n == 0 || n > 0 && is_odd(n-1);
is_odd n = n == 1 || n > 1 && is_even(n-1)}

Types

As mentioned, Haskell is statically typed.

From an algebraic point of view, a type is a triple like T=<V, Op, Ax>. Here, V is the set of type values (the carrier set of the type), Op is defined as the set of type operators (including the signatures of the operators), and Ax represents the set of axioms describing the behavior and how the operators interact. To illustrate this, let’s consider the following example:
type α list is
Op
       []: → α list                 // a list of the constructors
       : : α × α list →α            // [] represents the empty list, : is like cons
       head: α list { [] } →α    // a list of the selectors
       tail: α list { [] } →α    // head as carrier, tail is as a cdr
       null: α list → bool         // a list of the predicates
Ax
       L: α list, x: α
       null [] = true               // testing if the list is empty
       null (x:L) = false
       head(x : L) = x              // the head of the list
       tail(x : L) = L              // the tail of the list

Summary

This chapter presented the most important aspects of static typing.

You learned about the following:
  • Statically typing systems and how they are defined during a program/module

  • How new types can be defined

  • The rules behind the type system

  • Indentation and its importance

  • Types and how they are defined

References

  1. 1.

    A. Serrano Mena, Beginning Haskell: A Project-Based Approach (Apress, 2014).

     
  2. 2.
     
  3. 3.

    Haskell 98 Language and Libraries, https://www.haskell.org/onlinereport/

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.82.82