这里的任务如下:
像Python这样的动态类型语言很容易实现这样的东西。但是,考虑到Haskell的静态类型,这似乎越来越困难。
以下是我对这个问题的初步尝试:
假设模块中的函数只接受可序列化的参数。实现Serializable
类型类(从Cloud Haskell获取的想法)。
将函数存储在使用函数名称键入的映射中。不行。地图(功能对象)的值不必是同一类型。
我唯一能想到的就是解析模块并生成一串if else
语句(或case
语句),根据输入字符串调用正确的函数然后序列化结果。
这给出了可悲的性能,因为函数的最坏情况“查找”时间将取决于模块中的函数数量。
解决此问题的正确方法是什么?我在这里缺少一些微不足道的东西吗?
答案 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)。
我们可以按照以下步骤进行:
枚举函数的参数数量并应用相应的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)
,一个用户请求并处理它,从地图调用相应的函数。客户端非常相似。