Pinderkent

Pain and glory from the trenches of the IT world.

JavaScript cannot be considered a functional programming language.

Posted on Sunday, February 24, 2008 at 8:05 PM.

As both JavaScript and functional programming have gained traction over the past several years, one common misunderstanding that I've seen expressed more and more often is that JavaScript is somehow a functional programming language. Such an example of this can be found in this Reddit comment, where the author says, "I don't know much about functional programming other that what I've picked up hacking javascript, ..."

One of the main traits of functional programming languages is that of referential transparency. JavaScript is obviously nowhere near as purely functional as a language like Haskell. While it is possible in JavaScript to write functions in a way that minimizes or eliminates side effects, this is often not at all practical (especially when using JavaScript for Web development). Most JavaScript code heavily concerns itself with manipulating the mutable Web browser DOM.

Support for tail recursion is another hallmark of functional programming languages. JavaScript does not offer such support, by default. It is possible to get a hackish form of tail recursion working with JavaScript, but it clearly is not comparable to what is offered by Scheme implementations, Haskell, and other truly functional languages.

Having support for first-class functions does not make a language "functional". Functional programming is a much broader philosophy, and JavaScript does not subscribe to or exhibit many of its ideas. So we should not mistakenly think that by using JavaScript, we are partaking in functional programming using a functional language.

Permalink: http://pinderkent.phumblog.com/post/2008/02/javascript_cannot_be_considered_a_functional_programming_language
Share:

Groovy is clearly not a statically typed language.

Posted on Monday, February 18, 2008 at 12:40 PM.

I recently wrote about how undetected type errors when using languages like Ruby and Python can be quite dangerous. That article has gotten some notice, with one response considering Groovy.

The article about Groovy purports to have an example of a statically typed Groovy script that compiles fine, but results in a type error at runtime. The script is:

int x = "test"
The error received at runtime is:
Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot
cast object 'test' with class 'java.lang.String' to class 'java.lang.Integer'
at typesafe.run(typesafe.groovy:1)
at typesafe.main(typesafe.groovy)

If Groovy were truly a statically typed language, then the compiler would have raised a compile-time error. But since that did not happen, one or more of the following may be true:

  1. The Groovy compiler does not perform adequate compile-time typechecking.
  2. The Groovy compiler performs compile-time typechecking, but a bug prevents it from catching this particular error.
  3. The semantics of Groovy prevent it from being a statically typed language.

The essence of static typing is that such an error would always be caught at compile time. We can't say that a language is statically typed if such an error occurs at runtime. Just because the programmer can specify the type of a variable (eg. 'int' in this case), it does not mean that the language is statically typed. This is especially true if the compiler or interpreter completely ignores such type specifiers at compile-time.

So in this case, I think the verdict is quite clear: Groovy cannot be considered a statically typed language, as it exhibited a runtime type-related error.

Permalink: http://pinderkent.phumblog.com/post/2008/02/groovy_is_clearly_not_a_statically_typed_language
Share:

A small example of the hidden dangers of dynamically typed programming languages.

Posted on Sunday, February 17, 2008 at 9:09 AM.

Several days back I wrote about how unit testing is not a substitute for static typing. A comment posted to that article by James asked for more clarification regarding what I was talking about. James wrote, "I can't recall the last time I had Ruby code break because I tried to act on an object of the "wrong" type." Well, I will give a simple example of how such problems arise, and how different languages deal with them. The languages in question will be Ruby, Python, OCaml and Haskell.

Our example program will be simple. It will consist of two functions. One will be called 'test', it will take two integers, and it will return the arithmetic sum of them. The other function will be called 'main', and it will invoke the 'test' function in two different ways, depending on the size of the array or list containing the command line arguments passed to the program. If there are over three command line arguments, the result of the 'test' function applied to the values 1 and the function 'test' is returned. Otherwise, the value of 'test' applied to values 1 and 2 is returned. Thus the program should have a return value of 3 up until the array or list storing the command line parameters has more than 3 elements.

Since James mentioned Ruby, let's start with the Ruby version of the code:

def test(a, b)
  a + b
end

def main()
  if ARGV.length > 3
    test(1, test)
  else
    test(1, 2)
  end
end

Process.exit(main())

And now we'll run it, with warnings enabled and set at the most verbose setting:

$ ruby -w -W2 t.rb; echo $?
3
$ ruby -w -W2 t.rb 0; echo $?
3
$ ruby -w -W2 t.rb 0 1; echo $?
3
$ ruby -w -W2 t.rb 0 1 2; echo $?
3
$ ruby -w -W2 t.rb 0 1 2 3; echo $?
t.rb:7:in `test': wrong number of arguments (0 for 2) (ArgumentError)
        from t.rb:7:in `main'
        from t.rb:13
1
$

As is expected from a dynamically typed language like Ruby, the error wasn't detected until runtime. Not only that, but the program ran quite successfully before then, without giving any indication that a hidden problem might arise were too many command line arguments given. Even were unit tests to be used, it is quite possible that duplicating such a scenario would be missed, and a perplexed user would be faced with an error such as the one above.

Python doesn't fare much better. Here is the code we'll use for it:

"""docstring"""
import sys

def test(first_arg, second_arg):
    """docstring"""
    return first_arg + second_arg

def main():
    """docstring"""
    if len(sys.argv) > 3:
        return test(1, test)
    else:
        return test(1, 2)

sys.exit(main())

Just to be safe, that code was run through the pylint utility, which rates the above code as "10.00/10". So an unsuspecting programmer may believe they have written high-quality Python code, when they surely have not, as we will soon see when we go to run the code:

$ python -W error t.py; echo $?
3
$ python -W error t.py 0; echo $?
3
$ python -W error t.py 0 1; echo $?
3
$ python -W error t.py 0 1 2; echo $?
Traceback (most recent call last):
  File "t.py", line 15, in ?
    sys.exit(main())
  File "t.py", line 11, in main
    return test(1, test)
  File "t.py", line 6, in test
    return first_arg + second_arg
TypeError: unsupported operand type(s) for +: 'int' and 'function'
1
$

Python behaves similarly to Ruby. The error isn't caught until runtime, and there's a good chance that unit testing would not have caught it, as well.

Let us try OCaml, a statically typed language. Compiling:

let test a b =
  a + b;;

let main _ =
  if (Array.length Sys.argv) > 3 then
    test 1 test
  else
    test 1 2;;

exit (main ());;
gives the following error:
$ ocamlopt -w A t.ml 
File "t.ml", line 6, characters 11-15:
This expression has type int -> int -> int but is here used with type int
$
The OCaml interpreter also catches the error, even without the code being executed:
$ ocaml -w A t.ml 
File "t.ml", line 6, characters 11-15:
This expression has type int -> int -> int but is here used with type int
$ 

Unlike when using Python or Ruby, the OCaml compiler and interpreter catch the error before the code begins to execute. And note that this is done without any source-level type annotations. This compile-time failure forces the programmer to deal with the error, rather than the user. Thus we end up with a more reliable program. Not only that, but the error was caught without the developer having to write even a single unit test. Now instead of writing unit tests to check if his or her code types correct, the developer can write unit tests to check the actual functionality of his or her software.

We can write a similar program using Haskell, another statically typed language, and it will also catch the error during compilation:

import System(getArgs)
import System.Exit

test :: Int -> Int -> Int
test a b = a + b

main :: IO ()
main = do
  args <- getArgs
  if (length args) > 3
    then exitWith (ExitFailure (test 1 test))
    else exitWith (ExitFailure (test 1 2))

When we go to compile the above program using GHC, we get:

$ ghc -Wall t.hs 

t.hs:11:39:
    Couldn't match expected type `Int'
           against inferred type `Int -> Int -> Int'
    In the second argument of `test', namely `test'
    In the first argument of `ExitFailure', namely `(test 1 test)'
    In the first argument of `exitWith', namely
        `(ExitFailure (test 1 test))'
$
The same error is given by ghci, the interactive REPL of GHC. As with OCaml, the problem is caught at compile time, rather than runtime. Thus the developer must actively deal with it for his or her program to just compile, let alone execute. This helps increase the program's reliability.

As we have clearly seen above, dynamically typed languages like Ruby and Python can allow for some flawed code to be written with ease. But more dangerously, it is possible for the code to run just fine, until a certain context arises upon which a runtime error occurs. Even when running the interpreter with warnings enabled, or after using a code-checking tool like pylint, such problems go completely undetected. This deception is dangerous. Some developers think that unit testing for such situations is appropriate, but there's a very good chance that such typing errors won't be detected by the unit tests, either.

It can be said that the code above is unrealistic. That's true. But the scenario it simplifies is very real. Based on my own experience, I have seen far too many problems arise with dynamically typed languages, where rarely-executed code contains a type-related error, and the execution of this code causes a runtime error (usually at a most inopportune time).

Thankfully, statically typed languages provide a very natural way of avoiding such problems at runtime, instead having them be caught at compile-time. When using languages like Haskell and OCaml, that support type inference, it's not even necessary for the developer to specify the types in the source code. So such developers get the convenience of languages like Python and Ruby, but without the inherent runtime danger of those languages, and without the inconvenience of having to write unit tests to handle checking that a proper compiler can do automatically, and more rigorously than a human could.

Permalink: http://pinderkent.phumblog.com/post/2008/02/a_small_example_of_the_hidden_dangers_of_dynamically_typed_programming_languages
Share:

Others are leaving Ruby on Rails, as well. And it's not going well.

Posted on Saturday, February 16, 2008 at 2:47 PM.

Several months back, there was somewhat of an uproar in the Ruby and Ruby on Rails communities when it was revealed that after two years of effort, the CD Baby Web site was abandoning their Ruby on Rails rewrite. The CD Baby site was reimplemented in mere months after returning to the use of PHP. This past week, I have been working with another company that is in a very similar situation.

In this company's case, they had a number of legacy, in-house systems developed in C and COBOL that were eventually moved to Web applications written in Java in the late 1990s. By early 2006, their Java-based software systems were starting to show their age, and it was decided that they would start rewriting many of them using Ruby on Rails. After an initial analysis and design period, the implementation began, with the greatest portion of the work being completed by August of 2007. The rollout was complete by the beginning of last September.

There has been an endless stream of complaints from the users of the software since that time. I've read through six 3" binders full of complaint reports. Now, the software itself isn't poorly implemented. It's actually among some of the better corporate Ruby on Rails code that I've seen over the past few years. Design-wise, it's actually pretty decent, too. From the complaint reports I've read, most of the users are also happy with the performance of the system. It's just that the applications themselves cannot be used well in a productive manner.

After talking to a number of the developers who were involved with that project, I heard repeatedly that Ruby on Rails was too restrictive in a variety of ways. The developers had to developer towards what Ruby on Rails would effectively let them develop, rather than developing the software to meet the needs of the users. The management had decided on a Ruby on Rails-or-nothing approach, so using alternative technologies in such situations was unfortunately not an option.

At this point, the company is trying to return to their Java-based systems, which worked better than their Ruby on Rails implementation. This will not be an easy task for us. Their data model changed in many ways, to name one major stumbling block. It's not been decided yet whether they'll update their Java code to handle the new model, or transition their new data back to the previous databases. Regardless, that aspect of the transition alone will be difficult. Updates to the Java code to handle the new capabilities introduced to the Ruby on Rails system since its deployment will also be required. There has been some consideration of a hybrid approach, with some of each system being used, but some of the stakeholders feel this will only cause more problems.

A lesson we can take from this, beyond the obvious ones, is that when performing a large-scale rollout of a new system, it's important to be able to revert back to the existing system nearly immediately. Not only that, but it should be possible to do this several months after the new system is initially rolled out. Although it will incur an extra cost of doing business to maintain the old, albeit unused, software for some time, not having it available to roll back to could be tremendously more costly.

Permalink: http://pinderkent.phumblog.com/post/2008/02/others_are_leaving_ruby_on_rails_as_well_and_its_not_going_well
Share:

Web browser resource allocation and management improvements are sorely needed.

Posted on Saturday, February 16, 2008 at 2:27 PM.

If more and more software developers are going to be developing browser-based applications using technologies such as JavaScript, Flash, and AJAX, we will need to see the Web browser become a much more capable platform. In short, the browser will need to duplicate much of the work that has been done over the years in the operating systems field.

One of the main problems affecting browsers today is that of resource allocation and usage. These are the very same issues that operating system developers faced in the 1950s and 1960s, when their craft was first maturing. These days, we have the browser acting as a code execution environment, and allowing for multiple, concurrent executions (eg. in multiple browser windows or tabs).

It's very easy for a malicious or poorly-written JavaScript to execute in such a manner that it locks up the entire Web browser. Some improvements have been made in this area, such as the use of watchdog timers that will prompt the user to kill the script if it has been running for a long time. But this is not the correct path to take. It only scratches at the symptoms, rather than curing the actual root cause.

Web browsers today need greater isolation between the different Web pages that are being displayed in multiple browser windows or in multiple tabs within a single window. Konqueror does this to some extent. Unlike with browsers like Mozilla Firefox or Opera, it is by default possible to launch multiple Konqueror processes at once. That way, poor JavaScript or a browser crash will only affect one window. Unfortunately, such isolation is either not easy or not possible to do with Opera and Firefox.

Unfortunately, Konqueror doesn't offer that degree of isolation when it comes to multiple tabs. A resource-consuming JavaScript in one tab has a lot of potential to lock up the entire browser window, rendering the other tabs inaccessible or essentially useless. Of course, the situation is far worse with Firefox and Opera, where other browser windows will be locked up, in addition to the tabs sharing the window with the problematic Web page.

A problem in one browser tab should never be responsible for a decrease in the availability or usability of another browser tab, let alone another browser window running in the same process. This is the most basic level of isolation, an it is a disappointment that after nearly two decades, even the most popular Web browsers cannot handle this task in a suitable manner.

Permalink: http://pinderkent.phumblog.com/post/2008/02/web_browser_resource_allocation_and_management_improvements_are_sorely_needed
Share:
Feeds
  • RSS 2.0 Feed
  • Atom 2.0 Feed
Tags
Archives