Подтвердить что ты не робот

Есть ли быстрый запуск интерпретатора 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 быть полезным для сценариев.

4b9b3361

Ответ 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 для демона.