【问题标题】:Conditional "assignment" in functional programming函数式编程中的条件“赋值”
【发布时间】:2017-05-24 23:51:43
【问题描述】:

我正在编写一些没有副作用的程序,但我的代码可读性不强。 考虑以下代码:

(let [csv_data (if header_row (cons header_row data_rows) data_rows)]
)

我正在尝试在代码块中使用csv_data。对header_row 的存在进行调节的干净方法是什么?我看过if-let,但看不出这对这里有什么帮助。

我也遇到过功能性 for 循环的类似情况,我将结果绑定到局部变量,代码看起来像一堆表达式。

在很多情况下,我真的需要创建一个单独的辅助函数吗? 我在这里错过了什么?

【问题讨论】:

  • 我的回答错了,所以我同时删除了。
  • 嗯,我认为只有一个小错误,但这是一个完全有效的答案。

标签: clojure functional-programming lisp


【解决方案1】:

首先,不要使用下划线,最好使用破折号。

第二,一点辅助功能也没有错;毕竟,这似乎是处理您的特定数据格式的要求。

第三,如果您可以更改数据以跳过这些决策并为所有极端情况提供统一的表示,那就更好了。标题行包含不同类型的数据(列名?),因此您可能更愿意将它们分开:

(let [csv {:header header :rows rows}]
  ...)

或者也许在某些时候你可以让“标题”和“行”属于同一类型:行序列。然后你可以直接连接它们。 ensure-x 成语是标准化数据的一种非常常用的方法:

(defn ensure-list [data]
  (and data (list data)))

例如:

user=> (ensure-list "something")
("something")
user=> (ensure-list ())
(())
user=> (ensure-list nil)
nil

因此:

(let [csv (concat (ensure-list header) rows)]
  ...)

【讨论】:

  • 1. Clojure 喜欢破折号的原因有什么历史原因吗? 2.我正在导出数据,所以我必须合并所有内容并将其传递给一个库。
  • @m33lky (1) 破折号分隔符号来自 Lisp;这只是您可以在其他语言中找到的约定。 (2) 也许在某些时候你可以让“标题”和“行”属于同一类型:行序列。然后你可以直接连接它。我同意将问题推到其他地方。
  • @m33lky Clojure style guide 建议将 "lisp-case" 用于变量名称(因为 Clojure 是一种 lisp 方言)。 lisp 使用破折号而不是下划线的原因似乎是历史原因,因为在早期的 lisp 终端上输入破折号与下划线的难度More info
  • @coredump 我正在使用clojure.data.csv,它将序列中的行作为输入,但我仍然看不到如何很好地连接空标题。
  • @m33lky concat 适用于序列。也许我遗漏了一些东西,但这里有一个简单的例子:pastebin.com/raw/b3FP6DHg
【解决方案2】:

在考虑了命名空间中单行辅助函数的“成本”之后,我想出了一个本地函数:

(let [merge_header_fn   (fn [header_row data_rows] 
                          (if header_row 
                            (cons header_row data_rows) 
                            data_rows))
      csv_data (merge_header_fn header_row data_rows) ]
  ...
  <use csv_data>
  ...
) 

除非有人能提出更优雅的处理方式,否则我会保留这个作为答案。

【讨论】:

  • 我在这里没有看到改进,内联版本更易读恕我直言。另外,您所说的“成本”是什么?旧参考但仍然相关:groups.google.com/forum/message/raw?msg=comp.lang.lisp/…
  • 一行发生了太多事情。也缺乏结构。
  • 我经常制作这样的本地辅助函数,如果它们不会在本地上下文之外使用(我修改了你的格式以便于阅读)。无论是let 表达式中的本地表达式还是def 表达式中的顶级表达式,都没有“成本”。与往常一样,使代码简单、明显和正确胜过任何其他担忧。
  • 嗯,我想很明显我正在尝试做一个类似于你用命令式语言做的作业。这段代码本身没有什么意义,这就是为什么我觉得它不应该在本地范围之外并弄乱命名空间。为什么投反对票?
【解决方案3】:

我会提出一个实用宏。像这样的:

(defmacro update-when [check val-to-update f & params]
  `(if-let [x# ~check]
     (~f x# ~val-to-update ~@params)
     ~val-to-update))

user> (let [header-row :header
            data-rows [:data1 :data2]]
        (let [csv-data (update-when header-row data-rows cons)]
          csv-data))
;;=> (:header :data1 :data2)

user> (let [header-row nil
            data-rows [:data1 :data2]]
        (let [csv-data (update-when header-row data-rows cons)]
          csv-data))
;;=> [:data1 :data2]

它非常通用,让您完成更复杂的任务,而不是简单的 consing。例如,如果检查为真,您想反转一些 coll,并连接另一个列表...

user> (let [header-row :header
            data-rows [:data1 :data2]]
        (let [csv-data (update-when header-row data-rows
                                    (fn [h d & params] (apply concat (reverse d) params))
                                    [1 2 3] ['a 'b 'c])]
          csv-data))

;;=> (:data2 :data1 1 2 3 a b c)

更新 正如@amalloy 所注意到的,这个宏应该是一个函数:

(defn update-when [check val-to-update f & params]
  (if check
     (apply f check val-to-update params)
     val-to-update))

【讨论】:

    【解决方案4】:

    使用cond-&gt;&gt;

    (let [csv_data (cond->> data_rows
                     header_row (cons header-row)]
       )
    

    它的工作方式类似于常规的-&gt;&gt; 宏,但在每个线程表单之前必须放置一个测试表达式,以确定是否使用线程表单。

    还有cond-&gt;。在此处阅读有关线程宏的更多信息:Official threading macros guide

    【讨论】:

    • 虽然我可以看到这是惯用的高级 Clojure,但我发现它有点难以阅读。语义相当丰富:“请注意,与它的亲戚不同,some-> 或 cond,cond-> 永远不会短路评估,即使测试评估为 false 或 nil”。此外,这里发布了一个涉及 cond-> 的答案,并且出现了错误。我将其归因于认知负荷。我理解宏的价值,但在这里我感觉很沉重。
    • @m33lky 短路不会影响您的问题。这只是意味着,如果您有多个测试/表单对,一个虚假测试不会影响其他对。这绝不是一个“重”宏或高级 Clojure。它读起来很简单:取data_rows,如果有header_row,则使用header_row。当然,您必须学习宏的语法才能阅读它。考虑 case、cond、defn 等宏。宏是要学习的语法。
    • 我的意思是说它是为高级 Clojure 程序员准备的。我个人从未使用过这个特定的宏。虽然学习这个新功能很容易,但我认为需要经验才能以与 if/cond 等更传统的构造相同的速度和信心“阅读”此类逻辑。你同意 cond->> 比 cond 重得多吗?这有点不公平,因为用例不同。
    • 不,它在其他语言/习语中比cond 更不常见。这就是为什么它一开始可能看起来令人反感的原因。阅读它不需要艰苦的经验,你只需要学习它。我写了三遍左右,比我读任何其他代码时都要多。
    猜你喜欢
    • 2021-10-25
    • 1970-01-01
    • 2021-05-05
    • 2012-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-11
    相关资源
    最近更新 更多