1

我正在学习 Haskell,我发现它非常优雅和强大。但是我仍然无法想象如何使用 OOP 来做看起来如此简单的事情。以 zope3/grok/pyramid 网络平台为例。他们有一个美丽的哲学,基于匹配内容和视图来呈现页面。

在 zope 中,您有一个异端内容类型树。当您请求一个 URL 时,您使用它到traverse该树的路径并获取一个context对象。路径的最后一部分是“视图名称”。您将View根据对象的类型和视图名称获得。View是从特定上下文类型Request到对象的Response函数。

因此,如果您访问 URL http://example.com/aFolder/aFaq/aQuestion/index.html,zope将从树的根部开始。它将搜索一个名为 的节点aFolder,然后在该对象内搜索另一个名为的节点,并aFaq在其中搜索一个名为的节点aQuestion。因此该traverse函数可以返回任何类型的对象。

这里没有问题。因为我只遍历树,所以我可以在 Haskell 中创建一个新的包装类型或一个名为的类Traversable,所以我会有一个Tree Traversable和一个函数traverse :: Tree Traversable -> [string] -> Traversable

但随之而来的一个问题index.html是视图的名称。在一个简化的 exposition[*] 中,Zope 查找对(上下文类型,视图名称)并返回一个函数,粗略地说,类型为{type of the context} -> Request -> Response。我可以编写一个函数render :: Traversable -> String -> Response来检查可遍历的类型,但是,只要有人添加新的内容类型或新视图,就必须更新该函数。视图函数(或子函数)需要知道上下文的类型才能使用其数据。

那么,经验丰富的哈斯克尔是如何解决这类问题的呢?有一段时间我在 GADT 中思考过,但我不确定这是否会有所帮助,或者是否有更简单的替代方案。

谢谢!

编辑:伪代码澄清

def traverse(node, path):
    # returns the context and the view name
    itemname = path[0]
    if hasattr(node, itemname):
       # The next element in the path is a subnode of the node, let's visit it
       return traverse(node[itemname], path[1:])
    else:
       # We can't go down the tree anymore, we found our context and view name
       viewname = itemname
       return node, viewname

def render(tree, request):
    path = somehow_get_path_from_request(request)
    context, viewname = traverse(tree, path)
    # We get the view from a registry which is a map/dictionary
    view = registry[(context, viewname)]
    # here comes the problem:
    #   view is an object that knows exactly the type of context
    #   A view for a Question object can use its 'question' and 'answer' fields
    #   A View for a Folder can use its 'items' fields, a view for Image can use
    #   its 'img' field.
    #
    return view.render(context, request)

这是我不知道如何在haskell中做的事情。在 haskell f 我有一棵树,它必须是同质对象。所以我必须定义一个包装类型 Traversable。但是如果有人想添加一个新类型,他应该修改我的代码。或者我可以创建一个 Haskell 类 Traversable。通过这种方式,可以将未来类型添加到树中。但是我怎么能从 (context, viewname) 映射到一个未知上下文的函数呢?

[*] 事实要复杂一些。在zope中,一个对象或其类可以在运行时任意标记interfaces(python没有接口的概念,这完全是zope的构造)。这些接口形成一棵树。您将视图与一对(接口、名称)相关联。当您请求 (context, name) 的视图时,它会返回与最具体的界面关联的视图。这个想法是您可以更改视图,更改接口注册表而不修改代码。

4

2 回答 2

5

如果我理解这一点,您的问题归结为“我想将未知类型的东西塞入容器中,然后在运行时我想将它们拉出来并根据它们的类型运行不同的功能”。

有几种可能的方法来解决这个问题,具有不同程度的复杂性。

首先,Haskell 有类型擦除。当你编译一个程序时,在编译器检查所​​有类型都OK后,它会删除它们。因此,在运行时不可能判断任何东西的类型。这只有效,因为在编译时不可能不知道某物的类型。这是说“Haskell 是静态类型的”的冗长方式。

刚刚说过,“带有一些扩展的 Haskell”可以做到这一点。但是,我怀疑您的问题可能有一个更简单的解决方案......

好的,所以你想存储不同类型的东西。但是,一旦您存储了这些东西,您实际上想对它做什么呢?如果你只是想对数据执行一些函数(取决于它的类型),那么为什么不只存储函数本身呢?

想想看。与其内部包含许多不同类型的容器,不如简单地包含许多具有相同类型的函数。你获取你想要的函数,然后运行它。完毕。

那是什么?您需要能够对数据运行多个不同的功能吗?好吧,在这种情况下,请尝试存储一个包含您需要的所有功能的数据结构。

于 2012-07-09T09:33:06.553 回答
3

所以,我认为具体化的 Typeclass 可能会有所帮助。我还是不太清楚你想要什么。

{-# LANGUAGE ExistentialQuantification #-}

data Showable = forall a. (Show a) => Showable a

instance Show Showable where
  show (Showable x) = show x

exampleList = [Showable 42, Showable "have a nice day"]

在您的情况下,您需要创建一个 Context 类型类,然后在其上创建一个数据类型,称之为 Contextable (可怕的名字,但我当时想不出更好的东西,抱歉)。
然后,您可以将 Data.Map 从 (Contextable, ViewName) 存储到 Contextable -> Request -> Response。

现在,如果您有一个 SpecificContext,它的关联视图中有您需要的一些信息,但其他上下文都没有,您可以执行类似的操作

class Context a where
    maybeSpecificContext :: a -> MaybeSpecificContext
    ...

instance Context SpecificContext where
    maybeSpecificContext ctx = Just ctx
    ...

instance Context OtherContext where
    maybeSpecificContext _   = Nothing
    ...

但我认为这看起来有点难看。也许您为上下文的属性执行此操作,因此如果事情重叠,工作量就会减少。但它会起作用的。

于 2012-07-07T18:46:00.660 回答