« Back to article list

Type Safety and Lack Thereof

Table of Contents

Type Safety and Lack Thereof

Whether you're new to programming, or have been with it for awhile, you've probably heard the terms "types" and "type safety" tossed around (probably also "dynamic types", "static types", etc.).

You may also see little benefit in one method vs the other (or you're a zealot for your preferred choice).

Some examples of statically typed languages would be C, Java, Haskell.

Some examples of dynamically typed languages would be PHP, Perl, and Common Lisp.

Sure, sure, you've explained it, now show me!

Alright, you asked for it.

Lets see what can happen without type safety (hint: runtime errors).

We'll start with a trivial PHP program:

function mod ($a, $b)
  return $a % $b;

function call_mod ()
  return mod (8, 'three');

echo call_mod ();

You'll notice when running the script you receive a division by zero error (thanks to PHP changing our string into a 0 to "help" us).

Now, lets pretend this is nested as part of a much deeper system, and the call to the function is not occurring immediately, but as part of a condition.

It's almost guaranteed we will not find this error until it becomes a runtime error on a live system (luckily in PHP7 they plan to add some optional type support).

and what happens when we have strict typing (hint: compile errors)

How about in a C program?

#include <stdio.h>

int mod (int a, int b)
  return a % b;

int call_mod ()
  return mod (8, "three");

int main (int argc, char *argv[])
  int x = call_mod ();
  printf ("%d", x);
  return 0;

And what does the compiler tell us?

➜  /tmp  gcc -Wall ./types.c -o types
./types.c: In function ‘call_mod’:
./types.c:10:18: warning: passing argument 2 of ‘mod’ makes integer from pointer without a cast [-Wint-conversion]
   return mod (8, "three");
./types.c:3:5: note: expected ‘int’ but argument is of type ‘char *’
 int mod (int a, int b)

It won't even let us get as far as having an executable.

For good measure, lets see what happens in haskell:

mod' :: (Integral a) => a -> a -> a
mod' a b = a `mod` b

callMod :: (Integral a) => a
callMod = 8 `mod'` "three"

And the ghci REPL lets us know:

blub.hs:265:20-26: Couldn't match expected type ‘a’ with actual type ‘[Char]’ …
      ‘a’ is a rigid type variable bound by
          the type signature for callMod :: Integral a => a
          at /home/mcarter/src/haskell/blub.hs:264:12
    Relevant bindings include
      callMod :: a (bound at /home/mcarter/src/haskell/blub.hs:265:1)
    In the second argument of ‘mod'’, namely ‘"three"’
    In the expression: 8 `mod'` "three"
    In an equation for ‘callMod’: callMod = 8 `mod'` "three"

Thus preventing us from ever getting to the point of receiving the incorrectly compiled code.

So…what's best? Static types? Dynamic Types? How about both?

If only there were a language that allowed the flexibility of dynamic typing (for the benefits to rapid prototyping with poke and prod development via a REPL), as well as the higher level of feedback/warning to the developer to help prevent logic errors resulting from type incompatibilities.

Come to think of it, there is!

Stepping into the ring is Common Lisp!

I prefer my languages like my software, free (https://www.fsf.org/), and no language exists today that allows the developer more freedom in how the underpinnings of their program works than Common Lisp.

Now, Common Lisp is also a dynamically typed language (no static typing), but due to the power of the macro system, we can get it to act as if it had a type system (macros are not just simple text substitutions like they are in C, think of code that writes code, but more controlled than eval).

Lets look at one of the type failings (this matches up to our situation in the PHP example):

GLYPHS> (defun mod! (a b)
          (mod a b))
GLYPHS> (defun call-mod ()
          (mod! 8 "three"))
GLYPHS> (call-mod)
; Evaluation aborted on #<TYPE-ERROR expected-type: REAL datum: "three">.

Okay, it's a little better - we didn't end up with runaway code (Common Lisp catches the error for us in the REPL - that's read-eval-print-loop for the unwashed masses), but this can still end up tucked away as a runtime error until much later.

Lets add a simple static type system with a little haskell flavor (well, loosely based on it anyways).

(defmacro defn (name types args &rest rest)
  "Type safe defun"
  (let ((types (remove-if
                (lambda (x) (or (equal '-> x) (equal ' x))) types)))
    `(progn (defun ,name ,args
              ,@(loop for arg in args for type in types
                     collect `(check-type ,arg ,type))
            (declaim (ftype (function ,(butlast types) ,@(last types)) ,name)))))

I'll write a new post and give a more clear breakdown of the aforementioned code for you lispers at a later date (it's actually a pretty basic macro), but for now, lets see what happens when we define our function in a type safe manner using our new function defining call:

GLYPHS> (defn mod! (integer -> integer -> integer) (a b)
              (mod a b))
GLYPHS> (defun call-mod ()
          (mod! 8 "three"))
;     (GLYPHS::MOD! 8 "three")
; caught WARNING:
;   Constant "three" conflicts with its asserted type INTEGER.
;   See also:
;     The SBCL Manual, Node "Handling of Types"
; compilation unit finished
;   caught 1 WARNING condition

You'll notice that we received warnings at compile time (when the call-mod function was defined) instead of gladly accepting our obviously wrong call to the mod! function (mod is a built in function, so we used an exclamation mark to differentiate it, similar to the mod' definition in the haskell sample).

With the above macro, our REPL still compiles call-mod (which will hit a runtime error if encountered), but it gives a very clear message to the user that something was incorrect with their code.

Down the rabbit hole

You are the only limit to what you can do with Common Lisp.

With a few lines of macros, I have a very specific DSL that I use for my hobby projects via my Glyphs package (available via Quicklisp as well).

As of tonight, it has a new macro for using the static types, but you'll have to wait until next month for the Quicklisp update which has it!

GLYPHS> (ƒ factorial (integer  integer)
            0  1
            α  (* α (factorial (1- α))))

GLYPHS> (ƒ call-factorial α  (factorial 3.3))
; caught WARNING:
;   Constant 3.3 conflicts with its asserted type INTEGER.
;   See also:
;     The SBCL Manual, Node "Handling of Types"
; compilation unit finished
;   caught 1 WARNING condition


comments powered by Disqus