What exactly does it mean to have "good dynamic typing facilities"?
What exactly does it mean to have "good dynamic typing facilities"?
To quote Peter Norvig on the difference between Python and Lisp, but you could apply it to most other mainstream dynamic languages vs Lisp :
> Python is more dynamic, does less error-checking. In Python you won't get any warnings for undefined functions or fields, or wrong number of arguments passed to a function, or most anything else at load time; you have to wait until run time. The commercial Lisp implementations will flag many of these as warnings; simpler implementations like clisp do not. The one place where Python is demonstrably more dangerous is when you do self.feild = 0 when you meant to type self.field = 0; the former will dynamically create a new field. The equivalent in Lisp, (setf (feild self) 0) will give you an error. On the other hand, accessing an undefined field will give you an error in both languages.
Common Lisp has a (somewhat) sound, standardized language definition, and competing compiler/JIT implementations that are much faster than anything that could ever possibly come from the Python camp because the latter is actually too dynamic and ill-defined ("Python is what CPython does") and making Python run fast while ensuring 100% compatibility with its existing ecosystem, without putting further restraints into the language, is akin to a mirage.
1. The language specification is very big. This is true, it is a very big specification. On the other hand, this is mostly caused because the language spec also includes the spec for its own "standard library", unlike what happens in C or Java, for example, where the Std. lib is specified elsewhere. CL's "standard library" is very big, because there are many, many features.
The other reason the spec is so big, is that this is a language with a lot of features - you can do high level programming, low level, complex OOP, design-your-own OOP, bitwise manipulation, arbitrary precision arithmetic, dissasemble functions to machine language, redefine classes at runtime, etc etc etc.
Probably the extreme of the features is that there is a sql-like mini-programming language built in just for doing loops (!), "the LOOP macro". On the other hand, you can choose not to use it. And if you use it, it can help you write highly readable and concise code. More info:
http://cl-cookbook.sourceforge.net/loop.html
2. The "cruft"; Common Lisp is basically the unification ("common") of at least two main Lisp dialects that were in use during the 70s. So there are some parts (mind you, just some) in which some naming or function parameter orders could have been more consistent; for example here everything is consistent:
;; access a property list by property
(getf plist property)
;; access an array by index
(aref array index)
;; access an object's slot
(slot-value object slot-name)
... but here the consistency is broken: ;; gethash: obtain the element from a hash table, by key
(gethash key hash-table)
There is also sometimes some things that seem to be redundant, like for example "setq", where "setf" can do everything you can do with "setq" (and more); or for example "defparameter", and "defvar" where in theory "setf" might be enough. But there are differences, and knowing such differences help to write more readable and better code. And it's really nitpicking, for these are easy to overcome.3. Because of the above, CL is often criticized because of being a language "designed by committee". But, unlike other "committee-designed languages", this one was designed by selecting, from older Lisps, features that were already proven to be a Good Thing, and incorporating them into CL without too many changes. So you can also consider it to be "a curated collection of good features from older Lisps..."
4. Scheme, the other main "Lisp dialect", has a much, much smaller and simpler spec, so it's easier to learn. But on the other hand this also means that many features are just absent, and will need to be implemented by the programmer (or by external libs), without any standarization. On the other hand, due to the extensive standarization, usually Common Lisp code is highly portable between implementations, and often code will run in various CL implementations, straight away, with zero change.
Historically, Scheme was more popular inside the academic community while Common Lisp was more popular with production systems (i.e. science, space, simulation, CAD/CAM, etc.) Thus, there used to be an animosity between Schemers and Lispers, although jumping from one language to other is rather easy...
JavaScript is up to 885 pages. https://www.ecma-international.org/publications/files/ECMA-S...
The C++ 17 draft: 1623 pages. https://www.ecma-international.org/publications/files/ECMA-S...
C, which "is not a big language, and is not well served by a big book", according to Thompson and Ritchie's 1988 introduction in the K&R2, is up to 683 pages in C11. Almost triple the size of C90's 230 pages.
How about something non-language? USB 3.2 spec (just released Sep 22): 100+ megabyte .zip file download. Up from 2.0's 73.
1. This is a serious criticism, but it has nothing to do with soundness.
2. There is absolutely nothing wrong with a language being designed by a committee, so long as the committee's members are all competent.
3. Back to 0.
It sort of has a bad stigma, because two well-known, unloved languages were designed by committee: COBOL and PL/I.
> What exactly does it mean to have "good dynamic typing facilities"?
The ability to change the structure of your program at runtime will be at the top of the list for me. You can't do that with Ruby/Python.
Picking Common Lisp as an example:
(NOTE: Some of the features are also present in good statically typed languages as well, so what I advocate is to use good, well-featured languages, not really static vs dynamic.)
(NOTE 2: I'm sorry for being such a fanboy, but that thing is addictive like a hard drug...)
0. Code is a first class citizen, and it can be handled just as well as any other type of data. See "macros" below.
1. The system is truly dynamic: Functions can be redefined while the code is running. Objects can change class to a newer version (if you want to), while the code is running.
2. The runtime is very strong with regards to types. It will not allow any type mismatch at all.
3. The error handling system is exemplary: Not only designed to "catch" errors, but also to apply a potential correction and try running the function again. This is known as "condition and restarts", and sadly is not present in many programming languages.
4. The object oriented system (CLOS) allows multiple dispatch. This sometimes allows producing very short, easy to understand code, without having to resort to workarounds. The circle-ellipse problem is solved really easily here. (Note: You can argue that CLOS is in truth a statically typed system, and this is partly true -- the method's class(es) need to be specified statically, but the rest of arguments can be dynamic.)
5. The macro system reduces boilerplate code to exactly zero. And also allows you to reduce the length of your code, or have very explicit (clear to read) code at the high-level. This brings down the level of complexity of your code, and thus makes it easier to manage. It also reduces the need for conventional refactoring, since macros can do much more powerful, automatic, transformations to the existing code.
6. The type system is extensive -- i am not forced to convert 10/3 to 3.333333, because 10/3 can stay as 10/3 (fractional data type). A function that in some cases should return a complex number, will then return the complex number, if that should be the answer, rather than causing an error or (worse) truncating the result to a real. Arbitrarly length numbers are supported, so numbers usually do not overflow or get truncated. (Factorial of 100) / (factorial of 99) gives me the correct result (100), instead of overflowing or losing precision (and thus giving a wrong result).
So you feel safe, because the system will assign your data the data type that suits it the best, and afterwards will not try to attempt any further conversion.
7. The type system is very flexible. For example, i can (optionally) specify that a function's input type shall be an integer between 1 and 10, and the runtime will enforce this restriction.
8. There is an extensive, solid namespace system, so functions and classes are located precisely and names don't conflict with other packages. Symbols can be exported explicitely. This makes managing big codebases much easier, because the frontier between local (private) code versus "code that gets used outside", can be made explicit and enforced.
9. Namespaces for functions and variables (and keywords, and classes) are separate, so they don't clash. Naming things is thus easier; this makes programming a bit more comfortable and code easier to read.
10. Documentation is built into the system - function and class documentation is part of the language standard.
11. Development is interactive. The runtime and compiler is a "living" thing in constant interaction with the user. This allows, for example, for the compiler to immediately tell you where the definition of function "x" is, or which code is using such function. Errors are very explicit and descriptive. Functions are compiled immediately after definition, and it can also be dissasembled (to machine language) with just a commmand.
12. Closures are available. And functions are first-class citizens.
13. Recursion can be used without too many worries -- most implementations allow tail call optimizations.
14. The language can be extended easily; the reader can also be extended if necessary, so new syntaxes can be introduced if you like. Then, they need to be explicitely enabled, of course.
15. There is a clear distinction between "read time", "compile time" and "run time", and you can write code that executes on any of those three times, as you need.
16. Function signatures can be expressed in many ways, including named parameters (which Python also has and IMO is a great way to avoid bugs regarding to wrong parameters / wrong passing order.)
I found this extremely weird and (or hence) interesting at the same time. Where can I read more about this?
http://www.gigamonkeys.com/book/beyond-exception-handling-co...
they're (hand wave hand wave) basically a matter of stuffing the current continuation inside of the exception, whenever you throw an exception, and then making use of that to provide more options whenever the exception is caught.