There's a lot of features of Ruby that I like, but there are some that just drive me nuts like blocks not taking blocks and the ampersand operator. Raganwald did a great job of explaining blocks, procs, and the ampersand in this blog post: Explanation of Ruby's Unary operator. I came away with the feeling, "Wow! It took that much explanation just to tell how to send blocks around?" If blocks were first-class citizens, Ruby would be more elegant. Raganwald would not be writing huge blog posts on block vs. proc because it would be unneeded.
Ruby has all it needs right now. Ruby 2.0 should be a pruning of features and removing special cases. Get to the things that help you concentrate on your design and not on the language. Ampersand is nothing more than noise in your code and brain. Remember elegant is simple.
Showing posts with label ruby. Show all posts
Showing posts with label ruby. Show all posts
Monday, June 30, 2008
Sunday, June 22, 2008
My Hopes For Ruby 2.0
I must admit I haven't been following Ruby 2.0 too closely lately. But, I since I've been using 1.8 quite a bit lately. I thought I would list the things that are in 1.8, but I hope are gone in 2.0. This is a prayer of good vibes.
OK, that's it for now. The above things would make life so much easier. I could lobby for taking away case statements and most keywords, but I think the above would be great to take the language forward without hurting too much.
- Make blocks first class citizens. So, no more & in your methods. Everything should be an object. Blocks are no exception in fact, they have more right than anyone. This will make blocks syntactically cheaper too. No more "lambda". Happiness is cheap syntactic blocks.
- Reflection on everything. Yes, this means the stack. It also means keep the source in memory as well as part of classes and blocks. This might could used for a lot of cool things (like interesting ways to traverse code). Don't worry. It doesn't add much memory.
- Make blocks accept the same kind of parameters as methods. Right now, you can not have methods take an implicit block (you know with the & in front). Wait a minute. If number one is satisfied, this will be too. Oh goody. Cheap blocks take away complexity. Isn't this reason enough to have them?
OK, that's it for now. The above things would make life so much easier. I could lobby for taking away case statements and most keywords, but I think the above would be great to take the language forward without hurting too much.
Friday, April 04, 2008
Monkey Patching
Monkey patching seems to be all the debate rage currently in the dynamic language blogosphere. The one that caught my attention was Gilad Bracha's in particular. I don't disagree with him at all. I know it's strange reading that from a Smalltalker. But, I view monkey patching like inheritance. It's a great device to have in your programmer back pocket, but can be dangerous. Use with caution and look at your options. Don't use it without thinking of the implications it can cause. The reason its dangerous is because it's a global change that widens the protocol of the affected object or worse changes the contract for an already existing method.
The main reason monkey patching is so dangerous is the global change it makes and the increased probability of having a collision if another project names a method the same. Or worse, another project overrides the default behavior and replaces it with their own. Both can cause subtle bugs that can be hard to catch and find. It's the global nature of it that can bite. It's convenient, but you should think twice before doing it. It's limits your project's options.
Groovy has a novel approach to monkey patching in that it allows them to be scoped for the duration of a block called categories. This reduces the risk of a global change and the addition is only in affect for the duration of the block. Now, it can cause issues if something gets called outside of the block by accident, which is why I wish it had different ways of scoping (class, package, etc). But, with AspectJ these problems can be reduced. Categories reduce a lot of the risk of monkey patching in that collisions are highly unlikely and the protocol is only widened for that invocation. It also ensures that unknown project dependencies will not creep in. I worry less about monkey patching in this instance because if you get burned, it will be your project not everyone else. I think categories are the way to go (if they added class scoped categories, I would be in heaven).
I don't hate monkey patching. Far from it. It is handy to override method implementations during debugging. I can change pre-existing methods if they have bugs in them even before the next release is out. I don't have to wait on the vendor. These are great pluses. I'm just advocating thought before you monkey patch in your own projects. Much like before you swing the inheritance hammer, you should do the same with monkey patches.
The main reason monkey patching is so dangerous is the global change it makes and the increased probability of having a collision if another project names a method the same. Or worse, another project overrides the default behavior and replaces it with their own. Both can cause subtle bugs that can be hard to catch and find. It's the global nature of it that can bite. It's convenient, but you should think twice before doing it. It's limits your project's options.
Groovy has a novel approach to monkey patching in that it allows them to be scoped for the duration of a block called categories. This reduces the risk of a global change and the addition is only in affect for the duration of the block. Now, it can cause issues if something gets called outside of the block by accident, which is why I wish it had different ways of scoping (class, package, etc). But, with AspectJ these problems can be reduced. Categories reduce a lot of the risk of monkey patching in that collisions are highly unlikely and the protocol is only widened for that invocation. It also ensures that unknown project dependencies will not creep in. I worry less about monkey patching in this instance because if you get burned, it will be your project not everyone else. I think categories are the way to go (if they added class scoped categories, I would be in heaven).
I don't hate monkey patching. Far from it. It is handy to override method implementations during debugging. I can change pre-existing methods if they have bugs in them even before the next release is out. I don't have to wait on the vendor. These are great pluses. I'm just advocating thought before you monkey patch in your own projects. Much like before you swing the inheritance hammer, you should do the same with monkey patches.
Saturday, January 05, 2008
Hugs
I think some folks in the Ruby community need a hug. If you find one, give them one and tell them it'll be OK. Not everyone can have a job doing Rails.
Thursday, October 18, 2007
Debuggers...again
Giles Bowkett recently asserted: Debugger support considered harmful. It seems this has been going around for years. See this old post of mine: Am I a Bad Programmer?. Nothing new. The assertion is that if you have tests, you don't need a debugger. Interesting. I'm heavy into testing my code. Anything I write even for fun has a test to go with it. And yet, I still use my debugger quite a bit.
Why? Well, it depends on what I'm doing. I use the debugger for exploratory programming, when sinking into the depths of someone else's code (especially if they didn't write tests), during code reviews, and when faced with a nasty bug. Even though I write my code in small pieces (small classes and small methods), I am not perfect. When everything is combined and put into production, strange things happen to my code. The code doesn't change; the environment does. A situation occurs that I didn't think of and thus it is not tested. This is the realm of the debugger especially if the bug is nasty and hard to find. Any piece of complex software has these hidden. We are human. Let's embrace that. A world without debuggers is a weird sci-fi perfect world with monsters lurking beneath the surface.
Debuggers are great for exploratory programming when you are just trying out a new framework and seeing how it works. I like to walk line by line. I did this when I was learning Seaside and it was better than any documentation. Besides, watching beautiful code unfold in your debugger is nothing short of reading a great book. And when you're dealing with some ugly code, a debugger has shown me things that my eyes deceived me on when just looking at the code. Why dissect the dead animal when I can see how its organs work while it is still alive?
An expressive debugger saves time as well. I remember when I came back to Smalltalk after doing Java for several years. I had forgotten that you never need to restart your programs. I wrote a simple Seaside application and only restarted the web server once. I coded live in the system and when I saw a problem (that my tests didn't catch) in the debugger, I could change it right there. It was amazing.
I guess all of this talk of no debuggers or that debuggers is a crutch for bad developers. It can enable the "get it running once" mentality instead of thinking of all the cases and your design. Like any tool it can be abused, but that doesn't mean we should get rid of all tools. Duck typing can be abused as well and I wouldn't trade it for the world.
Why? Well, it depends on what I'm doing. I use the debugger for exploratory programming, when sinking into the depths of someone else's code (especially if they didn't write tests), during code reviews, and when faced with a nasty bug. Even though I write my code in small pieces (small classes and small methods), I am not perfect. When everything is combined and put into production, strange things happen to my code. The code doesn't change; the environment does. A situation occurs that I didn't think of and thus it is not tested. This is the realm of the debugger especially if the bug is nasty and hard to find. Any piece of complex software has these hidden. We are human. Let's embrace that. A world without debuggers is a weird sci-fi perfect world with monsters lurking beneath the surface.
Debuggers are great for exploratory programming when you are just trying out a new framework and seeing how it works. I like to walk line by line. I did this when I was learning Seaside and it was better than any documentation. Besides, watching beautiful code unfold in your debugger is nothing short of reading a great book. And when you're dealing with some ugly code, a debugger has shown me things that my eyes deceived me on when just looking at the code. Why dissect the dead animal when I can see how its organs work while it is still alive?
An expressive debugger saves time as well. I remember when I came back to Smalltalk after doing Java for several years. I had forgotten that you never need to restart your programs. I wrote a simple Seaside application and only restarted the web server once. I coded live in the system and when I saw a problem (that my tests didn't catch) in the debugger, I could change it right there. It was amazing.
I guess all of this talk of no debuggers or that debuggers is a crutch for bad developers. It can enable the "get it running once" mentality instead of thinking of all the cases and your design. Like any tool it can be abused, but that doesn't mean we should get rid of all tools. Duck typing can be abused as well and I wouldn't trade it for the world.
Sunday, September 23, 2007
I'm embarrassed
I can't believe what gets passed off as humor these days. I'm embarrassed to be part of a community where one of the main evangelists of the community post such bile and garbage. It was meant to be funny. But, calling developers outside your community "morons" even in jest is not cool. It certainly doesn't breed communication and good will. And if you were interested in "making the world of software development a more enjoyable, productive place", then name calling is not the way to do it. I understand it was done in humor and I know you are writing off my serious reply as "dumb" because of your debate killing "if you didn't get the humor, then you're dumb" post. But, if you wanted to be funny, you went about it the wrong way. All you displayed was a level of immaturity that should be unacceptable in our community. It wasn't witty.
And to end I will add, yes, Ruby does need tools. It needs tools badly. I would love to have refactoring support (that works) and a good debugger. I can't imagine doing a project of any size without those tools. Not every one is a great developer and eventually you will step into some unsavory code. Tools are for helping with those unsavory pieces of code and even to gain understanding on a large system. This is where learning comes in. Instead of writing the developer off as a "moron", I use unsavory code and tools to teach how to do it better. So, who's really "making the world of software development a more enjoyable, productive place"?
And to end I will add, yes, Ruby does need tools. It needs tools badly. I would love to have refactoring support (that works) and a good debugger. I can't imagine doing a project of any size without those tools. Not every one is a great developer and eventually you will step into some unsavory code. Tools are for helping with those unsavory pieces of code and even to gain understanding on a large system. This is where learning comes in. Instead of writing the developer off as a "moron", I use unsavory code and tools to teach how to do it better. So, who's really "making the world of software development a more enjoyable, productive place"?
Sunday, September 16, 2007
A Sorting Language
Inspired by Neal Ford's post on patterns and sorting in Ruby:
I loved Neal's post, but it got me thinking that sometimes I need to sort beyond one field. How could I go about that? I whipped up this example really quick. The function :by is simply syntactic sugar for converting to a comparator. I thought it read better. Neal's solution is the way to go if you need to sort on a single field. But when you need more, the above will work too. This is another one of my little late night coding thoughts. Enjoy.
class Comparator
def initialize(&default)
if (defined? default)
@compare_block=default
else
@compare_block=lambda { |a,b| a <=> b }
end
end
def compare(a,b)
@compare_block.call(a,b)
end
def to_proc
method(:compare).to_proc
end
def to_comparator
self
end
def then_by(next_aspect)
next_comparator=next_aspect.to_comparator
self.class.new do |a,b|
comparison=compare(a,b)
if (comparison == 0)
next_comparator.compare(a,b)
else
comparison
end
end
end
def reverse
self.class.new do |a,b|
comparison=compare(a,b)
if (comparison == 1)
-1
elsif (comparison == -1)
1
else
0
end
end
end
end
def by(aspect_to_compare)
aspect_to_compare.to_comparator
end
class Symbol
def to_comparator
Comparator.new { |a,b| a.send(self) <=> b.send(self) }
end
end
module Enumerable
def to_comparator
inject do |thus_far, every|
thus_far.to_comparator.then_by(every.to_comparator)
end
end
end
require 'rubyunit'
class SortTest < Test::Unit::TestCase
def test_simple
a=Person.new("blaine", 36)
b=Person.new("blaine", 12)
result=[a,b].sort(&by(:name).then_by(:age))
assert_equal([b,a], result)
end
def test_array
a=Person.new("blaine", 12)
b=Person.new("alice", 12)
result=[a,b].sort(&by([:age, :name]))
assert_equal([b,a], result)
end
def test_reverse
a=Person.new("grue", 123)
b=Person.new("thief", 34)
result=[a,b].sort(&by(:name).reverse)
assert_equal([b,a], result)
result=[a,b].sort(&by(:age).reverse)
assert_equal([a,b], result)
end
end
class Person
attr_reader :name, :age
def initialize(name, age)
@name=name
@age=age
end
end
I loved Neal's post, but it got me thinking that sometimes I need to sort beyond one field. How could I go about that? I whipped up this example really quick. The function :by is simply syntactic sugar for converting to a comparator. I thought it read better. Neal's solution is the way to go if you need to sort on a single field. But when you need more, the above will work too. This is another one of my little late night coding thoughts. Enjoy.
Saturday, September 15, 2007
Duh....
pdcawley wrote the following comment to my previous "reduce" post:
So, I looked it up in ri and got this entry:
Ouch. All that coding for nothing (well, I did get to play around with some stuff). Next time, I will consult the documentation before I post. Duh...Good call.
P.S. It seems Rails implements the Symbol extension of #to_proc already. It's in the activesupport stuff. Their implementation is similiar to mind. But, I found one implementation better than mine. I love reading other people's code because it always shows me things I didn't think of. For your reading pleasure:
This is better than using shift and cleaner. I got this from one of the comments off of Neal Ford's blog. Great stuff.
[1,2,3,4].inject() {|a,b| a + b} # 10
So, I looked it up in ri and got this entry:
------------------------------------------------------ Enumerable#inject
enum.inject(initial) {| memo, obj | block } => obj
enum.inject {| memo, obj | block } => obj
------------------------------------------------------------------------
Combines the elements of _enum_ by applying the block to an
accumulator value (_memo_) and each element in turn. At each step,
_memo_ is set to the value returned by the block. The first form
lets you supply an initial value for _memo_. The second form uses
the first element of the collection as a the initial value (and
skips that element while iterating).
# Sum some numbers
(5..10).inject {|sum, n| sum + n } #=> 45
# Multiply some numbers
(5..10).inject(1) {|product, n| product * n } #=> 151200
javascript:void(0)
# find the longest word
longest = %w{ cat sheep bear }.inject do |memo,word|
memo.length > word.length ? memo : word
end
longest #=> "sheep"
# find the length of the longest word
longest = %w{ cat sheep bear }.inject(0) do |memo,word|
memo >= word.length ? memo : word.length
end
longest #=> 5
Ouch. All that coding for nothing (well, I did get to play around with some stuff). Next time, I will consult the documentation before I post. Duh...Good call.
P.S. It seems Rails implements the Symbol extension of #to_proc already. It's in the activesupport stuff. Their implementation is similiar to mind. But, I found one implementation better than mine. I love reading other people's code because it always shows me things I didn't think of. For your reading pleasure:
class Symbol
def to_proc
lambda do |this, *arguments|
this.send(*arguments)
end
end
end
This is better than using shift and cleaner. I got this from one of the comments off of Neal Ford's blog. Great stuff.
Friday, September 14, 2007
More Functional Fun In Ruby
I have no clue whenever this will be useful, but it was a fun exercise. Hope you enjoy it as well. It's an implementation of Lisp's cons, car, and cdr.
def pair(left,right)
lambda do |dyadic|
dyadic.call(left,right)
end
end
def left(pair)
pair.call(lambda {|left,right| left})
end
def right(pair)
pair.call(lambda {|left,right| right})
end
require 'rubyunit'
class WorkspaceTest < Test::Unit::TestCase
def test_simple
a_b=pair('a','b')
assert_equal('a',left(a_b))
assert_equal('b',right(a_b))
end
def test_again
a_b=pair('a', 'b')
c_a_b=pair('c', a_b)
assert_equal('c', left(c_a_b))
assert_equal('a', left(right(c_a_b)))
end
end
Python's Reduce, (Fun)ctional Programming, and Ruby
Mike Hostetler blogged about Python's reduce. He said he never had a use for it until recently. I looked at his example and I thought that looks a lot like inject, but it uses the first element in the collection as the seed instead of it being given. I thought what mad fun would it be to write one in Ruby? Exactly. Here's my implementation:
It has a somewhat functional style in that instead of using a boolean to check for the first iteration, I just use a special lambda for the first pass and then it turns itself into the original one. There is some state (to_execute), but I'm still a functional newbie.
I do love inject, which has a lot of uses beyond aggregation, and this will reduce (no pun intended) code in some places where I use it.
Anyway, I couldn't stop myself. I thought wouldn't it be nice to just pass in a symbol instead of a block? I added the following method to Symbol and another test. Check it out:
All I do is take a list of arguments. I make the first one the receiver and send the rest as it arguments. How cool is that? I get to cut down on the line noise. I still have to put up with &, but until they make blocks first class citizens...
What's the point of this post? Nothing. Just wanted to do a fun little exercise. I thought others might find the implementation fun as well. Now, about that name 'reduce'...I think maybe aggregate would be better? But, that doesn't express for all cases either. Hmmm...
module Enumerable
def reduce(empty_return=nil, &dyadic)
to_execute=lambda do |thus_far,every|
to_execute=dyadic
every
end
inject(empty_return) do |thus_far, every|
to_execute.call(thus_far,every)
end
end
end
require 'rubyunit'
class ReduceTest < Test::Unit::TestCase
def test_simple
result=[1,2,3,4,5].reduce {|thus_far,every| thus_far + every}
assert_equal(15, result)
end
def test_empty_nil
result=[].reduce {|result,every| thus_far + every}
assert_nil(result)
end
def test_empty_default
result=[].reduce(0) {|result,every| thus_far + every}
assert_equal(0, result)
end
end
It has a somewhat functional style in that instead of using a boolean to check for the first iteration, I just use a special lambda for the first pass and then it turns itself into the original one. There is some state (to_execute), but I'm still a functional newbie.
I do love inject, which has a lot of uses beyond aggregation, and this will reduce (no pun intended) code in some places where I use it.
Anyway, I couldn't stop myself. I thought wouldn't it be nice to just pass in a symbol instead of a block? I added the following method to Symbol and another test. Check it out:
class Symbol
def to_proc
lambda do |*arguments|
arguments.shift.send(self, *arguments)
end
end
end
class SymbolTest < Test::Unit::TestCase
def test_simple
result=[1,2,3,4,5].reduce &:+
assert_equal(15, result)
end
end
All I do is take a list of arguments. I make the first one the receiver and send the rest as it arguments. How cool is that? I get to cut down on the line noise. I still have to put up with &, but until they make blocks first class citizens...
What's the point of this post? Nothing. Just wanted to do a fun little exercise. I thought others might find the implementation fun as well. Now, about that name 'reduce'...I think maybe aggregate would be better? But, that doesn't express for all cases either. Hmmm...
Monday, September 10, 2007
We Need Each Other: Ruby and Smalltalk
Neal Ford made a blog post on meta-programming, Ruby, and Smalltalk recently. It caused some discussion from long-time Smalltalker, James Robertson, and Rubyist, Glenn Vandenburg. Even Avi Bryant got in the discussion. There was a lot of talking past one another on the issues of code generation in Ruby and Smalltalk.
I'll start with a quote from Neal Ford:
There is no example given of what the "accidental complexity" was in Smalltalk. But, let's look at an example that Rubyists are familiar with and then I'll go forward:
This will generate the accessors :name and :name= at run-time. It's a nice way to describe a public field. There's even read and write only variations. It allows you to describe your intent of the field. Now, in Smalltalk you could do the same thing with class-side methods. Like so:
This could add the instance variable (if it didn't exist), and generate the getters and setters when the class is initialized. It is true in this case, we would use tools to do the generation and generate the code before hand. I think this is what Neal was talking about accidental complexity because we are adding methods that we later have to browse through. But, this is where they are mistaken.
Smalltalk has categories and usually, these code generated methods are placed in them. Accessors are generally placed in their own category so do not add to the cognitive friction. In fact, when I use code generation, I group the methods in a category with "AUTO" in it. This is so that I can later delete those methods and start over. Also, it allows me to not have to look at them when looking at the more important methods of the class.
There is one last way to do things like this in Smalltalk, but is rare. Classes are created in Smalltalk by sending messages. It's not some hard-coded construct in the language. It's a message. We can create our own messages to create our own classes. Here's what the example could look like in Squeak:
And lastly, there is a initialize method for all Smalltalk classes that would give a more Ruby feel:
Now, with all of that said, these are just examples to show what is possible. It's all a matter of taste and what you are trying to accomplish to which method you choose.
finally, my mouth dropped when I saw this in the same blog post:
I will answer this as bluntly and respectively as I can. Smalltalkers have NEVER shied away from meta-programming because of having to build a tool. Most Smalltalkers have a bag of tools that they use and add to their environment. Tools are so easy to write in Smalltalk that most programmers in it have at some point written one or two to help them. It's trivial to add functionality to the browser and inspectors. Generally, Smalltalkers only stay away from things that are hard to debug (ala method_missing, doesNotUnderstand:), but do them when necessary. Readability is always the utmost importance to Smalltalkers. Abbreviations are generally frowned upon even.
The last thing I want to remark on is the quote in bold above. I'll repeat it here because it makes me both shocked and sad:
I hardly call that a strength. Really. Why would I want to go back to bear skin and stone development? I have finally become productive in Java because of Eclipse (code browsing, auto-format, code completion, refactoring, etc). Why would I want to go back to command line brute force methods? I keep hoping for a great Ruby IDE (some are close like Ruby's Eclipse plug-in, Arachno, etc). Besides, I think while tools like ri and rdoc are nice, I want to look at source code and it's difficult to find the definitions of methods (without using grep) when going through new source code. A friend once told me he never trusted a language you couldn't write an IDE for it. There's truth to that. I never got FreeRIDE to last longer than a few minutes of development. Tools and easy to read syntax are necessary.
Ruby has great potential, but the syntax needs to be heavily reafactored (%w while being nice shorthand is unreadable) and made more consistent (blocks do not take the same types of arguments as methods, I can not send a block with & into a block example, lambda {|&block| block.call } gives a compile error). But, those are my gripes and I do still like Ruby. I think boasting that your language has no tools is short sighted at best.
I think both Smalltalk and Ruby developers could learn a lot from one another. So, if any Ruby developer has questions about Smaltallk, please feel free to email me. I will even extend the same to Smalltalk developers curious about Ruby. The point of this post was mainly to educate and inspire both Rubyists and Smalltalkers to be better.
I'll start with a quote from Neal Ford:
the Smalltalk version is a great example of accidental complexity, not essential complexity.
There is no example given of what the "accidental complexity" was in Smalltalk. But, let's look at an example that Rubyists are familiar with and then I'll go forward:
class Person
attr_accessor :name
end
This will generate the accessors :name and :name= at run-time. It's a nice way to describe a public field. There's even read and write only variations. It allows you to describe your intent of the field. Now, in Smalltalk you could do the same thing with class-side methods. Like so:
attributeAccessors
^#(name)
This could add the instance variable (if it didn't exist), and generate the getters and setters when the class is initialized. It is true in this case, we would use tools to do the generation and generate the code before hand. I think this is what Neal was talking about accidental complexity because we are adding methods that we later have to browse through. But, this is where they are mistaken.
Smalltalk has categories and usually, these code generated methods are placed in them. Accessors are generally placed in their own category so do not add to the cognitive friction. In fact, when I use code generation, I group the methods in a category with "AUTO" in it. This is so that I can later delete those methods and start over. Also, it allows me to not have to look at them when looking at the more important methods of the class.
There is one last way to do things like this in Smalltalk, but is rare. Classes are created in Smalltalk by sending messages. It's not some hard-coded construct in the language. It's a message. We can create our own messages to create our own classes. Here's what the example could look like in Squeak:
Object subclass: #Person
attributeAccessors: 'names'
classVariables: ''
poolDictionaries: ''
category: 'MetaExample'
And lastly, there is a initialize method for all Smalltalk classes that would give a more Ruby feel:
initialize
self attributeAccessors: 'name'
Now, with all of that said, these are just examples to show what is possible. It's all a matter of taste and what you are trying to accomplish to which method you choose.
finally, my mouth dropped when I saw this in the same blog post:
Smalltalk had (and has) an awesome environment, including incredible tool support. Because the tool is pervasive (literally part of the project itself), Smalltalkers generally shied away from the kind of meta-programming described above because you have to build tool support along with the meta-programmed code. This is a conscious trade off. One of the best things about the Ruby world (up until now) is the lack of tool support, meaning that the tools never constrained (either literally or influentially) what you can do. Fortunately, this level of power in Ruby is pretty ingrained (look at Rails for lots of examples), so even when the tools finally come out, they need to support the incredible things you can do in Ruby.
I will answer this as bluntly and respectively as I can. Smalltalkers have NEVER shied away from meta-programming because of having to build a tool. Most Smalltalkers have a bag of tools that they use and add to their environment. Tools are so easy to write in Smalltalk that most programmers in it have at some point written one or two to help them. It's trivial to add functionality to the browser and inspectors. Generally, Smalltalkers only stay away from things that are hard to debug (ala method_missing, doesNotUnderstand:), but do them when necessary. Readability is always the utmost importance to Smalltalkers. Abbreviations are generally frowned upon even.
The last thing I want to remark on is the quote in bold above. I'll repeat it here because it makes me both shocked and sad:
One of the best things about the Ruby world (up until now) is the lack of tool support
I hardly call that a strength. Really. Why would I want to go back to bear skin and stone development? I have finally become productive in Java because of Eclipse (code browsing, auto-format, code completion, refactoring, etc). Why would I want to go back to command line brute force methods? I keep hoping for a great Ruby IDE (some are close like Ruby's Eclipse plug-in, Arachno, etc). Besides, I think while tools like ri and rdoc are nice, I want to look at source code and it's difficult to find the definitions of methods (without using grep) when going through new source code. A friend once told me he never trusted a language you couldn't write an IDE for it. There's truth to that. I never got FreeRIDE to last longer than a few minutes of development. Tools and easy to read syntax are necessary.
Ruby has great potential, but the syntax needs to be heavily reafactored (%w while being nice shorthand is unreadable) and made more consistent (blocks do not take the same types of arguments as methods, I can not send a block with & into a block example, lambda {|&block| block.call } gives a compile error). But, those are my gripes and I do still like Ruby. I think boasting that your language has no tools is short sighted at best.
I think both Smalltalk and Ruby developers could learn a lot from one another. So, if any Ruby developer has questions about Smaltallk, please feel free to email me. I will even extend the same to Smalltalk developers curious about Ruby. The point of this post was mainly to educate and inspire both Rubyists and Smalltalkers to be better.
Tuesday, August 28, 2007
String Concatenation
I made a small mistake in the code in "Promises and String Concatenation". The DelayString object not being stateless is a huge problem. Here's the test to prove it:
Ouch. I fixed it by making DelayString only know a left and a right part. It cleaned the code up quite a bit. I factored out Promise because it made the code a little less readable. The resulting code is much simpler:
All of our tests run. DelayString is stateless (minus caching of the result). There's still improvements to be made, but the code is simpler and easier to understand. The performance did take a hit. It's twice as slow (43.94s) as the previous version. Not to worry it still beats normal concatenation by a large margin. I'll take the performance hit for more readable code anyday!
def test_independence
first = "3" + "4"
second = first + "5"
third = "2" + second
assert_equal("34", first.to_s)
assert_equal("345", second.to_s)
assert_equal("2345", third.to_s)
end
Ouch. I fixed it by making DelayString only know a left and a right part. It cleaned the code up quite a bit. I factored out Promise because it made the code a little less readable. The resulting code is much simpler:
class DelayString
def initialize(oneString, anotherString)
@left = oneString
@right = anotherString
end
def +(another)
DelayString.new(self, another)
end
def to_s()
return @result unless @result.nil?
to_process=[self]
@result=String.stream_contents do |out|
until (to_process.empty?)
current=to_process.pop
current.process(to_process,out)
end
end
end
def process(to_process,io)
to_process.push(@right)
to_process.push(@left)
end
end
class String
def self.stream_contents(&monadic)
StringIO.open() do |io|
monadic.call(io)
io.string
end
end
def +(another)
DelayString.new(self, another)
end
def process(to_process,io)
io << self
end
end
All of our tests run. DelayString is stateless (minus caching of the result). There's still improvements to be made, but the code is simpler and easier to understand. The performance did take a hit. It's twice as slow (43.94s) as the previous version. Not to worry it still beats normal concatenation by a large margin. I'll take the performance hit for more readable code anyday!
Saturday, August 25, 2007
Promises And String Concatenation
You've read about it a million times. Beware of multiple string concatenations. They make your code slow and consumes memory. You've read it in the Java books (known as "use StringBuilder or StringBuffer instead of +"). But, why? Why isn't this handled at a lower level? Why can't the VM or compiler just do the right thing?
Messages are the power of objects. So, why not make a new object that when sent the + message, it simply returns an object that waits to do the concatenation until it is needed. This new kind of object should understand the same protocol as string. This could all be handled underneath the covers. If it was done at the VM or compiler level, programmers would never have to know.
I thought I would do a sample implementation. It's rather easy (in a dynamic language). First, we need to implement a Promise class and here's the Ruby code complete with a simple test:
Pretty simple, huh? Create a new Promise object on a block (or closure or lambda or whatever you like to call it) and it will only call the block once when the message "value" is sent to it. If the message "value" is never sent, the block is never evaluated. Can you think where that might come in handy? I can think of several, but the best one is when trying to create a message to log. If you don't log the message, you wouldn't need to do the concatenation. Again, not doing the computation upfront can not only allow us to manage memory better, but also not to do needless calculations.
Enough talk, let's get to the good stuff, right? Here's my implementation of delaying concatenations and check the tests out at the bottom:
One new class called DelayString handles not doing the concatenation until absolutely necessary. It does this by creating a Promise that calculates the string by using a StringIO object (Stream or StringBuilder in Java terms). All it does is keeps a collection of all the strings it needs to append to one another. The power is now that we get the nice succinct message "+" and all of the benefits of using a stream object (or StringBuilder). Of course, we would need to add more methods on our DelayString so that it has the same protocol as String. A little more work to make our implementation seamless.
Below is the test method I added to find the times it took to run for delayed and normal concatenation:
The new delayed implementation ran at 23.8 seconds. Not bad to do a million additions and a lot of little ones at that. Now, what were the results the old way? Well, all you have to do is comment out the + message:
It took 4565.68 seconds to run the normal way. It performed poorly and took up a bunch of memory. Yuck. It's what the books warned us about right? It's what we expected somewhat. I didn't expect how much of a performance gain I really got. Pretty cool, huh? Amazing.
It's unlikely that we'll do something to this extreme in the real world. But, wouldn't it be nice to not worry about performance in our regular code? If we find that our implementation is sub-par, one of the new benefits is that we can change it in one place.
Wait a minute. We just got better performance and got to keep the simple way of doing things? Not one lick of our already existing code had to change. The power of messages is powerful indeed!
Messages are the power of objects. So, why not make a new object that when sent the + message, it simply returns an object that waits to do the concatenation until it is needed. This new kind of object should understand the same protocol as string. This could all be handled underneath the covers. If it was done at the VM or compiler level, programmers would never have to know.
I thought I would do a sample implementation. It's rather easy (in a dynamic language). First, we need to implement a Promise class and here's the Ruby code complete with a simple test:
require 'rubyunit'
class Promise
def initialize(&block)
@calculation=block
@value=nil
end
def value
return @value if @calculation.nil?
@value=@calculation.call()
@calculation=nil
freeze()
return @value
end
def value?
@calculation.nil?
end
end
def promise(&block)
Promise.new(&block)
end
class PromiseTest < Test::Unit::TestCase
def test_simple
promise = promise { 3 + 4 }
assert(!promise.value?)
assert(7 == promise.value)
assert(promise.value)
end
end
Pretty simple, huh? Create a new Promise object on a block (or closure or lambda or whatever you like to call it) and it will only call the block once when the message "value" is sent to it. If the message "value" is never sent, the block is never evaluated. Can you think where that might come in handy? I can think of several, but the best one is when trying to create a message to log. If you don't log the message, you wouldn't need to do the concatenation. Again, not doing the computation upfront can not only allow us to manage memory better, but also not to do needless calculations.
Enough talk, let's get to the good stuff, right? Here's my implementation of delaying concatenations and check the tests out at the bottom:
require 'stringio'
class DelayString
def initialize(oneString, anotherString)
@strings = [oneString, anotherString]
@promise = promise do
stream = @strings.inject(StringIO.new) do |output,each|
output << each
end
@strings = nil
freeze()
stream.rewind
stream.read
end
end
def +(another)
return another.concatBeforeDelayString(self)
end
def concatBeforeString(another)
@strings.unshift(another)
self
end
def concatAfterString(another)
@strings.push(another)
self
end
def concatBeforeDelayString(another)
strings_each do |each|
another.concatAfterString(each)
end
another
end
def concatAfterDelayString(another)
another.concatBeforeDelayString(self)
end
def to_s()
@promise.value()
end
private
def strings_each(&block)
@strings.each(&block)
end
end
class String
def +(another)
return another.concatBeforeString(self)
end
def concatBeforeString(another)
another.concatAfterString(self)
end
def concatAfterString(another)
DelayString.new(self, another)
end
def concatBeforeDelayString(another)
another.concatAfterString(self)
end
def concatAfterDelayString(another)
another.concatBeforeString(self)
end
end
class DelayStringTest < Test::Unit::TestCase
def test_simple
add = "3" + "4"
assert_equal("34", add.to_s)
end
def test_string
add = "3" + "4"
add = "2" + add
add = add + "5"
assert_equal("2345", add.to_s)
end
def test_delay
add_before = "1" + "2"
add_after = "3" + "4"
add = add_before + add_after
assert_equal("1234", add.to_s)
#make sure to get same answer twice
assert_equal("1234", add.to_s)
end
end
One new class called DelayString handles not doing the concatenation until absolutely necessary. It does this by creating a Promise that calculates the string by using a StringIO object (Stream or StringBuilder in Java terms). All it does is keeps a collection of all the strings it needs to append to one another. The power is now that we get the nice succinct message "+" and all of the benefits of using a stream object (or StringBuilder). Of course, we would need to add more methods on our DelayString so that it has the same protocol as String. A little more work to make our implementation seamless.
Below is the test method I added to find the times it took to run for delayed and normal concatenation:
def test_performance
add = ''
1000000.times do |iteration|
add = add + 'a'
end
add.to_s
end
The new delayed implementation ran at 23.8 seconds. Not bad to do a million additions and a lot of little ones at that. Now, what were the results the old way? Well, all you have to do is comment out the + message:
# def +(another)
# return another.concatBeforeString(self)
# end
It took 4565.68 seconds to run the normal way. It performed poorly and took up a bunch of memory. Yuck. It's what the books warned us about right? It's what we expected somewhat. I didn't expect how much of a performance gain I really got. Pretty cool, huh? Amazing.
It's unlikely that we'll do something to this extreme in the real world. But, wouldn't it be nice to not worry about performance in our regular code? If we find that our implementation is sub-par, one of the new benefits is that we can change it in one place.
Wait a minute. We just got better performance and got to keep the simple way of doing things? Not one lick of our already existing code had to change. The power of messages is powerful indeed!
Sunday, January 07, 2007
One Way
I was reading "Programmers At Work" this morning while giving platelets and ran across this quote from Jef Raskin:
I've seen this time and time again. I know I've even fallen for the "flexibility" fallacy where I should have been simple. The above can apply to not only user interfaces, but APIs and languages. It made me immediately think of Ruby.
Ruby falls prey to the "flexibility" vulture and it's even touted as one of its greatest strengths by some (Reason #7). You have two ways to define blocks ({}, do), various ways of constructing arrays (new Array, %w), and other things to make various programmers from other languages feel comfortable. I think Ruby itself needs to become more opinionated.
There's a lot of great things about Ruby that I'm proud that are finally getting into mainstream minds like dynamic typing, closures, and meta-programming. It's cool seeing all of the excitement. But, I think if Ruby wants to go further, it needs to trim some of the fat. Now, this fat might have made programmers from other languages more comfortable, but we need to lose it. I would like to see Ruby more lightweight, easier to parse (it's a nightmare), and of course more opinionated. But, I worry that with all of the activity that bad habits will continue. I'm worried about the future of Ruby because all of the forking and lack of a clear direction.
With all of the battles that Ruby has won, I would hate to see it fail because of sins to gain more acceptance. It's won that, now, let's make Ruby the best it can be.
Most computer designers, for some reason, delight in providing many ways of doing something. If there are fifteen ways to do something, they think it gives you freedom. The fact is that most users don't use the majority of the commands on their word processors. There are a few that everyone uses. And even though they've read the manual and know it might be a little more efficient to use a special technique, they don't bother. They use the same ones every time.
I've seen this time and time again. I know I've even fallen for the "flexibility" fallacy where I should have been simple. The above can apply to not only user interfaces, but APIs and languages. It made me immediately think of Ruby.
Ruby falls prey to the "flexibility" vulture and it's even touted as one of its greatest strengths by some (Reason #7). You have two ways to define blocks ({}, do), various ways of constructing arrays (new Array, %w), and other things to make various programmers from other languages feel comfortable. I think Ruby itself needs to become more opinionated.
There's a lot of great things about Ruby that I'm proud that are finally getting into mainstream minds like dynamic typing, closures, and meta-programming. It's cool seeing all of the excitement. But, I think if Ruby wants to go further, it needs to trim some of the fat. Now, this fat might have made programmers from other languages more comfortable, but we need to lose it. I would like to see Ruby more lightweight, easier to parse (it's a nightmare), and of course more opinionated. But, I worry that with all of the activity that bad habits will continue. I'm worried about the future of Ruby because all of the forking and lack of a clear direction.
With all of the battles that Ruby has won, I would hate to see it fail because of sins to gain more acceptance. It's won that, now, let's make Ruby the best it can be.
Subscribe to:
Posts (Atom)