zoul
(Tomáš Znamenáček)
1
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
sveinhal
(Svein Halvor Halvorsen)
3
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?
zoul
(Tomáš Znamenáček)
5
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.
endofunk
(endofunk)
6
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