| Lisp success story: CliniSys
|
|
30 May 05 |
|
[print
link
all
] |
Kenny Tilton posted this on May 26 to comp.lang.lisp
The question is _where_ is it gaining ground...
as a hobby (which I presume is what most c.l.l readers,
myself included, use it for), or as a substantial outfit
in a company or in academics, and in the latter I see none
of it in the last couple years (the almighty Java seemingly
smothering all and everything). Any recent success stories
(i.e. new or significantly increased use of Lisp) here from
non-hobbyist camps that someone is willing to share?
Sure, CliniSys.
Clinical drug trial management. Don’t ask. It is a big problem. Sites
by FDA requirement must operate independently of the drug sponsor in
conducting trials, which the FDA further want conducted in precise fashion
and with elaborate documentation. Investigators are busy people and often
screw up. Monitors from the sponsor visit every six weeks to corral things
temporarily.
Our mission was to bring everything under control with a thick client
installed at the site guiding sites thru the process, enforcing business
rules, and basically seeing to it that the whole process runs right while
documenting everything and with a precise audit trail of that process and
documentation. The thick client also meant we needed a sophisticated
application-level partial replication scheme to move clinical and audit
data around a wide-area network of collaborating trial professionals.
The hard part is that every trial is different. Even within a trial, the
documentation can vary from site to site. On the wish list is customizing
documentation to individual patients to support adaptable trials. To make
things worse, the documentation can change in the middle of a trial, but
data collected under one version of a document must remain associated with
that version as other versions come along.
And getting the documentation right means applying arbitrarily
sophisticated edits. eg, The pregnancy test result on Visit 2 is required
if the gender collected at Visit 1 was female, otherwise it must be
"Not applicable".
The final challenge is scaling any successful result to hundreds of trials
a year. Existing software does a small fraction of our intended
functionality and is so hard to configure to each trial that (a) they can
get $500k to do a big trial and (b) vendors max out at a couple dozen
trials a year. They hope sponsors will run the software (they call it
"technology transfer") and figure out how to configure the
software themselves faster than could the vendor. They do worse, and a lot
of these vendors go out of business.
So we needed to do WYSIWYG DTP, ICR (scanning and reading documents), our
own DBMS, workgroup software to support remote monitoring, and a full
browser app for reviewing and collaborating on the documentation set. ie,
we needed a GUI, too.
I knew right away that we would have to be able to configure the
application for a new trial without programming. Well, there would be a
scripting language, but it would be the kind of thing power users master
all the time. I also figured out pretty quickly that it would be easier to
make a programmable application with a programmable language.
The project was two years old and $2m spent when I signed on to take over
software development. My predecessor had just quit. In an exit interview I
determined that he was making the right move for himself, but that I would
like the gig.
The angel founder trusted me to use the right software, which only makes
sense since he was betting $3m on me succeeding. I brought on one other
person and in four man years we produced a system that was vastly superior
to systems built with tens of millions of dollars. 80kloc Lisp.
People in the business agree our system will revolutionize the conduct of
clinical trials. Some nice big partners have joined the effort to make our
case to our first customer. That helps since it is a little stange for a
three-person operation to have done what no one else even thought possible
(which is why they were all trying to use the Web to solve the problem).
Making progress, though. Close brushes more often <g> getting both
customer and investment.
How did we do it? First, I had done this programmable application thing a
couple times before. Second, Common Lisp. Third, Cells. Fourth,
AllegroCL’s AllegroStore database. Fifth, AllegroCL’s IDE and
Franz tech support.
|
| Zen Refactoring
|
|
24 May 05 |
|
[print
link
all
] |
Alex Chaffee posted this to the xp-list.
Yesterday I was pairing with Patrick, our new intern. He's great,
smart as a whip, and willing to learn. (When I told him we write tests
first, he laughed, incredulously, but then I said No, really, I'm
actually serious, and he listened, and later, he said, Oh, now I see.)
At the end of a long successful refactoring (turning a clutch of
static methods into instance methods), we were sort of buzzing and
didn't want to stop, were casting about for other refactorings to do
in the same class. I suggested a technique I only just then gave a
name to, that I've done occasionally on my own for a long time, but
hadn't yet taught: Zen Refactoring.
"Just let your eyes unfocus and scroll through the code and look for
refactorings. Look for duplication; look for too-long or
too-deeply-indented code blocks. When you notice something odd, don't
read it, just select it, hit Extract Method (ctl-alt-M), and hit
random keys for the name. Then focus your eyes again and see what
you've done."
I demonstrated. The first time we hit a bunch of print statements with
repeated formatting -- it worked, but the refactoring was tedious,
with marginal payoff. But the second time was golden. I saw a blurry
if-else block; the first arm meandered for 15 or 20 deeply-indented
lines, the second for fewer; their logic appeared opaque and disjoint.
But after extracting the first block as a method, lo and behold:
if (something() || other()) {
customerId = sdlfjds(x, y, z);
} else {
user = someOtherExistingMethod();
customerId = user.getUserId();
}
IDEA had figured out that the result of that meander was a single
value, and returned it from the new method. It was instantly clear
that we should rename the extracted method
customerIdForSomethingOrOther", and extract the whole conditional as
"getCustomerId". Shift-F6, ctl-alt-M, high five.
Then it was Patrick's turn. Not quite as much of a slam-dunk but still
a worthy refactoring. I can't wait to get an incredulous laugh from
the next one I spring this on...
|
| AutrijusRaisingPerl6IntoARealPuppy
|
|
18 May 05 |
|
[print
link
all
] |
|
Perl6 in Haskell after 100 days .. :-) Enjoy the link redhanded.hobix.com/inspect/autrijusRaisingPerl6IntoARealPuppy.html
|
| When talking to CEO and CFO types ..
|
|
16 May 05 |
|
[print
link
all
] |
Ron Jeffries posted in the xp-list:
When I talk with CEO and CFO types, I generally emphasize that XP /
Agile projects focus on the delivery of running, tested, actual
software, along the lines of my article /A Metric Leading to
Agility/, http://www.xprogramming.com/xpmag/jatRtsMetric.htm
|
| Good article about Tiger
|
|
08 May 05 |
|
[print
link
all
] |
|
My girlfriend told me to get a computer that makes less noise .. so I took
the opportunity to buy a Mac-Mini and to have a nice computer to play with
:-).
Thanks to Sven for emailing me this link .. a good overview article about
Tiger. arstechnica.com/reviews/os/macosx-10.4.ars
|
| Nice graphics
|
|
06 May 05 |
|
[print
link
all
] |
|
Joao made this nice graphics and posted it in his blog
|
| Link of the day
|
|
04 May 05 |
|
[print
link
all
] |
|
links.userfriendly.org/lotd/http://www.delltechforce.com/
Die Larry Ellison Puppe k?mpft gegen "Big Iron" und sagt
"Let’s go in and kick some proprietory ass!"
ROTFL
|
| Sin City Comic-to-Screen Comoparison
|
|
24 Apr 05 |
|
[print
link
all
] |
Very nice link
about a cool movie.
|
| Seeing Metaclasses Clearly
|
|
19 Apr 05 |
|
[print
link
all
] |
why wrote a nice article.
I've written a very nuts+bolts article on metaclasses (aka virtual
classes or metaobjects), since they still lurk under a shroud of fear
and enigma.
whytheluckystiff.net/articles/seeingMetaclassesClearly.html
I'm hoping this will help uncover the truth. If anything is unclear,
please report it. Shed a light.
|
| 300 engineers take forever writing anything
|
|
19 Apr 05 |
|
[print
link
all
] |
Nice post by Elizabeth D. Rather
Heh, that reminds me of a time in the late 70's, when IBM came out with a
new "mini-computer". We put Forth on it for a customer. Our standard
system came with a database capability, multitasker, multi-user support,
lots of other features. Another customer was considering this machine, and
we made a presentation on our software. Some IBM engineers were also there.
They said, "This machine has only been on the market for a few months. We
have 300 engineers writing software for it in Boca Raton (Florida), and they
only have a macro assembler and simple executive running so far. How could
you have done all this with only one engineer?"
Well, I said something about having a standard design implemented in high
level that was easy to port, but of course the real answer is that 300
engineers would take forever writing anything!
|
| Enterprise software
|
|
11 Apr 05 |
|
[print
link
all
] |
|
Software development and software buying in big companies can be rather
sick. This is a nice report about the madness in a common enterprise. link
I think I have to read my daily Dilbert now, before making yet another
Powerpoint presentation for work, instead of fixing my bugs.
|
| wee: More novel than popular
|
|
30 Mar 05 |
|
[print
link
all
] |
Avi posted a nice entry
about seaside, rails and wee.
Rails ... doesn't have any novel ideas. I'm not
trying to talk it down, that's just the reality of what
Rails is -- it's not Seaside or Wee (and if it was it
wouldn't be so popular anyway).
*More novel than popular; I can live with that.*
|
| Making videos to show off your latest software
|
|
28 Mar 05 |
|
[print
link
all
] |
|
xvidcap is a great tool to to
capture things going on on an X-Windows display to either individual frames
or an MPEG video.
A big thanks to Michael Neumann who pointed out this too to me. He made his
great wee-videos with xvidcap.
|
| Packet sniffing and replay
|
|
27 Mar 05 |
|
[print
link
all
] |
|
I just came across this most useful ksnuffle. It will
help us to automate some more work.
|
| RubyScript2Exe 0.3.3 is released!
|
|
26 Mar 05 |
|
[print
link
all
] |
|
RubyScript2Exe transforms your Ruby script into a standalone, compressed
Windows, Linux or Max OS X (Darwin) executable. You can look at it as a
"compiler". Not in the sense of a source-code-to-byte-code
compiler, but as a "collector", for it collects all necessary
files to run your script on an other machine: the Ruby script, the Ruby
interpreter and the Ruby runtime library (stripped down for this script).
Anyway, the result is the same: a standalone executable (application.exe).
And that’s what we want!
gegroet, Erik V.
link
|
| Rake 0.5.0 Released
|
|
26 Mar 05 |
|
[print
link
all
] |
It has been a long time in coming, but we finally have a new version of
Rake available.
Changes
- Fixed bug where missing intermediate file dependencies could cause an abort
with —trace or —dry-run. (Brian Chandler)
- Recursive rules are now supported (Tilman Sauerbeck).
- Added tar.gz and tar.bz2 support to package task (Tilman Sauerbeck).
- Added warning option for the Test Task (requested by Eric Hodel).
- The jamis rdoc template is only used if it exists.
- Added fix for Ruby 1.8.2 test/unit and rails problem.
- Added contributed rake man file. (Jani Monoses)
- Fixed documentation that was lacking the Rake module name (Tilman
Sauerbeck).
What is Rake
Rake is a build tool similar to the make program in many ways. But instead
of cryptic make recipes, Rake uses standard Ruby code to declare tasks and
dependencies. You have the full power of a modern scripting language built
right into your build tool.
Availability
The easiest way to get and install rake is via RubyGems
…
gem install rake (you may need root/admin privileges)
Otherwise, you can get it from the more traditional places:
Thanks
Lots of people provided input to this release. Thanks to Tilman Sauerbeck
for numerous patches, documentation fixes and suggestions. And for also
pushing me to get this release out. Also, thanks to Brian Chandler for the
finding and fixing —trace/dry-run fix. That was an obscure bug. Also
to Eric Hodel for some good suggestions.
— Jim Weirich
|
| .. that sounds like real life
|
|
13 Mar 05 |
|
[print
link
all
] |
|
I was once on a project where the customer realized only after 1.5 months
that they want us to modify an application that does not even exist at that
company. You really makes you wonder what goes on in big companies .. Great
Dilbert comic
|
| How to start a startup?
|
|
10 Mar 05 |
|
[print
link
all
] |
|
Nice article by Paul
Graham.
You need three things to create a successful startup: to start with good
people, to make something customers actually want, and to spend as little
money as possible. Most startups that fail do it because they fail at one
of these. A startup that does all three will probably succeed.
And that’s kind of exciting, when you think about it, because all
three are doable. Hard, but doable. And since a startup that succeeds
ordinarily makes its founders rich, that implies getting rich is doable
too. Hard, but doable.
If there is one message I’d like to get across about startups,
that’s it. There is no magically difficult step that requires
brilliance to solve.
|
| A cool job announcement
|
|
07 Mar 05 |
|
[print
link
all
] |
.. seen this in today’s ruby-talk
#!/usr/bin/env ruby
# Warning this is a job announcement!
# Run it/Read it if you are interested.
# Lack of comments and robust input handling are intentional.
class Company
attr_accessor :name, :location, :web_site, :description
attr_accessor :available_jobs
def initialize(name = nil, location = nil, web_site = nil)
self.name = name
self.location = location
self.web_site = web_site
self.available_jobs = Array.new
end
def ask_for_interview?(job_applicant)
available_jobs.each do |ajob|
return true if ajob.meets_requirements?(job_applicant)
end
false
end
def describe
puts "Company : #{name}"
puts "Location : #{location}"
puts "Web site : #{web_site}"
puts "","Brief description :"
puts description, ""
end
def announce_job_availability(good_match, not_so_good_match)
return if available_jobs.empty?
describe
puts "Available jobs:"
available_jobs.each_with_index do |job, idx|
puts "", "#{idx + 1} ) #{job.name}", job.description, ""
end
job_applicant = ask_for_job_applicant_information
return if job_applicant.nil?
if ask_for_interview?( job_applicant )
puts good_match
else
puts not_so_good_match
end
end
def ask_for_job_applicant_information
job_applicant = nil
puts "Would you like to apply for a job? Y/N"
res = gets.chomp
if res =~ /Y/i
msg = "Great! Please follow the prompts to input your profile"
msg<< " to see if there if a job matches."
puts msg, ""
job_applicant = JobApplicant.new_from_interactive_shell
else
puts "Well thanks for reading/running the program! Good Bye!"
end
job_applicant
end
end
class Job
attr_accessor :name, :description, :requirements, :threshold
def initialize(name = nil, description = nil,
threshold = 100, requirements = [] )
self.name = name
self.description = description
self.requirements = requirements
self.threshold = threshold
end
def meets_requirements?(job_applicant)
points = 0
requirements.each do |req|
points += req.check_requirement(job_applicant)
end
points >= threshold
end
end
class JobApplicant
attr_accessor :name, :resume, :location
attr_accessor :spoken_languages, :computer_languages_skills
def initialize
self.spoken_languages = Array.new
self.computer_languages_skills = Array.new
end
def JobApplicant.new_from_interactive_shell
applicant = JobApplicant.new
puts "What is your name?"
applicant.name = gets.chomp
puts "Where do you live? (City, Country)"
applicant.location = gets.chomp
note = " [One entry per line. Press CTRL-D to stop input] "
puts "What languages do you speak?", note
applicant.spoken_languages = readlines.map { |d| d.chomp }
cq1 = "What computer languages are you proficient in?"
cq2 = "And what other computer skills do you have?"
puts cq1, cq2, note
applicant.computer_languages_skills = readlines.map {|d| d.chomp }
puts ""
applicant
end
end
class Requirement
def initialize(points = 1, &proc)
@points = points
if proc
@requirement_calc = proc
else
@requirement_calc = Proc.new { |x| true }
end
end
def check_requirement(job_applicant)
points = 0
if @requirement_calc.call(job_applicant)
points = @points
end
points
end
end
ubit = Company.new("Ubit", "Tokyo, Japan", "http://ubit.com")
ubit.description =<<EOF
Ubit is a Japanese company focusing on mobile phone services and
content aggregation both in Japan and abroad.
EOF
developer = Job.new("Software Developer")
developer.description =<<EOF
Become knowledgeable in the inner workings of our
product platform and work as a team with other developers to implement
new features and improve our current capabilities. Ideally, you are
willing to work under dynamic conditions and communicate well with
others.
EOF
loose_find = lambda do |data, reg_match|
data.find { |v| v =~ match }
end
reqs = Array.new
reqs<< Requirement.new(25) do |ja|
ja.spoken_languages.include?("English")
end
reqs<< Requirement.new(25) do |ja|
ja.spoken_languages.include?("Japanese")
end
reqs<< Requirement.new(5) do |ja|
sub = ["English", "Japanese"]
(ja.spoken_languages - sub).size > 0
end
reqs<< Requirement.new(50) do |ja|
ja.computer_languages_skills.include?("Ruby")
end
reqs<< Requirement.new(25) do |ja|
ja.computer_languages_skills.include?("Databases")
end
reqs<< Requirement.new(10) do |ja|
ja.computer_languages_skills.include?("Mobile Technologies")
end
reqs<< Requirement.new(5) do |ja|
ja.computer_languages_skills.include?("*NIX")
end
reqs<< Requirement.new(5) do |ja|
(ja.computer_languages_skills - ["Ruby", "Database"]).size > 0
end
reqs<< Requirement.new(25) do |ja|
ja.location =~ /Japan/i
end
developer.requirements = reqs
developer.threshold = 125
ubit.available_jobs<< developer
good_match =<<EOF
Your profile looks promising!
If you are interested in working with us,
please send your resume to Zev Blut at rubyzbibd@ubit.com
EOF
nsgm =<<EOF
Sorry, at the moment we are in need of people who meet our specific
needs. But if you feel that you can meet them then go ahead and send
your resume to Zev Blut at rubyzbibd@ubit.com
EOF
ubit.announce_job_availability(good_match,nsgm)
> Now that is just too cool :-)
>
> Cheers,
> Tim
Hi, I found a few ways to improve your program.
--- tokyo_job.rb.orig 2005-03-07 12:41:23.457936200 -0500
+++ tokyo_job.rb 2005-03-07 13:16:14.736811208 -0500
@@ -101,11 +102,11 @@
applicant.location = gets.chomp
note = " [One entry per line. Press CTRL-D to stop input] "
puts "What languages do you speak?", note
- applicant.spoken_languages = readlines.map { |d| d.chomp }
+ applicant.spoken_languages = readlines.map { |d| d.downcase.chomp }
cq1 = "What computer languages are you proficient in?"
cq2 = "And what other computer skills do you have?"
puts cq1, cq2, note
- applicant.computer_languages_skills = readlines.map {|d| d.chomp }
+ applicant.computer_languages_skills = readlines.map {|d|
d.downcase.chomp }
puts ""
applicant
end
@@ -157,42 +158,55 @@
reqs = Array.new
reqs<< Requirement.new(25) do |ja|
- ja.spoken_languages.include?("English")
+ ja.spoken_languages.include?("english")
end
reqs<< Requirement.new(25) do |ja|
- ja.spoken_languages.include?("Japanese")
+ ja.spoken_languages.include?("japanese")
end
reqs<< Requirement.new(5) do |ja|
- sub = ["English", "Japanese"]
+ sub = ["english", "japanese"]
(ja.spoken_languages - sub).size > 0
end
reqs<< Requirement.new(50) do |ja|
- ja.computer_languages_skills.include?("Ruby")
+ ja.computer_languages_skills.include?("ruby")
end
reqs<< Requirement.new(25) do |ja|
- ja.computer_languages_skills.include?("Databases")
+ ja.computer_languages_skills.grep(/database/).size > 0 or
+ ja.computer_languages_skills.grep(/\bdb\b/).size > 0
+ ja.computer_languages_skills.grep(/sql/).size > 0
end
reqs<< Requirement.new(10) do |ja|
- ja.computer_languages_skills.include?("Mobile Technologies")
+ ja.computer_languages_skills.include?("mobile technologies")
end
reqs<< Requirement.new(5) do |ja|
- ja.computer_languages_skills.include?("*NIX")
+ ja.computer_languages_skills.grep(/linux|unix/).size > 0
end
reqs<< Requirement.new(5) do |ja|
- (ja.computer_languages_skills - ["Ruby", "Database"]).size > 0
+ ja.computer_languages_skills.find_all do |lang|
+ case lang
+ when /ruby/, /database/, /\bdb\b/, /sql/
+ false
+ else
+ true
+ end
+ end.size > 0
end
reqs<< Requirement.new(25) do |ja|
ja.location =~ /Japan/i
end
+reqs<< Requirement.new(5) do |ja|
+ ja.name =~ /Ben/i
+end
+
developer.requirements = reqs
developer.threshold = 125
With these changes, it doesn't require '*NIX', but will accept "Linux"
or "Unix", and it is a bit more accepting of various database keywords.
(Oh yeah, and it assigns bonus points for cool names)
Ben
(P.S. !Japan, !Japanese, !"Mobile Technologies", and !currently_looking?
but that was too much fun to pass up. :) )
|
|
|