Method chaining

I like the way method calls can be chained:

// It’s not important what the function does,
// the only thing that matters is the method chaining
// and the types.
func decodeFrontMatter(_ source: String) -> [String: String] {
    return source
        .split(separator: "\n")
        .map(String.init)
        .compactMap(decodeKeyValuePair) // String -> (String, String)?
        // and so on
}

This is easy to read, piping the source value through a number of transforms. But in the last step (after compactMap) I get an array of tuples that I would like to turn into a dictionary using Dictionary.init(keysAndValues:uniquingKeysWith:). And that cannot be chained any more (?), so I have to introduce a local variable to store the temporary tuple array result and pass that to the dictionary initializer. Is there a way around it, something like this perhaps?

return source
    .split(separator: "\n")
    .map(String.init)
    .compactMap(decodeKeyValuePair)
    .apply(Dictionary.init(keysAndValues:uniquingKeysWith:))

Would that be a good idea at all?

PS. Ah, I see, the dictionary initializer would not type-check as such, as it needs a second argument. Let’s pretend I have some partially applied version of the initializer that only needs the tuple list, then.

You can define:

infix operator |>: AdditionPrecedence // probably better to define a new precedence group
func |><A, B> (lhs: A, rhs: (A) -> B) -> B {
    return rhs(lhs)
}

(like you have in some other languages, e.g. Elixir, F#, ...)

Then you could write:

func decodeFrontMatter(_ source: String) -> [String: String] {
    return source
        .split(separator: "\n")
        .map(String.init)
        .compactMap(decodeKeyValuePair)
        |> { Dictionary.init(uniqueKeysWithValues: $0) }
}

I think that might be about the best thing you can get without some fundamental redesign of Swift.

4 Likes

This is what I always do. Also note that in the last step, you don't have to explicitly make a closure to wrap the initialiser, but you can use a function reference as an implicit closure:

func decodeFrontMatter(_ source: String) -> [String: String] {
    return source
        .split(separator: "\n")
        .map(String.init)
        .compactMap(decodeKeyValuePair)
        |> Dictionary.init(uniqueKeysWithValues:)
}
3 Likes

Ah, I didn't know this worked with keyword args. You're probably still limited to one-argument functions though, I assume, since Swift doesn't curry by default?

Yeah, I already had the operator defined and was toying with it, but the slight “impedance mismatch” between the functional and method approach bugged me a bit. (When I see |>, I tend to expect function composition using >> instead of method chaining.)

Right. There are libraries (such as Overture) that supply the usual curry and flip, but as I wrote above, I’m still on the fence about the right amount of functional programming in Swift.

Works if your source is a tuple that matches up with the function signature, for example:

func Add(a: Int, b: Int) -> Int {
  return a + b;
}
let r = [(1, 2), (2, 3)].compactMap(Add(a:b:)) // [3, 5]
3 Likes
Terms of Service

Privacy Policy

Cookie Policy