我正在学习 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) 的视图时,它会返回与最具体的界面关联的视图。这个想法是您可以更改视图,更改接口注册表而不修改代码。