Hacker News new | ask | show | jobs
by zeveb 3085 days ago
> I would stay away from dynamically-typed languages (e.g., the Lisp and Scheme families) for software engineering. Dynamic typing means more run-time errors

Note that Common Lisp has type declarations, which can move type errors from run-time to compile-time.

ML is a nice language family if you want something which is typed and participates in the Lisp mindset.

2 comments

Yes, in principle you can have a language that lets you cover the gamut from statically- to dynamically-typed code. Indeed, Haskell is such a language...

But it's not just whether the language supports it, but also:

- what is the default - what is the cultural default - the extent of type inferencing - the extent to which the compiler can generate efficient code (i.e., pass around unboxed, naked values sans run-time typing information)

CL basically doesn't go far enough. To begin with, the default is dynamic typing, and IIRC it has no type inference, though at least CL compilers can do a fair bit optimization but you'll always pay the price of some type encoding in pointer/fixed-sized-integer values' low order bits.

> Note that Common Lisp has type declarations, which can move type errors from run-time to compile-time.

This is a dangerous assumption. The standard does not require that Common Lisp type declarations cause the compiler to detect type inconsistencies (although some implementations might be smart enough to do so in some cases). CL type declarations tell the compiler it can remove runtime checks. They are performance optimizations. If anything they decrease type safety.

> CL type declarations tell the compiler it can remove runtime checks.

That's not true. To tell the compiler that it can remove runtime checks you declare the optimization quality SAFETY to 0.

The Common Lisp standard does not specify what type declarations do and what the interpreter or compiler does with it.

Some compilers will never check types. Some will use them when SAFETY is low as assertions and remove runtime checks. Some will use them as assertions both at compile and runtime.

> They are performance optimizations. If anything they decrease type safety.

That depends on the implementation and the compiler switches.

In SBCL by default it INCREASES type safety:

  * (defun foo (a) (declare (type (integer 0 100) a)) (+ a 10))
  WARNING: redefining COMMON-LISP-USER::FOO in DEFUN

  FOO
  * (foo -1)

  debugger invoked on a TYPE-ERROR in thread
  #<THREAD "main thread" RUNNING {1001950083}>:
    The value
      -1
    is not of type
      (MOD 101)
    when binding A

  Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

  restarts (invokable by number or by possibly-abbreviated name):
    0: [ABORT] Exit debugger, returning to top level.

  (FOO -1) [external]
     source: (SB-INT:NAMED-LAMBDA FOO
                 (A)
               (DECLARE (TYPE (INTEGER 0 100) A))
               (BLOCK FOO (+ A 10)))
  0]
You're right. The standard leaves the interpretation of type declarations in CL up to the implementation. I was coming from a CCL perspective, and I should have checked the others.

In CCL:

  ? (defun foo (a) (declare (type (integer 0 100) a)) (+ a 10))
  FOO
  ? (foo -1)
  9
It's dangerous to assume the effect type declarations will have in CL. You have to test it.
Even in CCL it depends on the compiler settings:

  ? (declaim (optimize (safety 3)))
  NIL
  ? (defun foo (a) (declare (type (integer 0 100) a)) (+ a 10))
  FOO
  ? (foo "bar")
  > Error: The value "bar" is not of the expected type (MOD 101).
  > While executing: FOO, in process listener(1).
  > Type :POP to abort, :R for a list of available restarts.
  > Type :? for other options.
  1 > 
Type declaration added -> runtime safety increased...

Best read the implementation manual to see how it deals with optimization values and type declarations.

Apparently the solution is to check the type before you declare it: https://stackoverflow.com/questions/32321054/using-declare-t...

I assume that most CL compilers are smart enough to optimize calls to such checking functions when they occur in a context where the type has already been declared. And you can probably write a macro to make it more convenient ...

There's no guarantee any given CL compiler will optimize such calls away. It would probably be considered wrong for the compiler to optimize them away. Remember that type declarations mean "don't check the type, ever." They do not mean "check the type at compile time" because CL never checks types at compile time.[1]

The technique in the example can defeat the purpose in simple cases (like the example) because type declarations removed runtime type checking and then you manually added it back in.

But it's a useful technique in cases such as an inner loop from which you remove type checking inside the loop, but move it out of the loop. This is an advanced CL technique. You have to be very careful about such things as adding 1 to a fixnum in the loop and possibly overflowing it, which is exactly what you have to care about in C.

[1] modulo certain compiler quirks and the use of compiler macros, which are an advanced technique.

> CL never checks types at compile time

SBCL, CMUCL, Scieneer CL do.

http://www.sbcl.org/manual/#Declarations-as-Assertions

You can enforce type checks by writing your own.

LISP is rather extensible. People have written Haskell-like type systems for LISP with compile-time checks.