I see now that Racket has types. At first glance it seems to be almost identical to Haskell typing. But is Lisp's CLOS covering some of the space Haskell types cover? Creating a very strict Haskell type and an object in any OO language seems vaguely similar. It's just that I've drunk some of the Haskell kool-aid and I'm totally paranoid that if I go down the Lisp road, I'll be screwed due to dynamic typing.
2 Answers
CL type system is more expressive than the Haskell one, e.g., you can have a type (or (integer 1 10) (integer 20 30))
for a value 1,2,...9,10,20,21,...,30
.
However, Lisp compilers does not force their understanding of type safety down your throat, so you can ignore their "notes" - at your own risk.
This means that you can write Haskell in Lisp (so to speak) by declaring all value types and carefully making sure that all the necessary types are inferred, but then it is easier to use Haskell in the first place.
Basically, if you want strong static typing, use Haskell or OCaml, if you want strong dynamic typing, use Lisp. If you want weak static typing, use C, if you want weak dynamic typing, use Perl/Python. Each path has its advantages (and zealots) and disadvantages (and detractors), so you will benefit from learning all of them.

- 729
- 5
- 12
-
19Anyone using terms like "force type safety down your throat" does not understand what type safety is or why it's useful. – Mason Wheeler Feb 17 '13 at 05:05
-
11@MasonWheeler: anyone making a sweeping conclusion from a single phrase will find himself wrong more often than otherwise. Like, e.g., in this case. – sds Feb 17 '13 at 06:00
-
4Since the topic is languages, the term "down your throat" is appropriate and apt imagery. – luser droog Feb 17 '13 at 07:30
-
What's your definition of `powerful` in the context of type systems? – KChaloux Mar 21 '13 at 15:39
-
1@KChaloux: I meant "expressive", as clarified by the example. – sds Mar 21 '13 at 15:40
-
1Expressive is another woefully ill-defined term people generally use to mean "good for me personally". – KChaloux Mar 21 '13 at 15:43
-
@KChaloux: not really: Lisp can describe all types which Haskell can, and there are types which Lisp can express and Haskell cannot. – sds Mar 21 '13 at 16:01
-
1@sds: Expressing a type and enforcing types (both when and what) are different things. Lisp may have a more expressive (read: lenient) type system, whereas Haskell has a more powerful one. – Thomas Eding Aug 21 '14 at 05:29
-
@ThomasEding: "powerful" is the wrong term: it is unclear but conveys a positive feeling. Lisp is more expressive, Haskell is stricter. – sds Aug 21 '14 at 16:55
-
1For fun, there are things a Haskell type system can do that Lisp's cannot (or so I think). Types can carry around compile time information, making certain ad-hoc polymorphism constructs possible. Examples include `return` or `minBound`. – Thomas Eding Aug 21 '14 at 17:20
-
4You've got it all backwards. Dynamic typing is a special case of static typing, where you force the programmer to use 1 type for everything. I can do the same thing (verbosely) in most statically-typed languages by declaring every variable as type `Object` or whatever the root of the type tree is. This is *less* expressive, because you're *deprived* of the *option* of saying that certain variables can only contain certain values. – Doval Aug 22 '14 at 15:13
-
1@Doval: you are confusing untyped languages and dynamically typed ones. In lisp you _can_ specify variable types, if you wish. – sds Aug 22 '14 at 15:19
-
1@sds: I believe Doval means you could have everything contain a type id too (thus typing is done at runtime). IOW one can write a Lisp interpreter in Haskell. But one can write a Haskell interpreter in Lisp.... I suspect it is significantly easier to write an interpreter for a dynamically typed language from a statically typed one, as opposed to vice versa. One thing to note of course is that emulating and simulating are different things. – Thomas Eding Aug 22 '14 at 17:11
-
1@ThomasEding: Any Turing-complete language can be used to implement any other. Each language has advantages and disadvantages. Alas, quite a few people here don't understand dynamic typing and how it is orthogonal to strict typing. Lisp has a strict dynamic typing. – sds Aug 22 '14 at 18:13
-
@sds Python has *strong* dynamic typing. – uselpa Aug 22 '14 at 20:22
-
@uselpa: Python has "duck typing", i.e., an object can have a type by accident, just because it happened to have certain fields. This is "weak". – sds Aug 22 '14 at 20:29
Typed Racket is very different from Haskell. Type systems in Lisp and Scheme, and indeed type systems in traditionally untyped language ecosystems in general, have a fundamental goal that other type systems do not - interoperating with existing untyped code. Typed Racket for example introduced whole new typing rules to accommodate various Racket idioms. Consider this function:
(define (first some-list)
(if (empty? some-list)
#f
(car some-list)))
For non-empty lists, this returns the first element. For empty lists, this returns false. This is common in untyped languages; a typed language would use some wrapper type like Maybe
or throw an error in the empty case. If we wanted to add a type to this function, what type should be used? It's not [a] -> a
(in Haskell notation), because it can return false. It's also not [a] -> Either a Boolean
, because (1) it always returns false in the empty case, not an arbitrary boolean and (2) an Either type would wrap elements in Left
and false in Right
and require you "unwrap the either" to get to the actual element. Instead, the value returns a true union - theres no wrapping constructors, it simply returns one type in some cases and another type in other cases. In Typed Racket, this is represented with the union type constructor:
(: first (All (A) (-> (Listof A) (U A #f))))
(define (first some-list)
(if (empty? some-list)
#f
(car some-list)))
The type (U A #f)
states the function could return either an element of the list or false without any wrapping Either
instance. The type checker can infer that some-list
is either of type (Pair A (Listof A))
or the empty list, and furthermore it infers that in the two branches of the if statement it is known which of those is the case. The type checker knows that in the (car some-list)
expression, the list must have the type (Pair A (Listof A))
because the if condition ensures it. This is called occurrence typing and is designed to ease transition from untyped code to typed code.
The problem is migration. There's a ton of untyped Racket code out there, and Typed Racket can't just force you to abandon all your favorite untyped libraries and spend a month adding types to your codebase if you want to use it. This problem applies whenever your adding types gradually to an existing codebase, see TypeScript and its Any type for a javascript application of these ideas.
A gradual type system must provide tools for dealing with common untyped idioms and interacting with existing untyped code. Using it will be quite painful otherwise, see "Why we're no longer using Core.typed" for a Clojure example.

- 4,449
- 2
- 26
- 30
-
1In economics, this is known as [Gresham's Law](https://en.wikipedia.org/wiki/Gresham's_law): *bad money drives out good.* It tends to find applications in engineering too. When you have two theoretically equivalent subsystems within your system, all too often the worse one will cause too many problems to make the better one worth using, as we're seeing here. – Mason Wheeler May 26 '16 at 19:39
-
"an example of designing a type system that": this sentence is not complete – coredump May 26 '16 at 21:35
-
@MasonWheeler The worse one is, let me guess, dynamic type checking. So as soon as you introduce it, it is not worth performing some static analysis? – coredump May 26 '16 at 21:44
-
1@coredump - Gradual typing is worth it, unless you interact with untyped code poorly. The Any type in TypeScript for example just tells the typechecker to give up, basically throwing out the benefits of the type system because it can cause a bad value to propagate through large amounts of statically checked code. Typed Racket uses contracts and module boundaries to avoid this problem. – Jack May 26 '16 at 22:24
-
1@coredump Read the Clojure article. Having dynamic typing around, particularly with all of the third-party code that had no static types available, really screwed things up for their attempts to improve their codebase with static types. – Mason Wheeler May 26 '16 at 22:25