根据一个参数的类型动态调用函数(是“Haskell中的Zope”)

根据一个参数的类型动态调用函数(是“Haskell中的Zope”),haskell,Haskell,我正在学习Haskell,我发现它非常优雅和强大。但我仍然无法想象如何使用OOP来完成看起来如此简单的事情。以zope3/grok/pyramid web平台为例。他们有一个美丽的理念,即匹配内容和视图来呈现页面 在zope中,您有一个非正统内容类型树。当您请求URL时,您使用其路径遍历该树并获取上下文对象。路径的最后一部分是视图名称。根据对象的类型和视图名称获取视图。视图是从特定上下文类型和请求到响应对象的函数 因此,如果访问url,zope将从树的根开始。它将搜索一个名为aFolder的节点

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

在zope中,您有一个非正统内容类型树。当您请求URL时,您使用其路径遍历该树并获取上下文对象。路径的最后一部分是视图名称。根据对象的类型和视图名称获取视图。视图是从特定上下文类型和请求到响应对象的函数

因此,如果访问url,zope将从树的根开始。它将搜索一个名为aFolder的节点,然后在该对象内搜索另一个名为aFaq的节点,然后在该对象内搜索一个名为aQuestion的节点。因此,遍历函数可以返回任何类型的对象

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

但随后出现了一个问题,index.html是视图的名称。在简化的说明中[*] Zope查找上下文对类型viewname,并返回一个函数,大致来说,类型为{type of the context}->Request->Response。我可以编写一个函数render::Traversable->String->Response,用于检查可遍历的类型,但是,无论何时有人添加新的内容类型或新的视图,都必须更新该函数。view函数或子函数需要知道使用其数据的上下文的类型

那么,经验丰富的哈斯凯勒是如何解决这类问题的呢?有一段时间我在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 f中,我有一棵树,它必须是同质对象。所以我必须定义一个可遍历的包装器类型。但是如果有人想添加一个新类型,他应该修改我的代码。或者我可以创建一个可遍历的haskell类。这样就可以向树中添加一个future类型。但是,我如何从上下文、viewname映射到未知上下文的函数呢


[*]事实有点复杂。在zope中,可以在运行时使用任意接口标记对象或其类python没有接口的概念,这完全是zope的构造。这些接口形成一棵树。将视图与一对接口(名称)关联。当您请求上下文视图时,name返回与最特定接口关联的视图。其想法是,您可以更改视图,更改接口注册表,而不修改代码。

因此,我认为一个具体化的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"]
在您的情况下,您需要创建一个上下文类型类,然后在其上创建一个数据类型,称之为可上下文可怕的名称,但我当时想不出更好的名称,抱歉。 然后您可以存储一个Data.Map,从Contextable、ViewName到Contextable->Request->Response

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

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

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

instance Context OtherContext where
    maybeSpecificContext _   = Nothing
    ...

但我觉得这看起来有点难看。也许您这样做是为了上下文的属性,所以如果事情重叠,工作就少了。但是它会工作的。

所以,我认为一个具体化的类型类可能会有所帮助。我还是不知道你想要什么

{-# 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"]
在您的情况下,您需要创建一个上下文类型类,然后在其上创建一个数据类型,称之为可上下文可怕的名称,但我当时想不出更好的名称,抱歉。 然后您可以存储一个Data.Map,从Contextable、ViewName到Contextable->Request->Response

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

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

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

instance Context OtherContext where
    maybeSpecificContext _   = Nothing
    ...

但我觉得这看起来有点难看。也许您这样做是为了上下文的属性,所以如果事情重叠,工作就少了。但它会起作用。

如果我理解正确,你的问题可以归结为我想把未知类型的东西塞进一个容器,然后在运行时我想把它们拉出来,并根据它们的类型运行不同的函数

有几种可能的方法来解决这个问题,其复杂程度各不相同

首先,Haskell有类型擦除功能。编译程序时,在编译器检查所有类型是否正常后,它会删除它们。所以,在运行时,不可能知道任何东西都是什么类型的。这仅仅是因为在编译时不可能不知道某个东西的类型。这是一个 说Haskell是静态类型的冗长方式

刚才已经说过,Haskell和一些扩展可以用来实现这一点。然而,我怀疑你的问题可能有一个更简单的解决方案

好的,所以你想储存不同类型的东西。但是一旦你储存了这些东西,你到底想对它们做什么呢?如果您只是想根据数据的类型对其执行一些函数,那么为什么不存储函数本身呢

想想看。它不是一个包含许多不同类型的容器,而是包含许多具有相同类型的函数。获取所需的函数,然后运行它。完成了


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

如果我理解正确,您的问题可以归结为我想将未知类型的东西装入一个容器,然后在运行时,我想将它们取出,并根据它们的类型运行不同的函数

有几种可能的方法来解决这个问题,其复杂程度各不相同

首先,Haskell有类型擦除功能。编译程序时,在编译器检查所有类型是否正常后,它会删除它们。所以,在运行时,不可能知道任何东西都是什么类型的。这仅仅是因为在编译时不可能不知道某个东西的类型。这是一种说Haskell是静态类型的冗长方式

刚才已经说过,Haskell和一些扩展可以用来实现这一点。然而,我怀疑你的问题可能有一个更简单的解决方案

好的,所以你想储存不同类型的东西。但是一旦你储存了这些东西,你到底想对它们做什么呢?如果您只是想根据数据的类型对其执行一些函数,那么为什么不存储函数本身呢

想想看。它不是一个包含许多不同类型的容器,而是包含许多具有相同类型的函数。获取所需的函数,然后运行它。完成了


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

那么在您的异端树中,它到底包含什么?文件夹树,然后将名称视为叶子?这是什么意思?树是由任何类型的python类组成的。这些类中的一些是容器,另一些只是项。容器可以是文件夹、常见问题解答、博客、新闻文件夹。。。项目可以是文件、图像、页面、新闻项目、问题……程序员可以添加自己的容器和项目,而无需修改以前的代码。遍历可以在任何一个台阶上结束,不需要一片叶子。因此,我们以一个上下文结束,它可以是任何类别的文件夹、博客、页面、问题或其他内容。视图由路径中的类型上下文和名称决定。我发现困难的是如何获得一个作用于特定类型上下文的函数,视图,这在编译时是未知的。好吧,一个问题似乎是它不是类型安全的,就像现在一样,注册表中的任何错误数据都会导致运行时类型错误。所以我们要么用类型来证明注册表中有正确的数据,要么重新格式化它,这样就不重要了。没错!对于像我这样的新手来说,一切看起来都需要副作用,或者应该是不安全的。所以我希望有人来告诉我,如果你做了这个改变,你可以得到同样的效果,同时保留类型信息。那么在你的异端树中,它到底包含了什么?文件夹树,然后将名称视为叶子?这是什么意思?树是由任何类型的python类组成的。这些类中的一些是容器,另一些只是项。容器可以是文件夹、常见问题解答、博客、新闻文件夹。。。项目可以是文件、图像、页面、新闻项目、问题……程序员可以添加自己的容器和项目,而无需修改以前的代码。遍历可以在任何一个台阶上结束,不需要一片叶子。因此,我们以一个上下文结束,它可以是任何类别的文件夹、博客、页面、问题或其他内容。视图由路径中的类型上下文和名称决定。我发现困难的是如何获得一个作用于特定类型上下文的函数,视图,这在编译时是未知的。好吧,一个问题似乎是它不是类型安全的,就像现在一样,注册表中的任何错误数据都会导致运行时类型错误。所以我们要么用类型来证明注册表中有正确的数据,要么重新格式化它,这样就不重要了。没错!对于像我这样的新手来说,一切看起来都需要副作用,或者应该是不安全的。所以我希望有人来告诉我,如果你做了这个改变,你可以得到同样的效果,保持原样
同时提供信息。我将跟踪您到Data.Map。我看到的问题是,有用的视图必须知道上下文的类型,文件夹->请求->响应或新闻项->请求->响应,因为视图的思想是显示特定于上下文的内容。但当然,这里有一个类型不匹配的问题,这是我不知道如何解决的部分。如果你只有两个不同的上下文:文件夹和文件,以及两个函数,一个文件夹->应答和一个文件->应答,你可以将它们存储在同一类型的文件夹文件->应答,如果给出了错误的一个,它会以某种方式失败,或者使用错误,或者通过返回501响应或其他方式很好地失败。您可以在不同的上下文中执行相同的操作。或者,由于上下文很多,要创建包装类型,context=CFolder Folder | CFile File |。。。。但有两个问题。第一,它不是优雅的,第二,如果有人想添加一个新类型,他应该修改这个函数,如果我们谈论的是一个可扩展的框架,这是不正确的。从逻辑上讲,我们想用一个超类型统一上下文,但我们不知道接口应该是什么。因此,我们应该使用所有类型的超类型Data.Dynamic。我跟着你到Data.Map。我看到的问题是,有用的视图必须知道上下文的类型,文件夹->请求->响应或新闻项->请求->响应,因为视图的思想是显示特定于上下文的内容。但当然,这里有一个类型不匹配的问题,这是我不知道如何解决的部分。如果你只有两个不同的上下文:文件夹和文件,以及两个函数,一个文件夹->应答和一个文件->应答,你可以将它们存储在同一类型的文件夹文件->应答,如果给出了错误的一个,它会以某种方式失败,或者使用错误,或者通过返回501响应或其他方式很好地失败。您可以在不同的上下文中执行相同的操作。或者,由于上下文很多,要创建包装类型,context=CFolder Folder | CFile File |。。。。但有两个问题。第一,它不是优雅的,第二,如果有人想添加一个新类型,他应该修改这个函数,如果我们谈论的是一个可扩展的框架,这是不正确的。从逻辑上讲,我们想用一个超类型统一上下文,但我们不知道接口应该是什么。因此,我们应该使用所有类型的超类型Data.Dynamic。我也打算提出这个建议。特别是,路由表不创建上下文,而是调用URL的处理程序,处理程序隐式地理解上下文。这是一种有趣的方法,但这不是浪费吗?例如,对于FAQ类型的所有记录,只有一个addQuestion视图/函数。因此,如果树中有五个常见问题,那么同一函数应该有五个副本。而且,如果有人决定添加一个新视图,那么他应该访问树上的所有节点,并在每个正确类型的节点上添加新函数的副本。特别是,路由表不创建上下文,而是调用URL的处理程序,处理程序隐式地理解上下文。这是一种有趣的方法,但这不是浪费吗?例如,对于FAQ类型的所有记录,只有一个addQuestion视图/函数。因此,如果树中有五个常见问题,那么同一函数应该有五个副本。而且,如果有人决定添加新视图,那么他应该访问树上的所有节点,并在每个正确类型的节点上添加新函数的副本。