I used to do a lot of web programming in Rails (PHP before that) before I started studying computer engineering.
Since then, I've done a lot of school work in C, and some personal stuff in Objective-C (Mac stuff). I learnt to love static typing.
But now I'm having to do some professional web development (freelancing) and have picked up Rails once again. I'm finding it really annoying to write non-semantic type-checking tests. I was getting those for free from C and Objective-C compilers. I loved hitting Build and having the system check all my code to see that A can call B, B can call some obscure library C, etc. All I had to do was test the semantics. But with Rails, I'm the compiler. :(
Has anyone treaded this same path? Are my only options for web development ASP.NET MVC with C# and Java + x framework? Looking for some suggestions, or even some sympathy... :P
By the way, I make a specific reference to Rails rather than Ruby because I don't mind Ruby's dynamic nature for simple stuff like scripting or what not. But since Rails depends on so many gems and since one usually adds a number of other gems, the dynamic typing becomes an issue.
Thanks!
edit:
I followed up on pst's suggestion and looked into Scala. In reading the book Programming in Scala, written by the language's creator, Martin Odersky, I came accross this bit of text that in many ways expresses my concerns and a bit more. Very interesting reading.
Taken from page 52 of Martin Odersky's Programming in Scala:
Scala is statically typed
A static type system classifies
variables and expressions according to
the kinds of values they hold and
compute. Scala stands out as a
language with a very advanced static
type system. Starting from a system of
nested class types much like Java’s,
it allows you to parameterize types
with generics, to combine types using
intersections, and to hide details of
types using abstract types. These give
a strong foundation for building and
composing your own types, so that you
can design interfaces that are at the
same time safe and flexible to use.
If you like dynamic languages such as
Perl, Python, Ruby, or Groovy, you
might find it a bit strange that
Scala’s static type system is listed
as one of its strong points. After
all, the absence of a static type
system has been cited by some as a
major advantage of dynamic languages.
The most common arguments against
static types are that they make
programs too verbose, prevent
programmers from expressing themselves
as they wish, and make impossible
certain patterns of dynamic
modifications of software systems.
However, often these arguments do not
go against the idea of static types in
general, but against specific type
systems, which are perceived to be too
verbose or too inflexible. For
instance, Alan Kay, the inventor of
the Smalltalk language, once remarked:
“I’m not against types, but I don’t
know of any type systemsthat aren’t a
complete pain, so I still like dynamic
typing.”
We hope to convince you in this book
that Scala’s type system is far from
being a “complete pain.” In fact, it
addresses nicely two of the usual
concerns about static typing:
verbosity is avoided through type
inference and flexibility is gained
through pattern matching and several
new ways to write and compose types.
With these impediments out of the way,
the classical benefits of static type
systems can be better appreciated.
Among the most important of these
benefits are verifiable properties of
program abstractions, safe
refactorings, and better
documentation.
Verifiable properties
Static type systems can prove the
absence of certain run-time errors.
For instance, they can prove
properties like: booleans are never
added to integers; private variables
are not accessed from outside their
class; functions are applied to the
right number of arguments; only
strings are ever added to a set of
strings.
Other kinds of errors are not detected
by today’s static type systems. For
instance, they will usually not detect
non-terminating functions, array
bounds violations, or divisions by
zero. They will also not detect that
your program does not conform to its
specification (assuming there is a
spec, that is!). Static type systems
have therefore been dismissed by some
as not being very useful. The argument
goes that since such type systems can
only detect simple errors, whereas
unit tests provide more extensive
coverage, why bother with static types
at all?
We believe that these arguments miss
the point. Although a static type
system certainly cannot replace unit
testing, it can reduce the number of
unit tests needed by taking care of
some properties that would otherwise
need to be tested. Likewise, unit
testing cannot replace static typing.
After all, as Edsger Dijkstra said,
testing can only prove the presence of
errors, never their absence. So the
guarantees that static typing gives
may be simple, but they are real
guarantees of a form no amount of
testing can deliver.
Safe refactorings
A static type system provides a
safety net that lets you make changes
to a codebase with a high degree of
confidence. Consider for instance a
refactoring that adds an additional
parameter to a method. In a statically
typed language you can do the change,
re-compile your system and simply fix
all lines that cause a type error.
Once you have finished with this, you
are sure to have found all places that
need to be changed. The same holds for
many other simple refactorings like
changing a method name, or moving
methods from one class to another. In
all cases a static type check will
provide enough assurance that the new
system works just like the old.
Documentation
Static types are program documentation
that is checked by the compiler for
correctness. Unlike a normal comment,
a type annotation can never be out of
date (at least not if the source file
that contains it has recently passed a
compiler). Furthermore, compilers and
integrated development environments
can make use of type annotations to
provide better context help. For
instance, an integrated development
environment can display all the
members available for a selection by
determining the static type of the
expression on which the selection is
made and looking up all members of
that type.
This is one of my "gripes" about dynamic languages. I want to test for semantics, not type errors ;-) That being said, a good testing framework/setup is really a must in all non-trivial situations and good code-coverage and tested requirements is/are important.
If you do want to go down the static-typing path on the JVM (I have), I would highly recommend looking at Scala. Coming from Ruby, it's far less painful (and actually lots of fun in different ways) than going to Java. You get to "keep" the things you take for granted -- an expression-based syntax, closures, the ability to omit types in many places (not as open as Ruby, but you do get compile-time type checking ;-), everything(*)-is-an-object OO, unified accessor methods, ability to construct DSLs easily, and sugar -- and get the benefits of a statically typed language with local type inference, pattern matching, a relatively rich collection framework, and decent integration with Java (including the numerous web-frameworks, there are some Scala-specific frameworks as well which leverage the Scala language).
C#3.0/4.0 (and .NET3.5+) isn't too shabby either (but avoid C#2.0, which is now hopefully a relic), with the introduction of LINQ/closures, basic type inference and other
nice language features I find it "acceptable" for most tasks (take a guess how I would rate Java as a language ;-). However, C# is a CLR-target language (there is/was a .NET Scala port, but I am not sure of the status -- it is not the main target platform though).
Since I have mentioned Scala, I should also mention F# (now an "official" .NET language) which takes the "Funtional with OO" approach being similar to OCaml -- Scala is more of the reverse and I would describe it as "OO with Functional". I have heard arguments for/against F# compared to C# w.r.t the type system, but have no practical experience with F#. You may or may not like the paradigm shift.
Happy coding.