2014-01-25 2 views
2

Задача здесь заключается в следующем:Как создать библиотеку, которая может обслуживать модуль Haskell по сети?

  1. Клиент запрашивает функцию, которая будет выполняться при условии имя и его аргументы.
  2. Сервер выполняет функцию с предоставленными аргументами и возвращает результат.

Что-то вроде этого довольно легко реализовать на динамически типизированном языке, таком как Python. Но, учитывая статичную типизацию Хаскелла, это становится все труднее.

Вот мой начальный удар по проблеме:

  • Предположим, что функции в модуле только принимать аргументы, которые сериализации. Внедрите класс типа Serializable (идея взята из Cloud Haskell).

  • Сохраните функции на карте с использованием имени функции. Не работает. Значения карты (объекты функции) не обязательно должны быть одного типа.

  • Единственное, что я мог придумать, чтобы разобрать модуль и создать цепочку if else утверждений (или case заявление), ссылающихся правильную функцию, основанную на входной строки, а затем сериализации результат.

    Это дает жалкую производительность, так как наихудший случай «поиска» времени функции зависит от количества функций в модуле.

Каков правильный подход к решению этой проблемы? Есть ли что-то тривиальное, что мне здесь не хватает?

+0

Если единственное, что беспокоит последний параметр, это линейный поиск по времени, вы можете написать/сгенерировать ряд функций типа 'Bytes -> Bytes', по одному для каждой функции f, формы let (arg1, ..., argN) = deSerializeArgs ввод в сериализации $ f arg1 ... argN'. Эти функции затем могут быть помещены в карту или любую другую структуру, которая вам нравится. Конечно, это все еще довольно неплохая работа и довольно уродливая. – delnan

+0

Есть ли проблема с использованием cloud-haskell (aka distrib-process)? –

+0

Вы можете использовать «Карта» для поиска функций - например, 'Map String Handler', где' Handler' - это что-то вроде 'Bytes -> Bytes' или' FileHandle -> IO() '. У вас просто будет написать оболочку для каждой функции RPC. – ErikR

ответ

1

Это решение, в котором я мог бы придумать. Идея - от @ delnan/@ user4502 в сочетании с библиотекой Aeson.

Предположим, я хотел бы 'serverize' модуль Foo. Будет создан новый модуль Bar, где есть обертки для функций в модуле Foo.

Например, если функция splitAt в модуле Foo имеет тип подписи:

splitAt :: Int -> [Int] -> ([Int], [Int]) 

Соответствующая функция обертка _splitAt в Bar будет иметь тип подписи:

_splitAt :: ByteString -> ByteString 

Сгенерированный Bar модуль будет выглядеть примерно так:

module Bar where 

    import qualified Data.ByteString.Lazy as L 
    import qualified Data.ByteString.Lazy.Char8 as LC 
    import qualified Foo as F 
    import qualified Data.Aeson as DA 

    errMsg = LC.pack "Invalid Input" 

    _splitAt :: L.ByteString->L.ByteString 
    _splitAt input = let args = DA.decode input :: Maybe (Int, [Int]) 
        in case args of 
         (Just (arg1, arg2)) -> DA.encode $ F.splitAt arg1 arg2 
         Nothing -> errMsg 

    -- And the rest of the functions 

Теперь, поскольку все функции имеют тип ByteString -> ByteString, их можно поместить в Map.

Это работает, но может ли код _splitAt быть реорганизованным, чтобы избежать предложения типа (не уверены в терминологии) Maybe (Int, [Int])?

1

Предположим, что все наши функции работают в пределах некоторой общей монады M, что, я думаю, будет распространенным случаем (для обработки ошибок и т. Д.). Таким образом, наши функции будут иметь такие типы, как f :: a -> b -> c -> M d.

Идея состоит в том, чтобы преобразовать все функции в один общий тип. Предположим, мы используем aeson. Тогда общий тип мы ищем это

type RPC m = forall i o . (FromJSON i, ToJSON o) => i -> m o 

(с использованием RankNTypes).

Мы можем действовать следующим образом:

  • Учитывая имя функции, используйте TH-х reify, чтобы проверить его тип.
  • Перечислите, сколько аргументов функции имеет и применяет соответствующую функцию uncurrying. Например, в нашем примере выше, имеет 3-х аргументов, так что бы быть преобразованы в

    uncurry3 f :: (a, b, c) -> M d 
    

    где uncurry3 может быть легко генерируется автоматически с помощью TH (возможно, есть пакет для этого). Не забывайте преобразовывать функции типа M a в () -> M a.

  • Теперь все функции вписываются в RPC M, поэтому мы можем создать Map String (RPC M) из списка функций (reify также дает нам имена функций). Например, что-то вроде mkRPC :: [Name] -> Q Exp.
  • Наконец, мы создаем обработчик, который принимает Map String (RPC M), пользовательский запрос и обрабатывает его, вызывая соответствующую функцию с карты.

Клиентская сторона будет очень похожа.

+0

Мне интересно, предпочитаете ли вы TH для подхода типа класса - например, [этот пост] (http://stackoverflow.com/questions/21362498/applying-a-list-of-strings-to-an-arbitrary-функция). Благодаря! – ErikR

+0

@ user5402 Я уже использовал TH в проекте, поэтому это был естественный выбор. Подход типа типа был бы приятным, но я боюсь, что невозможно избежать «UndecidableInstances» или «OverlappingInstances», что обычно делает результат бесполезным. Общая проблема заключается в том, что если у вас есть 1-аргументная функция 'a -> b', вы можете специализировать' b' на 'c -> d', и вдруг у вас есть функция с двумя аргументами' a -> c -> d '. –

 Смежные вопросы

  • Нет связанных вопросов^_^