From: Jeffery Zhang on
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?

-Jeffery

From: Pascal Bourguignon on
Jeffery Zhang <jz87(a)cornell.edu> 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))))



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

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."
From: Ken Tilton on


Jeffery Zhang wrote:
> 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?

http://common-lisp.net/project/slime/

Sorry, private joke. Real answer: no, Lisp has different crutches. Your
brain will rewire soon enough.

ken

--
Cells: http://common-lisp.net/project/cells/

"I'll say I'm losing my grip, and it feels terrific."
-- Smiling husband to scowling wife, New Yorker cartoon
From: Nathan Baum on
On Mon, 7 Aug 2006, Pascal Bourguignon wrote:

> Jeffery Zhang <jz87(a)cornell.edu> 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."
From: b.m.c on
Forgive glitches evident in this reply, as my posts to Usenet are in
the range of 'almost never'.

Newly fascinated with Lisp as I am, this phrase strikes me ... "First,
if you're processing lists, you shouldn't use NULL, CONS, CAR or
CDR!"... particularly were I to thumb a page or two of the "The Little
Lisper", or it's kin.

Data abstraction (and program efficiency) notwithstanding, at times are
not the above primitives still the natural choice for 'nuts & bolts'
computation of list structure?

Thanks,
Brian C.