Re: static typing



On Mon, 7 Aug 2006, Pascal Bourguignon wrote:

Jeffery Zhang <jz87@xxxxxxxxxxx> writes:

I've been learning Lisp since about Februrary, and I've implemeted
some small projects (~300 lines) in lisp. I've built up a small
library of utility functions and macros so things are going well.

But there is a nagging thought at the back of my head. I miss static
typing of languages like SML and Haskell. Specifically I miss things
like type inference.. A lot of times I make small mistakes like
forgetting the cdr returns a list and not an element. I feel like
without static typing I have to keep the defintiions of the
datastructures in my head, which is bad since I'm bad at remembering
things. For typed languages a lot of editors can offer code completion
based on type analysis, and warn when you pass the wrong type
in. Sometimes when list structures get complicated I forget how I
wrote it, I would like to be able to declare a type like list<integer>
and get warned if I pass this to mapcar and some function whose input
is not of type integer.

Is there anything that does this?

That's because you're not abstracting away your data structures!

First, if you're processing lists, you shouldn't use NULL, CONS, CAR or CDR!

NULL, CONS, CAR and CDR are used to _implement_ lists, but they're not
the public API for list processing!

The public API for list processing is ENDP, LIST, FIRST and REST.

But you shouldn't use them, if your data structure is not a list!

For example, if you have a tree of nodes with a label and a variable
number of children, you should use: LEAFP, MAKE-NODE, NODE-LABEL and
NODE-CHILDREN. You can still implement your nodes with lists:

(defun LEAFP (object) (not (consp object)))
(defun MAKE-NODE (label &rest children) (cons label children))
(defun NODE-LABEL (node) (first node))
(defun NODE-CHILDREN (node) (rest node))

(make-node 'animal (make-node 'vertebrate 'cat 'dog)
(make-node 'invertebrate 'plancton))


Of course, unless you're doing a Q&D prototype and want to write
literal trees as: '(animal (vertebrate cat dog) (invertebrate plancton))
you could as well use structures or CLOS objects:

(defstruct node label children)

(make-node :label 'animal
:children (list (make-node :label 'vertebrate
:children (list 'cat 'dog))
(make-node :label 'invertebrate
:children (list 'plancton))))


This doesn't completely solve the problem. There's no standard way to process a form/file checking for type errors, which is really the point. I can still type

> (defun bad ()
(node-label 12))

which is Obviously Wrong. It would be useful to do

> (typecheck bad)
*** argument to NODE-LABEL is FIXNUM, expecting NODE.

Of course, this could be implemented as a library, but I'm not aware of any such library existing.

A general library of Lisp 'linting' utilities might be very useful.


As for types, you're rather inconsistent. First you want type
inference, then you want to declare a type list<integer> !
Do you know what do you really want???

Anyways, if you want to declare types, you can.

(defun f (x)
(declare (integer x))
(+ 1 x))

(f 3.0)
--> debugger invoked on a TYPE-ERROR: The value 3.0 is not of type INTEGER.



Now for a type list<integer>, the problem is that in Lisp, there is no LIST.
What appear to be be lists in lisp are actually trees of CONS cells.

Nonetheless, you can still specify such a type, but not as a parameter
to a "LIST" type, like you could do it for a VECTOR.


(defmacro define-list-of-predicate (type)
(let ((name (intern (format nil "LIST<~A>" type))))
`(defun ,name (object)
(or (null object)
(and (consp object)
(typep (car object) ',type)
(,name (cdr object)))))))

(define-list-of-predicate integer)

(deftype list-of (type) `(satisfies ,(intern (format nil "LIST<~A>" type))))


(typep '(1 2 3) '(list-of integer)) --> T
(typep '(1 2 a 3) '(list-of integer)) --> NIL
(typep '(1 2 . 3) '(list-of integer)) --> NIL

So now, you can declare a function to take only list of integers as argument:

(defun f (x)
(declare (type x (list-of integer)))
(reduce (function +) x :initial-value 0))

(f '(2 4 6)) --> 12
(f '(2 4.0 6)) --> debugger invoked on a TYPE-ERROR: The value (2 4.0 6)
is not of type (SATISFIES LIST<INTEGER>).


As for type inference, what more do you want than:

* (defun k (x) (car (+ 1 x)))
; in: LAMBDA NIL
; (CAR (+ 1 X))
;
; caught WARNING:
; Asserted type LIST conflicts with derived type (VALUES NUMBER &OPTIONAL).
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition

Sure, but that isn't a requirement of the standard. Way back when I tested Corman and LispWorks, and I didn't notice them doing any type inference.

Of course, the hypothetical linting library could perform this kind of type checking, and attach explicit type annotations. This kind of functionality doesn't actually need to be 'in the language', but it would be useful functionality to have access to, I think.

K


--
__Pascal Bourguignon__ http://www.informatimago.com/

"What is this talk of "release"? Klingons do not make software
"releases". Our software "escapes" leaving a bloody trail of
designers and quality assurance people in its wake."
.



Relevant Pages

  • Re: Cons cell archaic!?
    ... How would the implementation of a Lisp without a using cons look like? ... the irregularity in its often cited regular syntax. ... Lisp at core is based on functional programing on lists. ...
    (comp.lang.lisp)
  • Re: How powerful macros are?
    ... >> for software engineers to use arrays for everything, ... Did I miss something or is #an impossibly unbearable syntax in Lisp? ... and arrays are often slower for very short lists. ... I've also extended the language so that I have re-readable hash tables ...
    (comp.lang.lisp)
  • Re: Cons cell archaic!?
    ... why lisp has the cons problem and was never ... langs that deal with lists, vast majority of list usage is just simple ... and if it's not done in those other languages it must be ... Java and Lisp as languages worth consiering, ...
    (comp.lang.lisp)
  • Re: Lisp collections
    ... If a programming language specification talks about a data type, ... For example, The Common Lisp Hyperspec talks about lists, ... misapprehension that Lisp programmers generally just used lists to store ...
    (comp.lang.lisp)
  • Static or runtime type-checking (was: Program compression)
    ... I know that everything and the kitchen sink has been added to LISP. ... Seed7 seems to be rather limited in what it can easily support. ... list-processing primitives as applied to linked lists (push new ... Only internal types can be checked at compile time. ...
    (comp.programming)