views:

1084

answers:

4

Does anyone know of a quick-starting Haskell interpreter that would be suitable for use in writing shell scripts? Running 'hello world' using Hugs took 400ms on my old laptop and takes 300ms on my current Thinkpad X300. That's too slow for instantaneous response. Times with GHCi are similar.

Functional languages don't have to be slow: both Objective Caml and Moscow ML run hello world in 1ms or less.

Clarification: I am a heavy user of GHC and I know how to use GHCi. I know all about compiling to get things fast. Parsing costs should be completely irrelevant: if ML and OCaml can start 300x faster than GHCi, then there is room for improvement.

I am looking for

  • The convenience of scripting: one source file, no binary code, same code runs on all platforms
  • Performance comparable to other interpreters, including fast startup and execution for a simple program like

    module Main where
    main = print 33
    

I am not looking for compiled performance for more serious programs. The whole point is to see if Haskell can be useful for scripting.

+5  A: 

If you are really concerned with speed you are going to be hampered by re-parsing the code for every launch. Haskell doesn't need to be run from an interpreter, compile it with GCH and you should get excellent performance.

Joel
Parsing costs are never completely irrelevant, and neither is the implicit richness of the expected runtime environment. Maybe ML and OCaml have fewer 'load by default' libraries. $ time python -c "print 33" is plenty fast! And I have no doubt there is room for improvement.
Joel
+3  A: 

You have two parts to this question:

  • you care about performance
  • you want scripting

If you care about performance, the only serious option is GHC, which is very very fast: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=all

If you want something light for Unix scripting, I'd use GHCi. It is about 30x faster than Hugs, but also supports all the libraries on hackage.

So install GHC now (and get GHCi for free).

Don Stewart
I don't care so much about overall performance as I do about *startup costs*.
Norman Ramsey
"Very very fast"? Let's not get carried away, Don---I've worked on GHC's back end, and I know where those particular bodies are buried :-)
Norman Ramsey
Hey Norman. I really don't think there's much alternative to compiling scripts (if they run often), or using the #!/usr/bin/runhaskell syntax if they're run rarely.
Don Stewart
That said, hugs starts up faster. It's just not very useful, imo.
Don Stewart
+3  A: 

Using ghc -e is pretty much equivalent to invoking ghci. I believe that GHC's runhaskell compiles the code to a temporary executable before running it, as opposed to interpreting it like ghc -e/ghci, but I'm not 100% certain.

$ time echo 'Hello, world!'
Hello, world!

real    0m0.021s
user    0m0.000s
sys     0m0.000s
$ time ghc -e 'putStrLn "Hello, world!"'
Hello, world!

real    0m0.401s
user    0m0.031s
sys     0m0.015s
$ echo 'main = putStrLn "Hello, world!"' > hw.hs
$ time runhaskell hw.hs
Hello, world!

real    0m0.335s
user    0m0.015s
sys     0m0.015s
$ time ghc --make hw
[1 of 1] Compiling Main             ( hw.hs, hw.o )
Linking hw ...

real    0m0.855s
user    0m0.015s
sys     0m0.015s
$ time ./hw
Hello, world!

real    0m0.037s
user    0m0.015s
sys     0m0.000s

How hard is it to simply compile all your "scripts" before running them?

Edit

Ah, providing binaries for multiple architectures is a pain indeed. I've gone down that road before, and it's not much fun...

Sadly, I don't think it's possible to make any Haskell compiler's startup overhead any better. The language's declarative nature means that it's necessary to read the entire program first even before trying to typecheck anything, nevermind execution, and then you either suffer the cost of strictness analysis or unnecessary laziness and thunking.

The popular 'scripting' languages (shell, Perl, Python, etc.) and the ML-based languages require only a single pass... well okay, ML requires a static typecheck pass and Perl has this amusing 5-pass scheme (with two of them running in reverse); either way, being procedural means that the compiler/interpreter has a lot easier of a job assembling the bits of the program together.

In short, I don't think it's possible to get much better than this. I haven't tested to see if Hugs or GHCi has a faster startup, but any difference there is still faaar away from non-Haskell languages.

ephemient
I use three different computing platforms: x86, amd64, and sparc. The inconvience of compiling code for all three is significant. I write a *lot* of scripts in Lua and `ksh`, and I'd rather use Haskell. But not if I have to compile everything.
Norman Ramsey
P.S. Your timings show my trouble exactly, except my 'echo' is a lot faster than yours... odd.
Norman Ramsey
This was originally timed under Cygwin, where (because Windows is braindead) process creation is friggin' expensive. Yeah, I don't see an easy solution to (lightweight + binary portable) Haskell "shell" scripts... sorry.
ephemient
+4  A: 

Why not create a script front-end that compiles the script if it hasn't been before or if the compiled version is out of date.

Here's the basic idea, this code could be improved a lot--search the path rather then assuming everything's in the same directory, handle other file extensions better, etc. Also i'm pretty green at haskell coding (ghc-compiled-script.hs):

import Control.Monad
import System
import System.Directory
import System.IO
import System.Posix.Files
import System.Posix.Process
import System.Process

getMTime f = getFileStatus f >>= return . modificationTime

main = do
  scr : args <- getArgs
  let cscr = takeWhile (/= '.') scr

  scrExists <- doesFileExist scr
  cscrExists <- doesFileExist cscr
  compile <- if scrExists && cscrExists
               then do
                 scrMTime <- getMTime scr
                 cscrMTime <- getMTime cscr
                 return $ cscrMTime <= scrMTime
               else
                   return True

  when compile $ do
         r <- system $ "ghc --make " ++ scr
         case r of
           ExitFailure i -> do
                   hPutStrLn stderr $
                            "'ghc --make " ++ scr ++ "' failed: " ++ show i
                   exitFailure
           ExitSuccess -> return ()

  executeFile cscr False args Nothing

Now we can create scripts such as this (hs-echo.hs):

#! ghc-compiled-script

import Data.List
import System
import System.Environment

main = do
  args <- getArgs
  putStrLn $ foldl (++) "" $ intersperse " " args

And now running it:

$ time hs-echo.hs "Hello, world\!"     
[1 of 1] Compiling Main             ( hs-echo.hs, hs-echo.o )
Linking hs-echo ...
Hello, world!
hs-echo.hs "Hello, world!"  0.83s user 0.21s system 97% cpu 1.062 total

$ time hs-echo.hs "Hello, world, again\!"
Hello, world, again!
hs-echo.hs "Hello, world, again!"  0.01s user 0.00s system 60% cpu 0.022 total
srparish
It's a really nice idea. +1It might not be too difficult to update it to deal with multiple architectures...
Norman Ramsey
This is a great idea, I wish I could upvote it twice
Jacob Stanley