Есть ли быстрый запуск интерпретатора Haskell, подходящий для сценариев?
Кто-нибудь знает о быстром запуске интерпретатора Haskell, который был бы подходящим для использования при написании сценариев оболочки? Запуск "hello world" с использованием Hugs занял 400 мс на моем старом ноутбуке и занимает 300 мс на моем нынешнем Thinkpad X300. Это слишком медленно для мгновенного ответа. Времена с GHCi похожи.
Функциональные языки не должны быть медленными: оба Objective Caml и Moscow ML запускают мир привет в 1 мс или меньше.
Уточнение. Я большой пользователь GHC, и я знаю, как использовать GHCi. Я знаю все о компиляции, чтобы быстро все наладить. Расходы на анализ должны быть совершенно неактуальными: если ML и OCaml могут начинаться в 300 раз быстрее, чем GHCi, тогда есть возможности для улучшения.
Я ищу
- Удобство написания сценариев: один исходный файл, не двоичный код, тот же код работает на всех платформах
-
Производительность, сопоставимая с другими интерпретаторами, включая быстрый запуск и выполнение для простой программы, например
module Main where
main = print 33
Я не ищу скомпилированную производительность для более серьезных программ. Все дело в том, чтобы увидеть, может ли Haskell быть полезным для сценариев.
Ответы
Ответ 1
Использование ghc -e
в значительной степени эквивалентно вызову ghci
. Я считаю, что GHC runhaskell
компилирует код во временный исполняемый файл перед его запуском, а не интерпретирует его как ghc -e
/ghci
, но я не уверен на 100%.
$ 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
Насколько сложно просто скомпилировать все ваши "скрипты" перед их запуском?
Изменить
А, предоставление двоичных файлов для нескольких архитектур - это действительно боль. Я пошел по этой дороге раньше, и это не очень весело...
К сожалению, я не думаю, что лучше сделать любой запуск компилятора Haskell лучше. Декларативный характер языка означает, что сначала необходимо прочитать всю программу, прежде чем пытаться описать что-либо, невзирая на выполнение, а затем вы либо страдаете ценой анализа строгости, либо ненужной лени и тряски.
Популярные языки сценариев (shell, Perl, Python и т.д.) и языки на основе ML требуют только одного прохода... хорошо, ML требует статического прохода typecheck, и Perl имеет этот забавный 5-проход схема (с двумя из них работает в обратном порядке); в любом случае, являясь процедурной, означает, что компилятор/интерпретатор намного легче выполнять сборку битов программы вместе.
Короче говоря, я не думаю, что это может стать намного лучше этого. Я не проверял, есть ли у Hugs или GHCi более быстрый запуск, но какая-либо разница там все еще фаара от языков, отличных от Haskell.
Ответ 2
Почему бы не создать внешний интерфейс script, который компилирует script, если он не был до или если скомпилированная версия устарела.
Вот основная идея, этот код может быть значительно улучшен - поиск по пути, а затем принятие всего в том же каталоге, улучшение других расширений файлов и т.д. Также я довольно зеленый при кодировании haskell (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
Теперь мы можем создавать такие скрипты (hs-echo.hs):
#! ghc-compiled-script
import Data.List
import System
import System.Environment
main = do
args <- getArgs
putStrLn $ foldl (++) "" $ intersperse " " args
И теперь его запускаем:
$ 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
Ответ 3
Если вы действительно обеспокоены скоростью, вам будет мешать повторное разбор кода для каждого запуска. Haskell не нужно запускать из интерпретатора, скомпилировать его с помощью GHC, и вы должны получить отличную производительность.
Ответ 4
У вас есть две части к этому вопросу:
- Вы заботитесь о производительности
- вы хотите создавать сценарии
Если вы заботитесь о производительности, единственным серьезным вариантом является GHC, который очень быстр: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=all
Если вы хотите что-то светить для сценариев Unix, я бы использовал GHCi. Это примерно в 30 раз быстрее, чем Hugs, но также поддерживает все библиотеки для взлома.
Итак, установите GHC сейчас (и получите GHCi бесплатно).
Ответ 5
Как насчет того, что демон ghci и фидер script, который принимает путь и местоположение script, связывается с уже запущенным процессом ghci для загрузки и выполнения script в соответствующей директории и передает вывод обратно на фидер script для stdout?
К сожалению, я понятия не имею, как написать что-то подобное, но похоже, что это может быть очень быстро, судя по скорости: l в ghci. Как кажется, большая часть затрат в runhaskell заключается в запуске ghci, а не в анализе и запуске script.
Изменить: После некоторых игр я нашел Hint package (обертка вокруг API GHC), чтобы быть здесь безупречным. Следующий код загрузит переданное в имени модуля (здесь предполагается, что оно находится в одном каталоге) и будет выполнять основную функцию. Теперь осталось "все", чтобы сделать его демоном и принять сценарии на трубе или сокете.
import Language.Haskell.Interpreter
import Control.Monad
run = runInterpreter . test
test :: String -> Interpreter ()
test mname =
do
loadModules [mname ++ ".hs"]
setTopLevelModules [mname]
res <- interpret "main" (as :: IO())
liftIO res
Edit2: Что касается stdout/err/in go, используя этот конкретный трюк GHC Это похоже на можно было бы перенаправить std клиентской программы в программу фидера, а затем в некоторые именованные каналы (возможно), к которой подключен демон каждый раз, а затем вывести демона обратно на другой именованный канал, который программа фидера слушать. Псевдо-пример:
grep ... | feeder my_script.hs | xargs ...
| ^---------------- <
V |
named pipe -> daemon -> named pipe
Здесь фидер будет представлять собой небольшую скомпилированную программу жгута, чтобы просто перенаправить std в, а затем вернуться из демона и указать имя и расположение script для демона.