有没有办法将字段名称传递给 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 代码通常有很多复制粘贴,但这并不总是坏事。
这里我有几个函数,它们都只是在模型记录上设置一个字段。 在更动态的语言中,我只有一个 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 代码通常有很多复制粘贴,但这并不总是坏事。