Tuesday, October 16, 2007
I have a friend who is taking a first-year computer science course. She's come into this course without any programming knowledge. Her school requires her, for her degree, to take a number of introductory courses for various science disciplines -- she isn't going to take another computer science course after this one.
This introductory comp sci course teaches Java. Java, as you probably know, is a language in which the canonical "Hello, world!" program takes seven lines of code and requires you to invoke the following concepts:
Now, this need not be an inherently insurmountable stumbling block -- I wouldn't go so far as to say that it is impossible to effectively teach programming in such an environment. But I did see one of her practice midterms. It tested her on the following concepts:
Man, I'll say.
Is this not self-evidently ludicrous to anyone else? Does it ever occur to anyone that the way to
teach programming is not first to hope that they already know enough to get by, and then indoctrinate them into the cult of OO before they learn how to actually make the computer do anything? How is this better than learning Pascal? ARGH.
This introductory comp sci course teaches Java. Java, as you probably know, is a language in which the canonical "Hello, world!" program takes seven lines of code and requires you to invoke the following concepts:
- Classes
- Static methods
- Command-line arguments
- Visibility
Now, this need not be an inherently insurmountable stumbling block -- I wouldn't go so far as to say that it is impossible to effectively teach programming in such an environment. But I did see one of her practice midterms. It tested her on the following concepts:
- Conversion rules from double to int
- Filling in the correct types for the methods in some class
- What happens when two variables point at the same object
- Writing code to perform some calculation
- Filling in methods that mutate some object
- What kinds of methods take "an implicit parameter" (which after some Googling, I determined meant the "this" pointer)
- Calling some methods on an object whose API was defined in two brief comments
- If statements
- Loops
Man, I'll say.
Is this not self-evidently ludicrous to anyone else? Does it ever occur to anyone that the way to
teach programming is not first to hope that they already know enough to get by, and then indoctrinate them into the cult of OO before they learn how to actually make the computer do anything? How is this better than learning Pascal? ARGH.
Friday, June 16, 2006
Sometimes I can't help myself from trying to be the voice of reason in inane Slashdot threads. It's a curse. Recently one came up entitled, "Staying On-Top of Programming Trends?"
My response:
By the by -- I'm now working for Intentional Software. Since what we're building is still pretty mysterious to the world, and my work now aligns pretty closely with my personal interests, I should mention that, obviously, the opinions and shit expressed on this blog are mine and do not represent those of my employer. There's a seperate blog for that, anyway.
For the record: We are building some extremely cool things.
"Trends are constantly changing, upgrading, or become popular due to high end user demand or just basic usefulness. I do my best to keep up with the trends, believing that for the most part they will be better then the current methods in place, or just comfort in knowing that if enough people use it, that there will be allot of help out there. Ultimately though, its keeping up with these trends and trying to figure out what's a fad versus what's actually useful that's the difficult part. What do some of you do to keep up with the trends? Websites? Magazines such as Dr. Dobbs? Forums? I know there's not one solve all, but for the sake of argument, suppose you wanted to stay on the forefront of Java based web development, what would you do?"
My response:
You want to know the latest trends for Java-based web development? Fewer and fewer people are going to be doing Java-based web development in the future.
Fuck trends. They're wrong. Every day the industry continues to stay with its current ridiculous technologies when vastly superior ones were invented decades ago infuriates me further. If it doesn't infuriate you, you're not paying close enough attention.
My advice: read Lambda the Ultimate and Steve Yegge's blog. Endeavor to learn what the lambda calculus and referential transparency are. If you are sincerely interested in bettering yourself as a programmer and don't go find out who Alonzo Church was then so help me God I will kick you in the balls. Learn about SML and type inference. Learn about Haskell and monads. Learn about process calculi and Erlang. Learn about Lisp and code generation and domain-specific languages. Learn about Scheme and lexical closures and continuations. Learn about Smalltalk and what OO was really supposed to be. Learn about type theory and formalism and the Curry-Howard correspondence. Learn about Forth and Joy and how you can have a powerful, expressive language without even so much as a grammar. Learn about Intercal and Befunge and just how badly your choice of programming language can torture you. Learn about UML and Ruby on Rails and Seaside and agile programming and Java generics and Python generators. Learn about aspect-oriented programming, context-oriented programming and concept programming. Learn about multi-paradigm languages like OCaml or Oz. Learn about weird Lisp dialects with syntax like Rebol or Dylan.
Realize that library design is language design. Realize that asynchronous programming with callbacks and explicit state in a world where lightweight coroutines were around in the days of fucking Simula in the 60s for Christ's sake is cruel and unusual torture. (Sorry, pet programming construct.) Realize that the programming language research community, while considering systems programming a solved problem and generally not interested in talking about human factors, is doing some genuinely promising work. Did you know that there are concurrency models with strong type systems that can prove your system will never deadlock? Isn't that fucking cool?
That ought to keep you busy for a little while.
By the by -- I'm now working for Intentional Software. Since what we're building is still pretty mysterious to the world, and my work now aligns pretty closely with my personal interests, I should mention that, obviously, the opinions and shit expressed on this blog are mine and do not represent those of my employer. There's a seperate blog for that, anyway.
For the record: We are building some extremely cool things.
Friday, March 31, 2006
I'm always interested in the challenging of conventional wisdom. So I consider the 40-minute presentation I just watched on Immediate Mode GUIs to be time well spent.
Essentially: In a situation where your application is going to be constantly updating the screen -- ie, a videogame -- you can make some serious optimizations from the traditional object-oriented GUI paradigm which make your code a lot more clear. Rather than having event-driven binding, where you've got callbacks everywhere to update your model when the GUI state changes, and update your GUI when your model state changes, you draw the widget every frame, with parameters telling the library what should be in it, and get out of that single function call a response with which you can immediately act on.
Instead of Create-Update-Destroy, it's one call to DoWidget, which may return an interesting value if the user's done something interesting to it (clicked a button, etc). The logic is simplified immensely. For optimization purposes, you can still do caching on the library side (so you don't have to rewrap the text in an edit box every frame, for example).
I've recently had some excellent results applying coroutines to GUIs (I've reached a point where I think hand-coded state machines are almost totally worthless if you have coroutines -- unless you want to serialize or look into the state of a task); now I'm curious if I can combine the two ideas.
Essentially: In a situation where your application is going to be constantly updating the screen -- ie, a videogame -- you can make some serious optimizations from the traditional object-oriented GUI paradigm which make your code a lot more clear. Rather than having event-driven binding, where you've got callbacks everywhere to update your model when the GUI state changes, and update your GUI when your model state changes, you draw the widget every frame, with parameters telling the library what should be in it, and get out of that single function call a response with which you can immediately act on.
Instead of Create-Update-Destroy, it's one call to DoWidget, which may return an interesting value if the user's done something interesting to it (clicked a button, etc). The logic is simplified immensely. For optimization purposes, you can still do caching on the library side (so you don't have to rewrap the text in an edit box every frame, for example).
I've recently had some excellent results applying coroutines to GUIs (I've reached a point where I think hand-coded state machines are almost totally worthless if you have coroutines -- unless you want to serialize or look into the state of a task); now I'm curious if I can combine the two ideas.
Friday, March 17, 2006
So, over the past little while, for a variety of reasons, I've been learning Forth. It's a bizarre, wrongheaded little language that somehow manages to mostly turn its weaknesses into strengths. I can see how its inventor, Chuck Moore, was seduced by it, and by the philosophy of total simplification -- once he simplified the problem of the compiler to ridiculous extremes, it fell apart in his hands and he was left with a rather powerful little language.
Some quick background:
Forth, as a language, is really, really simple. It's so simple that there isn't syntax, or even a parse tree. The Forth parser simply reads some sort of input until it comes across a space, looks up this "word" in its internal dictionary, and executes its definition. Argument passing is done implicitly, on the stack. You can think of Forth as a language where even data is code; "1" is literally a word that means, "push '1' onto the stack."
What this ends up meaning is that Forth is an aggressively postfix language. To say
What this suggests to me is that the most fundamental choice that every general-purpose programming language must make is how it will provide a way for abstractions to communicate.
Let's back up a moment and make sure this is clear. The things that make code Forth-like are the things that make it play the nicest with other Forth code. On an informal level, this means taking advantage of the language features available to you when it is appropriate. These features are built to work smoothly with the language's built-in abstractions; when everybody uses these abstractions, everybody works smoothly with everybody else.
Now, why is this interesting to me? Well, I firmly believe that the next important evolution in software development will only become possible when we have the ability to create domain-specific languages with a hell of a lot more ease than we currently can. We need not only to be able to pick and choose the abstractions that are relevant to the problem at hand, but to create new ones that play well with others. We need the ability to write code in the language of the problem. And rather than merely waiting patiently for XL, IP, or MPS to bear fruit, I like to think about what exactly the problem entails and how it could best be solved.
The biggest concern, in developing these kinds of systems, is -- how can you develop a system to be completely flexible and extensible, when all of your components need to interact? You have to decide on some method for your abstractions to communicate, and you are building your entire system on the premise that you'll never be able to predict all of the needs of your users. If you just pick one, as Forth does, you constrain yourself. You create problems that are unnecessarily difficult to solve, because you have to convert them into this protocol.
Some of you are probably screaming LISP! and LAMBDA CALCULUS! at me, like I was a great big moron for not having considered the mathematically pure options. Why, if your language gives you access to continuations and macros, there's nothing you can't build! Except... you know... embedded systems.
Sometimes, I need to be able to talk von Neumann. Sometimes, I need statically-calculated everything and no runtime. Sometimes, I cannot abide by garbage collection. And it kills me that I can't do that and use real coroutines, which are safer, more efficient, and exhibit way better realtime behaviour than threads.
So I desire an environment that will allow me to drop into low-level abstractions when I need to. Not necessarily "inline assembler", or "C code that interfaces with Lisp", or whatever -- I've written hard-realtime interrupt handlers in SML, just to see if it could be done. I just need the ability to use the appropriate level of abstraction, and I would really, really like to use the appropriate syntax at each level. Imagine an application consisting of a bunch of towers of abstraction, connected only at the bottom levels, and you'll see the issue that I'm struggling with.
Built-in language constructs have traditionally had three advantages over user-defined abstractions:
#3, though, is important to think about further. An extensible development environment can take one of two approaches: It can provide a monolithic language core that all extensions build on, or it can attempt to build even the core elements of its language out of reusable, reconfigurable components. While the second approach is the more flexible, it comes with its own set of problems.
The big problem that I see is this -- any user-written abstractions are probably going to build on some base-level abstractions. However, you don't necessarily want to inherit an entire language just to use a single feature, and you want extensions to be able to work well with each other.
In essence, this is literally cross-platform development -- building an abstraction that can sit on top of different kinds of abstraction towers, and building bridges between these towers when useful. This already happens routinely on the macro level; a hundred different language communities go and re-implement bindings for their own language for some useful C library. It would be kind of nice if this effort could be reduced or even eliminated, because it doesn't SEEM like a fundamentally hard problem.
Consider the design difficulty, if you wanted to implement a portable abstraction for lazy evaluation. Do you assume you've got continuations? Or do you build it on less powerful abstractions? You could probably build it on top of something coroutine-like, but there's just so many coroutine-like abstractions out there! Python's got its generators, Ruby's got its blocks, and some people won't settle for anything less than Yield the Magnificent. They're all sort of functionally similar, but there's no standard conceptual map to build on. In the end, your nice feature that make things clean and concise ends up being intractably ugly to actually implement.
That can't be what developers will actually end up doing; if they developed their whole project that way, for total reusability, it would be a collossal waste of effort and wouldn't make anything even remotely easier. Developers will pick a set of abstraction towers that make sense for their problem, and build on top of them.
In the end, you can't imagine an ecology of abstractions producing a single instance of an abstraction that will play nicely with all others. It can't and won't work that way. You will still see strong semantic platforms emerge, just like our current situation with lots and lots of languages incorporating lots of different ideas. Though abstractions are coupled to each other by nature, decoupling them from a standard language syntax makes it easier to build natural abstractions, without having to resort to writing your own compiler and designing a whole new language from scratch.
Essentially, just as there is no One True Abstraction for everyone to build on (no everything is not an object), there is no One True Abstraction through which other abstractions should communicate.
Some quick background:
Forth, as a language, is really, really simple. It's so simple that there isn't syntax, or even a parse tree. The Forth parser simply reads some sort of input until it comes across a space, looks up this "word" in its internal dictionary, and executes its definition. Argument passing is done implicitly, on the stack. You can think of Forth as a language where even data is code; "1" is literally a word that means, "push '1' onto the stack."
What this ends up meaning is that Forth is an aggressively postfix language. To say
1 + 2, you pass two arguments to + on the stack by writing 1 2 +. Now, Forth is flexible enough that you could rewrite + to read the next word from the input buffer and execute it first, or parse it as a number, or whatever, but it's unForthlike and causes problems. For example, what if the word on the right side of + causes two items to be put onto the stack? It just doesn't play nice with Forth's model of the world, and addition is too fundamental an operation to not play nice.What this suggests to me is that the most fundamental choice that every general-purpose programming language must make is how it will provide a way for abstractions to communicate.
Let's back up a moment and make sure this is clear. The things that make code Forth-like are the things that make it play the nicest with other Forth code. On an informal level, this means taking advantage of the language features available to you when it is appropriate. These features are built to work smoothly with the language's built-in abstractions; when everybody uses these abstractions, everybody works smoothly with everybody else.
Now, why is this interesting to me? Well, I firmly believe that the next important evolution in software development will only become possible when we have the ability to create domain-specific languages with a hell of a lot more ease than we currently can. We need not only to be able to pick and choose the abstractions that are relevant to the problem at hand, but to create new ones that play well with others. We need the ability to write code in the language of the problem. And rather than merely waiting patiently for XL, IP, or MPS to bear fruit, I like to think about what exactly the problem entails and how it could best be solved.
The biggest concern, in developing these kinds of systems, is -- how can you develop a system to be completely flexible and extensible, when all of your components need to interact? You have to decide on some method for your abstractions to communicate, and you are building your entire system on the premise that you'll never be able to predict all of the needs of your users. If you just pick one, as Forth does, you constrain yourself. You create problems that are unnecessarily difficult to solve, because you have to convert them into this protocol.
Some of you are probably screaming LISP! and LAMBDA CALCULUS! at me, like I was a great big moron for not having considered the mathematically pure options. Why, if your language gives you access to continuations and macros, there's nothing you can't build! Except... you know... embedded systems.
Sometimes, I need to be able to talk von Neumann. Sometimes, I need statically-calculated everything and no runtime. Sometimes, I cannot abide by garbage collection. And it kills me that I can't do that and use real coroutines, which are safer, more efficient, and exhibit way better realtime behaviour than threads.
So I desire an environment that will allow me to drop into low-level abstractions when I need to. Not necessarily "inline assembler", or "C code that interfaces with Lisp", or whatever -- I've written hard-realtime interrupt handlers in SML, just to see if it could be done. I just need the ability to use the appropriate level of abstraction, and I would really, really like to use the appropriate syntax at each level. Imagine an application consisting of a bunch of towers of abstraction, connected only at the bottom levels, and you'll see the issue that I'm struggling with.
Built-in language constructs have traditionally had three advantages over user-defined abstractions:
- A custom syntax that is more comfortable to express problems in.
- Access to compile-time abstractions.
- Ubiquitousness.
#3, though, is important to think about further. An extensible development environment can take one of two approaches: It can provide a monolithic language core that all extensions build on, or it can attempt to build even the core elements of its language out of reusable, reconfigurable components. While the second approach is the more flexible, it comes with its own set of problems.
The big problem that I see is this -- any user-written abstractions are probably going to build on some base-level abstractions. However, you don't necessarily want to inherit an entire language just to use a single feature, and you want extensions to be able to work well with each other.
In essence, this is literally cross-platform development -- building an abstraction that can sit on top of different kinds of abstraction towers, and building bridges between these towers when useful. This already happens routinely on the macro level; a hundred different language communities go and re-implement bindings for their own language for some useful C library. It would be kind of nice if this effort could be reduced or even eliminated, because it doesn't SEEM like a fundamentally hard problem.
Consider the design difficulty, if you wanted to implement a portable abstraction for lazy evaluation. Do you assume you've got continuations? Or do you build it on less powerful abstractions? You could probably build it on top of something coroutine-like, but there's just so many coroutine-like abstractions out there! Python's got its generators, Ruby's got its blocks, and some people won't settle for anything less than Yield the Magnificent. They're all sort of functionally similar, but there's no standard conceptual map to build on. In the end, your nice feature that make things clean and concise ends up being intractably ugly to actually implement.
That can't be what developers will actually end up doing; if they developed their whole project that way, for total reusability, it would be a collossal waste of effort and wouldn't make anything even remotely easier. Developers will pick a set of abstraction towers that make sense for their problem, and build on top of them.
In the end, you can't imagine an ecology of abstractions producing a single instance of an abstraction that will play nicely with all others. It can't and won't work that way. You will still see strong semantic platforms emerge, just like our current situation with lots and lots of languages incorporating lots of different ideas. Though abstractions are coupled to each other by nature, decoupling them from a standard language syntax makes it easier to build natural abstractions, without having to resort to writing your own compiler and designing a whole new language from scratch.
Essentially, just as there is no One True Abstraction for everyone to build on (no everything is not an object), there is no One True Abstraction through which other abstractions should communicate.
Sunday, March 12, 2006
I just killed a couple of days reading Stevey's Drunken Blog Rants, which is a fascinating account of a talented engineer spending a year searching for better languages, and finding Lisp in the end. Lots of interesting insights -- I found his essays on practicing programming and math every day to be particularly inspiring, since I've already found Lisp, and Ruby, and OCaml, and Erlang, and pretty much all of the other languages that are mentioned. Except Rebol. That one was new.
I've resolved to read more of the CompSci books that, for no good reason, I haven't gotten around to reading. I think I've somehow tricked myself into thinking that because Christophe de Dinechin hasn't written a book about Concept Programming, that there are no books out there that have ideas worth thinking about. Obviously this is fallacious. Just because I might consider the idea that everything is an object to be -- and I'm quoting here from Czarnecki and Eisenecker's Generative Programming -- "profane", doesn't mean I have any excuse for not having read Design Patterns. Hell, it's not as if that's stopped me from toying with Smalltalk. If you've got any great CS books to recommend, please do so in the comments!
I just rediscovered an excellent post about Forth and abstraction, sitting in draft form, from last June. I'm going to go clean that one up and post it.
I've resolved to read more of the CompSci books that, for no good reason, I haven't gotten around to reading. I think I've somehow tricked myself into thinking that because Christophe de Dinechin hasn't written a book about Concept Programming, that there are no books out there that have ideas worth thinking about. Obviously this is fallacious. Just because I might consider the idea that everything is an object to be -- and I'm quoting here from Czarnecki and Eisenecker's Generative Programming -- "profane", doesn't mean I have any excuse for not having read Design Patterns. Hell, it's not as if that's stopped me from toying with Smalltalk. If you've got any great CS books to recommend, please do so in the comments!
I just rediscovered an excellent post about Forth and abstraction, sitting in draft form, from last June. I'm going to go clean that one up and post it.
Friday, December 02, 2005
This first-person shooter written in Haskell makes me happy. I feel I should learn more about functional reactive programming; it looks like a fantastically useful technique. I'll definitely be reading that thesis.
Tuesday, November 08, 2005
I've seriously got four big, long posts, saved as drafts because the ideas aren't fully fleshed out in them. Sorry about that. Today I realized, the point here isn't to spring my brilliant fully-formed ideas out into the world; it's to help me work through them, and perhaps talk to some like-minded people who will smack me when my ideas are silly.
I have been really intrigued by the ideas explored by Dabble ever since I stumbled upon this blog, months ago. (One of my stored draft posts also mentions Seaside, another fantastically interesting bit of technology from the creators of Dabble.) I actually spent several weeks, back and forth on the bus to work, hashing out a design for an application much like it -- which, for various reasons, I never got around to implementing. I look forward to it existing without having to do the work of implementing it.
I have been really intrigued by the ideas explored by Dabble ever since I stumbled upon this blog, months ago. (One of my stored draft posts also mentions Seaside, another fantastically interesting bit of technology from the creators of Dabble.) I actually spent several weeks, back and forth on the bus to work, hashing out a design for an application much like it -- which, for various reasons, I never got around to implementing. I look forward to it existing without having to do the work of implementing it.