如何设计一个可以通过网络为Haskell模块提供服务的库?

时间:2014-01-25 14:18:59

标签: haskell template-haskell

这里的任务如下:

  1. 客户端请求提供名称及其参数的函数。
  2. 服务器使用提供的参数执行函数并返回结果。
  3. 像Python这样的动态类型语言很容易实现这样的东西。但是,考虑到Haskell的静态类型,这似乎越来越困难。

    以下是我对这个问题的初步尝试:

    • 假设模块中的函数只接受可序列化的参数。实现Serializable类型类(从Cloud Haskell获取的想法)。

    • 将函数存储在使用函数名称键入的映射中。不行。地图(功能对象)的值不必是同一类型。

    • 我唯一能想到的就是解析模块并生成一串if else语句(或case语句),根据输入字符串调用正确的函数然后序列化结果。

      这给出了可悲的性能,因为函数的最坏情况“查找”时间将取决于模块中的函数数量。

    解决此问题的正确方法是什么?我在这里缺少一些微不足道的东西吗?

2 个答案:

答案 0 :(得分:1)

这是我能提出的解决方案。这个想法来自@ delnan / @ user4502和Aeson库。

假设我想'服务化'模块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 :(得分:1)

让我们假设我们所有的函数都在一些常见的monad 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),一个用户请求并处理它,从地图调用相应的函数。

客户端非常相似。