Hacker News new | ask | show | jobs
by charriu 3985 days ago
I think the first section confuses a couple of things.

First, a program can be compiled (static) or interpreted (dynamic) as stated in the article. However, that does not mean that you can't have a dynamic type system in a compiled language, or vice versa.

Also, if you add type inference, the examples given for variables in dynamic languages are perfectly valid examples for variables in a language with a static type system (the type would just be defined on first assignment).

2 comments

Static and dynamic are theoretically unrelated to compiled or interpreted. The reason why it seems like these two are the same is that it's much harder to implement a compiled version of a dynamic language than an interpreted dynamic language, although I'm sure it's been done. There are plenty of interpreted static languages though.

I'm missing a discussion of weak vs strong in the article though. Visual Basic might be statically typed but it does have implicit type coercion which gives it a very different feel from — say — C#.

It's harder to compile, period. But's it's in many ways easier to compile a dynamic language than a static language, as you can implement a single mechanis for defining classes and calling methods. That mechanism can often be very similar to how it would be in an interpreter.

What is harder is to achieve the same performance jump from interpreted to compiled. Exactly because the "naive" approach to compiling dynamically typed languages gives you similar machinery to a well written interpreter, while for static language the "naive" approach often gets you much more efficient results.

> it's much harder to implement a compiled version of a dynamic language

No it is not. All major dynamic languages can be compiled usually JIT. Every major browser has a JIT compiler for Javascript. Python, Ruby, and Lua have JIT-compiled forks/reimplementations.

The difference of JIT vs ahead-of-time compilation is irrelevant for the high level of the article.

> All major dynamic languages can be compiled usually JIT.

JITs are profoundly different from standalone compilers. And are many times more complicated.

> The difference of JIT vs ahead-of-time compilation is irrelevant for the high level of the article.

No it's not irrelevant. JITs are starting to kick in at a very different point than the static compilers: they've got a path trace, runtime type information and profiling information, while the static compiler only got an AST.

JITs can do gradual refinement, and for this they may implement multiple different compilation techniques. Static compiler must do all it can at once, there is no other chance to improve.

Common Lisp is an example of a compiled dynamic language.
And a rather opaque one at that. I've never heard of anyone discussing the implementation details of a Scheme or Lisp compiler, leading me to believe that they must have really complicated internal logic.

I might be better served applying my time towards development on Chicken (Scheme).

In response to being downvoted — I would assume that tracking all of the records in function closures (first-class functions) would be a real headache, though slightly aided by the fact that garbage collection is available, and the question of whether to support eval raises a number of issues.

In any case, compiled Common Lisp is not something that resembles other compiled languages... and the purpose of my article is to discuss either designing an interpreter or a compiler, not a dynamic compiler, recompiler, or compiled code that requires some kind of runtime interpreter as a side, all of which are extraneous use cases for hackers not people learning how to program. They also make for poor pedagogical tools.

There are books about Lisp implementations and papers about various Lisp compilers. Actually there is a whole bunch of literature about it.

Personally I find that the article needs a bunch of improvements:

static vs. dynamic vs. statically typed and dynamically typed

These are different things. Either we are talking about runtime behavior (a dynamic language can change itself or the program at runtime) or we are talking about typing.

For example Java is a statically typed language, but it allows the use of class loaders which can reload classes at runtime - which provides some dynamic features. On the other end of the spectrum are many compiled Lisp implementations which allow various changes to the programming language at runtime, for example via an extensive meta-object protocol.

> Attempting to access a value that has not been named in a relevant scope leads to a syntax error issued at compile time.

A syntax error? Really? Syntax?

> So called dynamically typed languages are sometimes referred to as duck-typed or duck languages.

Definitely not. Duck typing is a only a special form of dynamic typing, usually in an object-oriented language. Non object-oriented dynamically typed languages don't provide duck typing, because they lack OOP objects.

Does compiled Lisp look different?

Maybe... Let's look at SBCL on a 64bit Intel processor:

    * (defun calc (a b c) (+ (* 2 a) (* 3 b) (* 4 c)))

    CALC

    * (disassemble #'calc)

    ; disassembly for CALC
    ; Size: 118 bytes. Origin: #x1002EC2599
    ; 599:       48895DE8         MOV [RBP-24], RBX               ; no-arg-parsing entry point
    ; 59D:       BF04000000       MOV EDI, 4
    ; 5A2:       488BD1           MOV RDX, RCX
    ; 5A5:       41BBD0020020     MOV R11D, 536871632             ; GENERIC-*
    ; 5AB:       41FFD3           CALL R11
    ; 5AE:       488BF2           MOV RSI, RDX
    ; 5B1:       488B5DE8         MOV RBX, [RBP-24]
    ; 5B5:       488975F0         MOV [RBP-16], RSI
    ; 5B9:       BF06000000       MOV EDI, 6
    ; 5BE:       488BD3           MOV RDX, RBX
    ; 5C1:       41BBD0020020     MOV R11D, 536871632             ; GENERIC-*
    ; 5C7:       41FFD3           CALL R11
    ; 5CA:       488BFA           MOV RDI, RDX
    ; 5CD:       488B75F0         MOV RSI, [RBP-16]
    ; 5D1:       488BD6           MOV RDX, RSI
    ; 5D4:       41BBD0010020     MOV R11D, 536871376             ; GENERIC-+
    ; 5DA:       41FFD3           CALL R11
    ; 5DD:       488BDA           MOV RBX, RDX
    ; 5E0:       48895DF0         MOV [RBP-16], RBX
    ; 5E4:       488B55F8         MOV RDX, [RBP-8]
    ; 5E8:       BF08000000       MOV EDI, 8
    ; 5ED:       41BBD0020020     MOV R11D, 536871632             ; GENERIC-*
    ; 5F3:       41FFD3           CALL R11
    ; 5F6:       488BFA           MOV RDI, RDX
    ; 5F9:       488B5DF0         MOV RBX, [RBP-16]
    ; 5FD:       488BD3           MOV RDX, RBX
    ; 600:       41BBD0010020     MOV R11D, 536871376             ; GENERIC-+
    ; 606:       41FFD3           CALL R11
    ; 609:       488BE5           MOV RSP, RBP
    ; 60C:       F8               CLC
    ; 60D:       5D               POP RBP
    ; 60E:       C3               RET
    NIL
Now we can even define the types in Common Lisp:

    * (defun calc (a b c)
      (declare (fixnum a b c))
      (the fixnum
           (+ (the fixnum (* 2 a))
              (the fixnum (* 3 b))
              (the fixnum (* 4 c)))))
    STYLE-WARNING: redefining COMMON-LISP-USER::CALC in DEFUN

    CALC
    * (disassemble #'calc)

    ; disassembly for CALC
    ; Size: 38 bytes. Origin: #x1002F4B802
    ; 02:       48D1E1           SHL RCX, 1                       ; no-arg-parsing entry point
    ; 05:       488D047F         LEA RAX, [RDI+RDI*2]
    ; 09:       48D1F9           SAR RCX, 1
    ; 0C:       48D1F8           SAR RAX, 1
    ; 0F:       4801C1           ADD RCX, RAX
    ; 12:       48C1E602         SHL RSI, 2
    ; 16:       48D1FE           SAR RSI, 1
    ; 19:       4801F1           ADD RCX, RSI
    ; 1C:       48D1E1           SHL RCX, 1
    ; 1F:       488BD1           MOV RDX, RCX
    ; 22:       488BE5           MOV RSP, RBP
    ; 25:       F8               CLC
    ; 26:       5D               POP RBP
    ; 27:       C3               RET
    NIL
    *
Overkill.

This article isn't a dictionary entry, an encyclopedia, or a research paper. It describes what's necessary to build a programming language for someone who has the necessary skills, which is someone who programs.

The entire premise of this programming language isn't based on as rigorous ideas as what you're criticizing, and that's been the reaction since the first time I posted about it.

As for what I was talking about, I'm only interested in dynamically typed languages that are compiled and provide support for 'eval in this case, because I don't believe that this is possible without providing a complete library for an interpreter with a compiled executable. For every other case, who really cares? Being able to replace every variable declaration with the keyword `auto' does not a new (or useful) programming language make. Additionally, all of these other gray areas in-between are not something I am interested in.

For this purpose, when speaking of the Duck programming language, dynamicism refers to being able to literally manipulate types and data in any way imaginable, both in terms of runtime behavior AND typing. In any case, it is designed to be the most dynamic language, as the union of all of these features, and as such that invalidates a huge number of complaints.

>> Attempting to access a value that has not been named in a relevant scope leads to a syntax error issued at compile time.

> A syntax error? Really? Syntax?

This is a very minor complaint and mirrors 99% of the criticism I've received.

I wish I could get more interesting feedback for the content of my writing rather than the semantics.

As is JavaScript
No, they're very far away from each other on the dynamism scale. There is absolutely no point in trying to compile JavaScript statically, but yet it works very well for Common Lisp. The opposite is also true, smart tracing JITs won't do much for Common Lisp, but shine for JavaScript.
I was just adding on that JavaScript is another example of a dynamic language that is compiled (using JIT in this case) rather than strictly interpreted such as the MRI implementation of Ruby.
Since both this and my parent post on JavaScript being compiled were downvoted without comment I can only make the assumption this is because someone came along who believes this is not the case.

So first I will link out to this StackExchange doc that does a pretty good job of describing this.

http://programmers.stackexchange.com/questions/138521/is-jav...

The JavaScript specification says nothing about the language being interpreted or compiled. Today; most current versions of major browsers use a just in time compiler to handle JavaScript. V8 (Chrome & Nodejs), SpiderMonkey (Firefox), Chakra (InternetExplorer), Carakan (Opera), JavaScriptCore(Webkit/Safari)

For more information take a look at the Wiki entry for EcmaScript engines.

https://en.wikipedia.org/wiki/List_of_ECMAScript_engines

Could you elaborate on this point?
The biggest gain of the tracing JITs is elimination of dynamic dispatch. CL does not use it to an extend comparable to JavaScript, Python or Smalltalk.
The only difference for me is how often I need to cast types so I guess that's kind of lost on me.
Sure, that's what it comes down to. Languages with implicit type coercion do have a set of bugs not found in strongly typed languages though, but the additional safety of a strong language is -- like you say -- gained at the expense of writing casts. That does seem like a trade-off worth discussing in an article about programming language design.
C# allows you to do implicit casts when you do not lose information, e.g. int to long. I cannot remember the last time I had to cast to something where I would potentially lose information.

However, I don't think manual casting is a cost at all - it's more of a guide telling you something in the design is wrong, or a reminder to make a comment of why you do it. And for any long maintanence projects, you want to catch all the bugs you can as early as you can.

Yes, but those are all tasks which the compiler has to do when compiling source. So after a programmer has written code but before it can even run.

Really any run time could completely disregard types, it just might lead to a huge number of failure cases. These generalizations are there for us to start to get things to work.

When programming languages add features like these, especially 10 or 20 years after they were developed, they definitely feel tacked-on and if you inspect how they work, it's not pretty. If it's an interesting quirk then it might be an example to study.