Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save frankitox/5b9180506435ba57dc31199f3c62732f to your computer and use it in GitHub Desktop.
Save frankitox/5b9180506435ba57dc31199f3c62732f to your computer and use it in GitHub Desktop.

Static/dynamic, strong/weak typing.

You have discovered a soft spot in the terminology that amateurs use to talk about programming languages. Don't use the terms "strong" and "weak" typing, because they don't have a universally agreed on technical meaning. By contrast, static typing means that programs are checked before being executed, and a program might be rejected before it starts. Dynamic typing means that the types of values are checked during execution, and a poorly typed operation might cause the program to halt or otherwise signal an error at run time. A primary reason for static typing is to rule out programs that might have such "dynamic type errors".

Strong typing generally means that there are no loopholes in the type system, whereas weak typing means the type system can be subverted (invalidating any guarantees). The terms are often used incorrectly to mean static and dynamic typing. To see the difference, think of C: the language is type-checked at compile time (static typing), but there are plenty of loopholes; you can pretty much cast a value of any type to another type of the same size---in particular, you can cast pointer types freely. Pascal was a language that was intended to be strongly typed but famously had an unforeseen loophole: a variant record with no tag.

Implementations of strongly typed languages often acquire loopholes over time, usually so that part of the run-time system can be implemented in the high-level language. For example, Objective Caml has a function called Obj.magic which has the run-time effect of simply returning its argument, but at compile time it converts a value of any type to one of any other type. My favorite example is Modula-3, whose designers called their type-casting construct LOOPHOLE.

Having said that, you can't count on any two people using the words "strong" and "weak" in exactly the same way. So avoid them.

Source

Also note that statically typed languages associate type to variables, while in dynamically typed languages you associate types to values. e.g:

# This works in a dynamically typed language:
goals = "None"
goals = 1
# While in a static:
string goals
goals = "None"
goals = 1 # Here it breaks.

Also, in statically typed languages with type inference you don't have to always make explicit the type of each expression.

Haskell types and typeclasses.

Every value in Haskell has a type. There are several types:

  • The usuals: Bool, Char, Int, etc.
  • Little weird: list involved types (which can wrap other type: [Char], [(Int, Char)], [Bool]. n-tuple types (which also can wrap other types): (Bool, Char, Int), ([Int], Int), ().
  • Function related types: Int -> Int, [(Int, Char)] -> (Int, Char).

There's also type variables (They start with a lower case letter) e.g: fst :: (a, b) -> a. Note that just because a and b are different type variables, they don't have to be different types. Functions that have type variables are called polymorphic functions.

Defining a typeclass.

A typeclass is a sort of interface that declares certain functions. Each parameter of the class is a concrete type class. e.g:

class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

You can also define a typeclass specifying typeclass restrictions over certain type variables. e.g:

class Eq a => Ord a where
  compare :: a -> a -> Ordering
  (<) :: a -> a -> Bool
  (<=) :: a -> a -> Bool
  (>) :: a -> a -> Bool
  (>=) :: a -> a -> Bool
  max :: a -> a -> a
  min :: a -> a -> a

Note that, a priori, you can use only one type variable in a class definition, this is banned:

class MadeUp a b c where
  madeUp :: a -> b -> c

Make a type belong to a certain typeclass.

type ∊ typeclass iff implements the typeclass functions.

Where a typeclass is a set of types.

Now, we'll implement Eq for lists:

instance (Eq a) => Eq [a] where
    []     == []     = True
    (x:xs) == (y:ys) = x == y && xs == ys
    _xs    == _ys    = False

Notice that, in this particular case, Eq typeclass allows you to define == or != and it will automagically fill the other definition for you.

Another important typeclasses are Show and Read. Another thing to note is that typeclasses allows us to create polymorphic constants. An example of use:

class Bounded a where
  minBound :: a
  maxBound :: a

maxBound :: Int  -- Returns the biggest int.
maxBound :: Bool -- Returns True.

See how you must specify the proper concrete type of the constant. Numbers are also an example of polymorphic constants:

1::Int -- Returns 1
1::Float -- Returns 1.0
:t 1 -- 1 :: Num a => a

Once that we have typeclasses.

Now we can restrict a type variable to a certain typeclass. e.g: fromIntegral :: (Num b, Integral a) => a -> b. Here we state that b must be of a type which belongs to the typeclass Num and a of a type belonging to Integral typeclass.

Source

data declarations.

Data declarations allow us to wrap several types in one.

-- Note that <data-name> can be the same as
-- one of the <constructor-name>s
data <data-name> [type-vars] = <constructor-name> <type-1> <type-2> ...
                             | <constructor-name> <type-1> <type-2> ...
                             | ...
data WhichWay a b = Left Int a
                  | Right Int b

Constructors like Left are really peculiar, they start with caps and in the sentence <constructor-name> <types> (Left Int a) you are basically saying that there's a function¹ called <constructor-name> (Left) of type <type-1> ... (Int a) that returns a <data-name> [type-vars] (WhichWay a b). If the constructor doesn't define <type-1> ... it becomes a constant of type <data-name> [type-vars]. ¹A constructor function, they must start with a capital letter.

Now, once you defined a data type and use the constructors to wrap values in your new data type, you can unwrap them using pattern matching over the same constructors:

data WhichWay a b = Left Int a
                  | Right Int b
w = Left 1 'c'
getNumber :: WhichWay a b -> Int
getNumber (Left  n _) = n
getNumber (Right n _) = n
one = getNumber w

There are particular cases where you only need one constructor which wraps plenty types:

data Person = Person String String Int Float String String deriving (Show)

You may lost track of the order/meaning of each type parameter of Person. Records are meant to solve this little problem.

data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     , height :: Float
                     , phoneNumber :: String
                     , flavor :: String
                     } deriving (Show)
-- Old way to construct a person:
p1 = Person "Franco" "Biasin" ...
-- New and better:
p1 = Person { firstName = "Franco",
              lastName  = "Biasin",
              ... }
-- For free you have getters for each 'field':
firstName p1 == "Franco"

don't use typeclasses restrictions in data declarations

You can specify typeclasses. e.g: data (Ord k) => Map k v = ... , but they're not a good practice cause noideawhy.

recursiveness in data declarations.

Of course you can use recursion in your data definitions, you'll have to specify at least one type of one of you're data constructor the same as your data type:

data Tree a = CTree (Tree a) a (Tree a)
            | Leaf

CTree is used instead of Tree to emphasize the fact that this is the constructor, and the other are type declarations.

Type synonyms.

With type we can define type synonyms. They're just syntax sugar to clear code. Notice that here there's no mention of data constructors. Of course you can also use type variables.

type Operator a = a -> a -> a

Kinds, as a mechanism to explain proper class instantiation.

TODO

Source

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment