Showing posts tagged “pl design”. Show All
16th
2007
Dec
permalink

On Practicality and Tool-Making

A comment on my last post on design and programming languages spawned the following.

Q: Are you implementing some of your ideas?

A: It’s always in the back of my mind to design and implement my dream programming language. This blog is in part my outlet for ideas that come streaming into my mind about programming languages. In fact, it’s my only outlet. The more specialized you get, the fewer and fewer people you can find who consciously value your field; most people don’t even know it exists. Consequently, I would just love to have someone to bounce ideas between who actually has a clue of what I’m talking about.

That said, I’m not actively working on something at the moment, and I think that warrants an explanation.

Back when I started programming, I primarily liked to make things that were useful on a day-to-day basis. In making such things, I started to see patterns and got the idea of making tools to make creating them easier and quicker. That eventually led me to programming languages, the medium of expression of all software. As a result, I spent practically my entire undergraduate career studying type-theory and compilers.

To this day, I am more convinced than ever that PL design is one of the most important things to {software as we know it}.

However, given all that, tool-making can be a huge side-track. Because like all things, there is a pattern of pattern-discovery and tool-making.

  1. You use the tools you have to solve a problem.
  2. You discover patterns in the use of those tools.
  3. You make new tools that eliminate those patterns.
  4. You return to step 1 with your new tools.

But if you’re like me and see patterns everywhere, you could end up spending most of your time in step 3, not step 1. Especially when you see patterns in the tool-making process and start to recurse on sub-goals. And if your ultimate goal is to solve your original problem, there’s something wrong.

The bottom line is that hard work — sometimes an obscene amount of it — pays off if it’s focused on your goal. And the whole tool-making branch only makes sense towards your bottom-line if {the amount of work spent making your tool} plus {the amount of work it takes to accomplish your goal with the tool} is less than {the amount of work it would take without the tool}. This simple accounting formula has told me that making compilers and new programming languages is just not worth it given my goals. The cost of making a compiler and all the tools necessary to be productive with a new language is just so high that it dominates the inequality.

This can be frustrating, especially for someone who wants to create something beautiful.

But it is a matter of practicality.

Now, this may seem like an elaborate excuse to not work on something hard and important. But to me, it is the reason why I should work on other hard and important things.

I’m not trying to discourage anyone from working on compilers or designing new programming languages. On the contrary. If your goal is to make the best programming language in the history of programming languages — a so-called hundred-year language — then by all means, work on that! Seriously, please do; we are all in need.

All I’m really saying is that everything has an opportunity cost, and you should evaluate everything in terms of your goal, whatever that may be. Sometimes that means putting aside your own desires, like the desire to make a tool. But in the end, it’s worth it.

Tags:
9th
2007
Dec
permalink

1, 2, n

It’s the 1, 2, n rule in software development.

You do something once. You do something twice. And the next time you do it, you generalize it for all times henceforth.

Waiting longer to generalize is painful at best. In the worst case, it leads to maintenance nightmares as when the same thing is done 14 times in 14 different places, each time slightly different than the rest.

Generalizing too soon however leads to other problems. Everyone’s done this at one point in their coding career or another. Hey, this is a great idea! Why hasn’t someone else done it before? I’ll code it completely general the first time around so that it’ll be done right, from the start. And then a few weeks of coding go by, you start to actually use it, and you find out the hard way that either it was a really bad idea or the API is so terrible that you have to re-write it. Because basically, you didn’t iterate your design.

Code is all about design. And good design is re-design.

This is a big part of the reason why programming languages are so far behind. The amount of work that it currently takes to create (a compiler for) a programming language is so high, that the time it takes for a language designer to get feedback on his design is years. But by that time (if he was lucky enough to actually get users), he’s already invested so much into it that he is unlikely to throw it away and start from scratch — something a good designer must do. On top of that, even if he wanted to start from scratch, it would take many months to de-program his mind from thinking in the previous language. (Like the first time a Java programmer learns Haskell and tries to write a loop!)

An IDE is part of a language. Error messages are part of a language. Interacting with other systems, written in other languages, is part of a language. They’re all part of the interface of the language. And the interface is everything.

It’s a chicken-vs.-egg problem though. Because to get all these tools made, based on history, requires that a language be popular. But how do you get people to use a language if it doesn’t have the most basic tools like a debugger?

JavaScript is doing it right now. JavaScript tools — which make using the language no longer so painful — are cropping up everywhere these days. And the way it did that was by being the only choice if developers want their code to be executed in a browser window. Most languages aren’t so lucky. So what is a language designer to do?

As a designer, I can’t simply make a command-line compiler for my language. I have a vision of what editing code is like. And it’s not with a text-editor. The closest thing I’ve ever seen to a true code-editor is Eclipse for editing Java code, but Eclipse has been around long enough for us to learn a few lessons. I can already hear the Emacs junkies crying about how Emacs is the ultimate editor. But again, it’s still a glorified text-editor.

A true code-editor must absolutely be aware of the semantics of the language you are editing. It is inseparable from the compiler. Because the compiler knows how to interpret your code, but when something is amiss, it must be able to feed back to the programmer.

Writing code is all about design. And a designer needs feedback — as much feedback as possible. The quicker he gets that feedback the better, because he can make any necessary changes to the design only after he’s gotten feedback on his current design.

So any code-editor that doesn’t give immediate feedback on parse errors and requires that you do a full build is simply crap! How can you possibly work like that?! Ditto for type errors, link errors, and test-assertion errors. People see it as obvious when the old-timers tell stories about how they used to submit compilation jobs to mainframes on punch-cards, only to find out the next day that they forgot a semicolon and had to re-submit the entire job. Yet when it comes to where we are, here and now, people are blind.

Frankly, I’m getting tired of it. I want to see test data flow through my code as I write it and the unit-test results change from red to green as I fix a bug. I want to be able to highlight two blocks of code and have my code-editor factor out a function, replacing the two blocks with applications. I want bottlenecks to be highlighted in-editor based on profiling results. And compilation should be done completely in the background so I don’t have to wait after making a change to use it. And this read-eval-print-loop that’s all the rage nowadays might actually be useful if I could use it in the context of a particular point of my code.

Is it really that hard? Someone please tell me I’m a visionary because I can’t believe that this can’t be done. Nothing a little universal substitution and computing power can’t solve.

16th
2007
Sep
permalink

PL Feature Wish List

Here’s my programming language feature wish list:

  • Parallelism using list comprehensions like nesl
  • Seamless parallelism over a network like erlang, with ability to extend with encryption/security
  • Turing-complete type-system like Qi, but with implicit runtime verification
  • Turing-complete macros like Lisp (i.e. extending the compiler), but maybe just the ability to have function parameters optionally evaluated which would give you the equivalent, assuming all computations that can be evaluated at compile-time are
  • Expose the interpreter/compiler for meta-circular evaluation
  • Arbitrary meta-data like Java annotations or .NET attributes on all source code nodes, not just functions.
  • Full source reflection at runtime, compile-time, anytime (although this might not be necessary if the IDE has scripting — see below)
  • In other words, blur distinction of compile/run/etc. times
  • Bi-directional mappings for implicit parsing and printing (i.e. syntax skinning) with ability to use them inline (which would, among other things, give you the usefulness of reader macros)
  • Seamless FFI and importing of Java libraries — we have to build on top of something so it might as well be the thing with the most well-developed libraries to make the language a viable option for production code
  • Seamless interaction with the native platform when needed
  • Infinite data structures like Haskell — I guess this means lazy evaluation
  • Seamless language-integrated persistence
  • Type inference with useful error messages when my programs fail to type-check
  • Namespaces — I’ve never seen a language sustain large applications without these, and many languages have failed to catch on
  • Modules and true functors (i.e. functions over modules)
  • Type-classes — they’re really useful
  • User-definable monads
  • Examples/test cases in source

An IDE with:

  • Searching by function type-signature, which will also return functions that can be used with simple coercion functions on the parameters (e.g. using stringOfExp : exp -> string as an implicit coercion)
  • Graphing of module and function dependencies so I can see where the entry-points are when I am trying to look for something in a module I’m not familiar with
  • Profiling which feeds back into the editor (color-coded perhaps) so I can see where I should optimize when I’m editing
  • Scripting like Emacs but in the language that it edits, not a variant of Lisp or some other random language that the editor happens to be written in
  • Incremental background compilation with automatic dependency analysis
  • Knowledge of the abstract syntax tree to allow for (scripted) refactoring. In fact, why not define parts of the language (or skin) as mappings between UI events and semantics, taking the idea of bridging the gap between abstract syntax trees and the user even further.
  • Type inference that feeds back into the editor, as if you had typed it in
  • A graphical window designer like Visual Studio’s Form Builder or Xcode’s Interface Builder
  • Toggling of debug output which is structured, not just text, so that more information can be expanded without re-running. Toggling should be optionally automatic by tracking which pieces of code have changed recently and only showing dataflow affected by the changes.

A library with:

  • Good data-structures (e.g. hash tables, balanced trees, heaps, graphs, etc.)
  • Hash functions on common types and typed encryption algorithms (i.e. an 'a rsa_encrypted type) with an interface that makes them easy to use (i.e. no coding cost and little knowledge of encryption algorithms required)
  • A hardware/platform abstraction layer for every-day drawing using a box-model similar to CSS

Notice that just about everything here is agnostic to what the actual expressions of the language are.

Is this technically feasible? Absolutely. Just about all of it’s been done before in one form or another.

Is this realistic? No. But no one ever made it anywhere interesting without dreaming big.

I’ll add to this post’s list when I think of something else.

Tags:
31st
2007
Aug
permalink

Static Vs. Dynamic Typing

Static vs. dynamic typing. Here’s my ravings.

Being thoroughly trained in ML, I understand the benefits of static typing. Here are some claimed benefits.

  1. Reduced CPU usage since types don’t need to be checked at runtime
  2. Reduced memory usage since types don’t need to be stored at runtime
  3. Other type-directed optimizations
  4. Discovery of errors as early as possible, for all my code
  5. Static typing is more powerful/expressive since dynamic typing is a special case, or subset, of static typing where everything is tagged

Now let’s look at the claimed benefits of dynamic typing.

  1. Easier to run and test since there are practically no compile-time or link-time errors
  2. Programs are not limited by the expressiveness of the type system of the language — e.g. heterogeneous data structures w/o explicit tagging
  3. Allows for implicit open recursion, late binding, dynamic scope, and duck typing
  4. Programs are simpler since I don’t have to worry about types. Things are often coerced implicitly making my programs more concise, and I don’t have to use really abstract things like type variables and higher-order types. In other words, they have been abstracted away by being pushed down to a place I don’t have to think about.
  5. Faster prototyping due to not having to write down types which change often during prototyping

There are a few claimed benefits, however, which are not true. Allow me to refute.

Myth: Static typing is more powerful/expressive since dynamic typing is a special case, or subset, of static typing where everything is tagged.

It’s true that dynamic typing is a special case of static typing. This can be seen by simply creating a tagged union where each branch is a different possible runtime type. Then make every expression in your language a value of this type. Voila — dynamic typing.1

However, it does not follow that this makes static typing more powerful. To see this more clearly, let’s use a simpler example. C vs. assembly. C is more powerful than assembly. But there are things you can write in assembly that you simply can not write in C. Because you are restricted. Restricted by the definition of the C language which does not know anything about your cool new machine instruction to factor large numbers and break your gramma’s closely-guarded RSA-encrypted banana nut bread recipes. However, anything you can write in C, you can obviously write in assembly. So C is a special case, or subset, of assembly.

In other words, the more powerful language is the one that is the subset.

And this does not depend on the specifics of the language. So in the case of dynamic vs. static typing, dynamic typing is actually more powerful. I alluded to this already by the fact that dynamic typing abstracts away types by pushing them down into the runtime system.

Myth: Dynamic typing allows for faster prototyping due to not having to write down types which change often during prototyping.

This myth stems from the belief that types in dynamically typed languages don’t have to be written down at all, while types always have to be written in statically typed languages. And this is absolutely false!

First of all, even though you don’t have to declare the types of variables and functions in dynamically typed languages, you still write down some types. Whenever you make a class, declare fields or methods of a class, or specify inheritance relationships, you are creating and/or specifying types.

Secondly, type information does not need to be specified in statically typed languages that include type inference. Many languages with type inference like ML and Haskell are specifically designed so that you don’t have to write down any types. The only exceptions are enumeration-like data structures (i.e. tagged unions) which have runtime values. So the difference between the amount of type information you must write depends greatly on the language itself, not whether that language has static or dynamic typing.

So we are down to the following for static typing.

  1. Reduced CPU usage since types don’t need to be checked at runtime
  2. Reduced memory usage since types don’t need to be stored at runtime
  3. Other type-directed optimizations
  4. Discovery of errors as early as possible, for all my code
  5. Static typing is more powerful/expressive since dynamic typing is a special case, or subset, of static typing where everything is tagged

And for dynamic typing.

  1. Easier to run and test since there are practically no compile-time or link-time errors
  2. Programs are not limited by the expressiveness of the type system of the language — e.g. heterogeneous data structures w/o explicit tagging
  3. Allows for implicit open recursion, late binding, dynamic scope, and duck typing
  4. Programs are simpler since I don’t have to worry about types. Things are often coerced implicitly making my programs more concise, and I don’t have to use really abstract things like type variables and higher-order types. In other words, they have been abstracted away by being pushed down to a place I don’t have to think about.
  5. Faster prototyping due to not having to write down types which change often during prototyping

Some people have counterarguments as to why these are not real benefits. I will go through some of them.

Myth: The cost of runtime type information in speed or memory is not significant, especially with today’s hardware.

In general keyboard-speed applications, the cost of runtime type information is not significant. But in core loops, it can be huge. Because it is not just a matter of checking the types, which itself can be very costly in a loop. But the fact that you must store everything as a tagged value means that everything must be stored as a pair in memory. This means that in dynamically typed languages, there is no way to specify the computation of adding the values in two registers and storing the result in another register. The add operation in a dynamically typed language must load a tag from memory, conditionally branch on the value of the tag, load a 2nd tag from memory, conditionally branch on the value of that tag, load the first number from memory, load the 2nd number from memory, add the numbers, store the result in memory, and then store a new tag. This is ignoring the case where the runtime system has to allocate memory for this new object, as the statically typed version may spill registers, so call it even.

For such a simple operation though, this is a huge cost.

Myth: Type-checking is a substitute for testing.

Again, absolutely not true. Although some people may use type-checking (static in particular) for testing program correctness, this is neither its intended purpose nor its limit of use. Type-checking is more about ensuring meaningful, well-defined execution than ensuring correctness. Even though you may hear strong static-typing advocates say, “When your program type-checks, you’ll often find that it just works”, this is simply not true for large, intricate programs. Although type-checking may help you find errors, it is not the same as testing. Thus, it is not a suitable substitute for testing.

Static type-checking will however discover one entire class (or a few classes) of errors at compile-time, early in the development cycle — something that is dependent upon executing every code path in a dynamically-typed language, which is simply infeasible.

Other Issues

Open Recursion, Late Binding, Dynamic Scope

Open recursion and late binding are just special cases of dynamic scope. It’s not clear whether having these features is a good thing or not. They make it easy to introduce bugs when extending code in a way that was originally not intended (which happens fairly often). They can definitely be useful though since they allow you to do things that can’t be done easily without the features. But as I explained earlier, more flexibility does not imply power. In fact it’s the opposite, as long as the constraints are well-designed. Except in bottlenecks where optimization is crucial, Duff’s device is a bad feature for a language to support. And although you can implement open recursion in a statically typed language, you can’t implement dynamic scope in general (and be type-safe) without converting your entire program to use tagged values, which would be implementing the entirety of dynamic typing. So in this respect, dynamic typing is actually more flexible.

Some may argue that the language should not constrain the programmer. Let him decide whether or not to use it. After all, exceptions are a special case of dynamic scoping, and many statically-typed languages support this. The problem is that you will inevitably be using or extending someone else’s code. And the person who wrote that code will inevitably make a judgment call that you consider to be poor, making the code non-obvious and unnecessarily complicated from your perspective — which can really be said about any powerful language feature, so maybe it’s just me. But this one can make it especially difficult to reason about your programs. …Again, it’s not completely clear whether these features are good or bad.

Type-Directed Optimizations

If you care about performance, which you will for just about any production app, type-directed optimizations can help you a great deal. I won’t go into the details of how they work, but the basic idea is that the compiler uses type information to optimize your code. Theoretically, you could have a compiler for a dynamically-typed language that performed the same optimizations. However, to do this at compile-time, it would have to collect the same information as a compiler for a statically-typed language. I’ve never seen a compiler able to do this in general, but such a hybrid is the future of typing.

An alternative is for the compiler to do the optimizations at run-time — i.e. just-in-time.

The problem with static typing is that there is always more type-expressiveness that would be nice to add, and so more and more complicated type systems emerge, oftentimes complicating a language with quirks such as ML’s value restriction. On the other hand, if dynamic typing were used, the problem causing such complication would disappear. A simple solution is often the best.

Types in the Source Code

Dynamic-typing advocates claim that writing down types is a hindrance, especially during prototyping. This stems from the assumption that you have to do additional work to think of and change the types of the expressions you’re dealing with.

I definitely understand the criticism of writing down types because I don’t usually write down loop invariants, so I could see that as being “extra work”. But types are no different. Both types and loop invariants are required to create the program, but not to execute the program.

For anyone who’s become proficient in a language with a powerful type system, he knows that in order to even write a meaningful program, you have to use the correct types. And so when you’re writing the program, you implicitly (and unconsciously for some) give a type to every single expression you write. So why not write down all this extra information that was used to create the program so that it can be used not only by the compiler, but by you when you come back to your code after a week. The same goes for invariants — loop invariants in particular — but I’ll save that for another post on theorem provers!

Knowing what I know about theorem-proving and loop invariants, I believe writing down the types is the right thing. However, in a prototyping situation where the types are changing faster than you can type them, you shouldn’t have to write them down. That’s where type-inference comes in. But as the system begins to stabilize — as some parts which are getting pushed down in the hierarchy do — you should write the types down, because at that point you know what the types should be, and the extra information can be used by the compiler to check and optimize, and by those reading your code to understand your program’s interfaces and semantics.

Typing of the Future

Here’s the typing hybrid I see as the future, as the answer is never on either end of a spectrum, but a balance somewhere in the middle — a pattern of the Universe.

  1. The flexibility to omit the vast majority of types, but consequently,
  2. a significant amount of type-inference that uses
  3. a powerful type system that is so expressive that it is itself Turing-complete, which requires
  4. blurring the lines between compile-time and runtime to the point where the programmer can not always tell (and should not always care) whether static or dynamic typing is being used by the compiler.

And we will get there by pushing on.


1. This is where the “un(i)typed language” joke comes from. A language that is “untyped” really has exactly one type for all expressions.
Tags: