1

Consider

type alias Rec = { a: Int, b: Int, c:Int }
updateRec r aVal            = { r|a = aVal } 
updateRec2 r aVal bVal      = { r|a = aVal, b= bVal } 
updateRec3 r aVal bVal cVal = ...

How to generalize updateRec and updateRec2... into one function?

lifebalance
  • 1,846
  • 3
  • 25
  • 57

3 Answers3

2

You want to write a function that has a variable number of differently-typed parameters. That's common in dynamically typed languages (Javascript, Python, Ruby) but usually not allowed in typed languages. Elm doesn't allow it.

You can emulate a variable number of differently-typed parameterswith the Maybe type, understanding Nothing as "missing argument":

updateRec : Rec -> Maybe Int -> Maybe Int -> Maybe Int -> Rec
updateRec r a b c = 
  { r
  | a = a |> Maybe.withDefault r.a
  , b = b |> Maybe.withDefault r.b
  , c = c |> Maybe.withDefault r.c
  } 

If the record fields are all of the same type (here Int), you could accept a List Int instead:

updateRec : Rec -> List Int -> Rec 
updateRec r fields = 
  case fields of 
    [a] -> { r | a = a }
    [a,b] -> { r | a = a, b = b }
    [a,b,c] -> { r | a = a, b = b, c = c }
    _ -> r

I don't like this solution because you it'll fail silently if you accidentally supply a list with 0 or 4+ elements. If this function is helpful to you anyway, perhaps it would be better to use List Int instead of Rec in the first place.

Søren Debois
  • 5,598
  • 26
  • 48
  • Apparently, even if JS, you will need additional code as per this article - http://javascript.crockford.com/www_svendtofte_com/code/curried_javascript/ . Your first version will work for me ,thanks. – lifebalance May 05 '16 at 06:40
  • I'm getting an error in the eml-repl as follows: The 2nd argument to function `updateRec` is causing a mismatch. updateRec r 3 ^ Function `updateRec` is expecting the 2nd argument to be: `Maybe number` But it is: `number` – lifebalance May 05 '16 at 07:03
  • 1
    Sounds as if you're trying to do a call `updateRec r 3 ...` but you need to call it `updateRec r (Just 3)`. – Søren Debois May 05 '16 at 17:46
  • Got it! But then, `updateRec (Just 1) (Just 2) (Just 3)` ...it can get long winded...makes me want to agree with @Chad that this is probably not as intuitive as the record `update` syntax, but useful to know that it _is_ possible. – lifebalance May 06 '16 at 08:35
  • Agree. Your comment made me think of a different answer which I gave below. – Søren Debois May 07 '16 at 12:32
2

Here's a better way of doing the same thing:

updateA : Int -> Rec -> Rec
updateA x rec = { rec | a = x }

-- Similarly for b, c

Now you can do the following, supposing you already have a value rec : Rec you want to update:

myUpdatedRec : Rec
myUpdatedRec =
  rec 
  |> updateA 7
  |> updateB 19

You can now update an arbitrary number of fields by stringing together |> updateX ....

Søren Debois
  • 5,598
  • 26
  • 48
1

Elm's record update syntax seems to be exactly what you are looking for. You "pass in" the record that you want updated, r below, and you can return a record with whatever fields you want changed without having to specify every field:

ex1 = { r|a = 1 }
ex2 = { r|b = 2, c = 3 }

You don't need to create a set of additional functions for updating only certain fields at certain times, because Elm's record update syntax is that generalized function.

Chad Gilbert
  • 36,115
  • 4
  • 89
  • 97
  • Ok. Just for theoretical purposes, in the above code, `ex1` won't accept any parameters, right? Or, how would you invoke `ex1` and `ex2`? – lifebalance May 05 '16 at 12:48
  • 1
    Right. I would argue that you don't need to define any extra functions for general purpose updating. As for `ex1` and `ex2` above, those are for example purposes only and unnecessary depending on your situation. You _could_ do something like that inside a `let` statement to clean up your code. Otherwise, think of `{ r|a = 1 }` as a value unto itself that can be used wherever values are used. – Chad Gilbert May 05 '16 at 12:52