有没有办法将字段名称传递给 setter 函数?

Is there a way to pass the name of a field to a setter function?

这里我有几个函数,它们都只是在模型记录上设置一个字段。 在更动态的语言中,我只有一个 setter 函数并将字段名称(作为字符串)和我想在模型对象上设置的值传递给它。

有没有办法在 Elm 中传递字段名称? Elm 做这样的事情的方式是什么?

type alias Patient =
  { id : String
  , name : String
  , dateOfBirth : String
  , sex : String
  ... other fields
  }

setPatientName : Patient -> String -> Patient
setPatientName patient value =
    { patient | name = value }

setPatientDateOfBirth : Patient -> String -> Patient
setPatientDateOfBirth patient value =
  { patient | dateOfBirth = value }

setPatientSex : Patient -> String -> Patient
setPatientSex patient value =
  { patient | sex = value }

... many others

-- idx is the index of the patient in the model (which is an array of patients)
-- UpdateCell is a variant of my Msg type, like this: UpdateCell Int (Patient -> String -> Patient) String
onInputHandler : Int -> (Patient -> String -> Patient) -> String -> Msg
onInputHandler idx setter inputText =
    UpdateCell idx setter inputText

-- idx is the index of the patient in the model (which is an array of patients)
createTableRow : Int -> Patient -> Html Msg
createTableRow idx patient =
    ...
    , input [ type_ "text", onInput (onInputHandler idx setPatientName), value patient.name ] []
    , input [ type_ "text", onInput (onInputHandler idx setPatientDateOfBirth), value patient.dateOfBirth ] []
    ...

我目前正在使用这些函数中的每一个作为输入元素的事件处理程序。所以我需要一个可用于处理输入事件的函数。理想情况下,我只定义一个函数并将该函数用于所有输入元素,并将它传递给我要在患者记录上更新的字段。

简短的回答是 "no"。不过这个好像有点像an XY problem。目前尚不清楚您试图获得什么好处,因为这样一个函数的完整应用将比等效的记录更新表达式更长:

setField "name" patient value

-- vs

{ patient | name = value }

并且作为部分应用函数仅比具有缩短参数名称的等效匿名函数略短:

setField "name"

-- vs

\r x -> { r | name = x }

尽管后者 对所有符号来说明显更嘈杂。

还有一个获取记录字段的简写函数:

.name

-- vs

\r -> r.name

因此也有一些为 setter 函数使用专用语法的先例,但不幸的是没有。可能是因为它会使语言,尤其是语法复杂化,而收益相对较小。因此,我很好奇你实际上想要完成什么。

问题更新后编辑:

将函数放在 Msg 中是一个非常糟糕的主意,因为它违背了 Elm 架构。它使状态转换不透明并且不能很好地与调试器一起工作。当出现问题时你仍然可以看到之前和之后的状态,但是你将很难理解发生了什么,以及为什么它发生了,因为该信息被编码在一个不透明的函数中,这可能不是它应该的。

您在分解逻辑时也会遇到麻烦。如果您只需要在某个字段更新时发生某些事情,您可能必须将逻辑放在视图中,或者通过将逻辑放在 update 而其余部分放在 view,例如。无论哪种方式,您都在走向混乱的代码库。

您通常应该为描述发生的事情而不是做什么的消息使用名称,因为这往往会导致命令式思维。例如,您可以将其称为 InputChanged 而不是 UpdateCell。然后你应该有一个字段的标识符而不是函数。理想情况下是自定义类型,如 InputChanged Name,但即使是字符串也可以,但更容易错过拼写错误。

因此,您只需对消息进行大小写匹配并在 update 函数中设置字段,而不是每个字段的 setter 函数:

InputChanged Name value ->
    { patient | name = value }

-- vs

setPatientName : Patient -> String -> Patient
setPatientName patient value =
    { patient | name = value }

那么如果你需要在改名时清除性别,比如(因为...的原因),你可以简单地做:

InputChanged Name value ->
    { patient | name = value, sex = "" }

Elm 架构很好,因为它使更改变得简单和安全,而不是因为它简洁且没有样板。好的 Elm 代码通常有很多复制粘贴,但这并不总是坏事。