我可以在Clojure中重载count函数吗?

时间:2017-05-19 18:26:05

标签: clojure

在Python中如果我想自定义如何定义对象大小的方法,我为类定义了__len__方法,这改变了len函数的行为。有没有办法在Clojure中使用count函数执行此操作?如果是这样,怎么样?

5 个答案:

答案 0 :(得分:4)

当你从一种范式转向时,这是一个合理的问题 另一个是OO功能,但可能不是问题。在像这样的语言 Clojure,您通常首先要弄清楚如何表示您的数据 一个现有的数据结构(集合)而不是定义一个新的 用于表示数据的结构。这个想法是只有几个不同的数据 具有大量明确定义和理解的功能的结构 以高效可靠的方式对这些结构进行操作。作为一个 结果,一旦你决定如何表示你的图表,你就会 可能会发现像计算顶点数一样 琐碎的 - 你可能只需要使用一个非常简单的函数使用reduce 而这又使用其他核心功能,例如现有的count()。一把钥匙 熟练掌握Clojure是在学习核心功能是什么 他们做什么。

在OO语言中,您封装数据并将其隐藏在对象中。这个 意味着你现在需要定义操作其中数据的方法 宾语。为了支持多态性,你经常会做一些事情,比如添加一个 对象特定的大小或长度方法。在Clojure中,底层数据结构 不隐藏,并且经常构建在其中一种标准集合类型上, 所以你不需要拖曳尺寸/长度功能,你可以使用标准 计数功能。当标准集合不适合您需要时 更多,那么你倾向于看协议,你可以在哪里定义你的 自己的专业功能,尺寸。

在您的情况下,不太可能需要协议或记录 - 代表 图表非常适合标准集合,我不知道 如果你可以重新实现你在C或C ++中所做的事情,那就太惊讶了 Clojure的线条少得多,而且可能更具说服性和更清晰 方式。首先看看标准的Clojure集合类型是如何形成的 用于表示您的图表。想想你想如何操作图表 以及您是否最好将图表表示为节点或顶点然后 看看你将如何回答诸如此类问题中的问题 图表&#34?;并看看如何使用可用的内置功能获得答案 功能。

当你转向功能时,你需要以不同的方式思考问题 范例。你会得到一个真正的" Aha"那一刻 便士滴。一旦它发生,你可能会非常惊讶它有多好,但是 直到那一刻,你可能会经历一些挫折和头发 拉动。这场战斗是值得的,因为你甚至可能会发现你的旧比较 编程技巧的好处,当你必须回到C / C ++或Python,你的 代码更清晰,更简洁。尽量避免重现的诱惑 你在Clojure中用C / Python做了什么。相反,专注于你想要的结果 实现并了解所提供的语言设施将如何帮助您 那。

答案 1 :(得分:1)

您的评论说您正在处理图表。考虑到使用标准数据结构的好建议,让我们考虑如何表示它们。

您通常将图表表示为地图节点 - >节点的集合。例如,

(def graph {:a [:b], :b [:a :c]})

然后

(count graph)                 
=> 2

但是,如果您确保每个节点都有一个地图条目,即使是那些没有传入弧线的条目,那么您只需要count图形的地图。一个函数添加空条目是......

(defn add-empties [gm]
  (if (empty? gm)
    gm
    (let [EMPTY (-> gm first val empty)
          missing (->> gm
                     vals
                     (apply concat)
                     (remove gm))]
      (reduce (fn [acc x] (assoc acc x EMPTY)) gm missing))))

例如,

(add-empties graph)
=> {:a [:b], :b [:a :c], :c []}

(count(add-empties graph))
=> 3

答案 2 :(得分:1)

count对图表意味着什么?

count应该为图表返回什么?我可以想到两个同样明显的选项 - 图中的节点的数量或边缘的数量。所以也许count对图表来说不是很直观。

实施计数

尽管如此,您当然可以在Clojure中定义自己的计数数据结构。关键是要实施clojure.lang.Counted interface

现在,如果通过以下deftype代表图表:

(deftype NodeToEdgeGraph [node->neighbors]
  ...)

我们可以count编辑:

(deftype NodeToEdgeGraph [node->neighbors]
  clojure.lang.Counted
  (count [this]
    (count node->neighbors))
  ...)

如果我们将图形表示为将每个节点映射到其“邻居”集合的映射(其中节点被视为“邻居”,如果且仅当两者之间存在边缘时),我们希望count返回节点的数量

或者,我们可以将图表表示为一组对(在有向图的情况下是有序的;或者在无向图的情况下是无序的)。在这种情况下,我们有:

(deftype EdgeGraph [edges]
  ...)

我们可以让count在图表中返回 edge 的数量:

(deftype EdgeGraph [edges]
  clojure.lang.Counted
  (count [this]
    (count edges))
  ...)

到目前为止,我们一直在底层结构上使用count来为图表实现count。这是有效的,因为底层结构方便地具有与我们计算每个相应图形表示的方式相同的计数。但是,没有理由我们不能使用任何一种表示法来计算。此外,还有其他方法可以表示可能与我们想要计算的方式不太一致的图形。在这些情况下,我们可以始终保持自己的内部计数:

(deftype ???Graph [cnt ???]
  clojure.lang.Counted
  (count [this]
    cnt)
  ...)

在这里,我们依靠图操作的实现来适当地维护cnt

内置或自定义类型?

其他人建议仅使用内置数据结构来表示图形。实际上,如果您查看NodeGraphEdgeGraph,您会发现count只是委托给底层结构。因此,对于这些表示(以及count的定义),您可以简单地删除deftype并直接在map s / set s上实施图表操作。但是,让我们看看在自定义类型中封装图形的一些优点:

  • 易于实施替代表示。使用自定义类型,您可以拥有无​​限数量的图表表示。您需要做的就是定义一些protocols并根据这些协议定义图形操作。虽然您也可以将协议扩展到内置类型,但每个内置类型仅限于一个表示/实现。这对你来说可能是也可能不够。
  • 维护内部不变量。使用自定义类型,您可以更好地控制内部结构。也就是说,您可以以维护任何必要内部不变量的方式实现协议/接口。例如,如果要实现无向图,则可以以确保从节点NodeGraph向节点A添加边的方式实现B,同时从节点{{1}添加边}到节点B。同样删除边缘。对于内置类型,这些不变量只能由您实现的图形函数维护。 Clojure核心函数不会为你维护这些不变量,因为它们对它们一无所知。因此,如果您将“图形”(通过内置类型)移交给某些调用非图形函数的函数,则无法保证将获得有效的图形。另一方面,自定义类型仅允许您实现的操作,并将以实现它们的方式执行它们。只要您注意所实施的所有操作都保持适当的不变量,您就可以放心,这些不变量将始终保持不变。

另一方面,有时仅使用内置类型是合适的。例如,如果您的应用程序仅使用图形操作,那么简单地使用内置类型可能会更方便。另一方面,如果您的应用程序是面向图形的并且进行了大量的图形操作,那么从长远来看实现自定义类型可能会更好。

当然,你不会被迫选择一个而不是另一个。使用协议,您可以为内置类型和自定义类型实现图形操作。这样,您可以根据您的应用选择“快速和脏”和“沉重但强大”。这里“沉重”只意味着可能需要更多的工作来使用具有在Clojure集合接口上实现的功能的图形。换句话说,在某些情况下,您可能需要将图形转换为其他类型。这在很大程度上取决于您为自定义图形类型实现这些接口所花费的精力(以及它们对图形有意义的程度)。

答案 3 :(得分:1)

顺便说一句,您不能使用with-redefs或任何相关功能覆盖该功能。这里有一个隐藏的技巧:如果你检查count函数的源代码,你会在其元数据中看到一个有趣的inline属性:

(defn count
  "Returns the number of items in the collection. (count nil) returns
  0.  Also works on strings, arrays, and Java Collections and Maps"
  {
   :inline (fn  [x] `(. clojure.lang.RT (count ~x)))
   :added "1.0"}
  [coll] (clojure.lang.RT/count coll))

此属性表示函数的代码将按原样插入到生成的字节码中,而不调用原始函数。大多数通用核心功能都具有这样的属性。这就是为什么你无法覆盖countgetintnth等等。

答案 4 :(得分:0)

抱歉,不适用于您无法控制的现有收藏类型。

您可以定义自己的计数,了解您的需求并在您的代码中使用它,但不幸的是,clojure不使用通用协议进行计数,因此您无处可附加,这将扩展现有集合的计数。

如果计算一个协议而不是一个接口,这对你来说会更容易,尽管它早于语言演变中的协议。

如果您正在制作自己的收藏类型,那么您当然可以随意实施计数。