问题
Here I have several functions that all just set a single field on a model record. In a more dynamic language, I'd just have a single setter function and pass it the name of the field (as a string) and the value that I want to set on the model object.
Is there a way to pass the name of the field in Elm? What's the Elm way of doing something like this?
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 ] []
...
I'm currently using each of these functions as an event handler for input elements. So I need a function that I can use for handling the input event. Ideally, I'd define just a single function and use that single one for all the input elements and pass it the field I want to update on the patient record.
回答1:
The short answer is "no". But this seems a bit like an XY problem. It's not clear what benefit you are trying to achieve since the full application of such a function would be longer than the equivalent record update expression:
setField "name" patient value
-- vs
{ patient | name = value }
and as a partially applied function is only slightly shorter than the equivalent anonymous function with shortened argument names:
setField "name"
-- vs
\r x -> { r | name = x }
Although the latter is significantly noisier with all the symbols.
There is also a short-hand function for getting a record field:
.name
-- vs
\r -> r.name
So there is some precedent for having a dedicated syntax for setter functions too, but unfortunately there is not. Likely because it would complicate the language, and the syntax in particular, for relatively little benefit. I'm therefore curious about what you're actually trying to accomplish.
Edit after question update:
Putting functions in the Msg
is a very bad idea because it goes against the Elm Architecture. It makes the state transition opaque and won't work very well with the debugger. When something goes wrong you can still see the state before and after, but you'll have trouble understanding what happened, and why it happened, because that information is encoded in an opaque function which probably isn't the one it should be.
You'll also have trouble factoring your logic. If you need something to happen only when a certain field updates, you might have to put the logic in the view, or special-case that field by putting the logic for that in update
while the rest is in view
, for example. Either way, you're on the path to a messy code base.
You should generally use names for messages that describe what happened, not what to do, because that tends to lead to an imperative mindset. Instead of UpdateCell
you could call it InputChanged
, for example. Then instead of the function you should have an identifier for the field. Ideally a custom type, like InputChanged Name
, but even a string will work, though it will be much easier to miss a typo.
So instead of setter functions for each field you'll just case match the message and set the field in the update
function:
InputChanged Name value ->
{ patient | name = value }
-- vs
setPatientName : Patient -> String -> Patient
setPatientName patient value =
{ patient | name = value }
Then if you need to clear the sex when the name changes, for example (because reasons...), you can simply do:
InputChanged Name value ->
{ patient | name = value, sex = "" }
The Elm Architecture is good because it makes changes easy and safe, not because it's concise and free of boiler-plate. Good Elm code often has a lot of copy-and-paste, but that's not always bad.
来源:https://stackoverflow.com/questions/59005617/is-there-a-way-to-pass-the-name-of-a-field-to-a-setter-function