Add function application to Swift's standard library

I often find myself in need of the following:

let includedProductIdentifiers = puzzleLibrary.volumes
    .compactMap { volume in volume.productIdentifier }
    .apply(Set.init)

The apply function is implemented as follows:

extension Collection {
    func apply<T>(_ transform: (Self) -> T) -> T {
        return transform(self)
    }
}

Without apply, this code becomes much less readable.

Wouldn't it be a good idea to have this function in the standard library?

3 Likes

That reminds me that I wanted to do a thorough search for proposals to add an operator for function composition -- those seem to be much harder to find than I thought ;-)

Yes, I think this would be useful -- but probably not only for Collection...

1 Like

I've got an operator defined for this in my personal projects, along with an optional-propagating variant. They're very handy, but can be easy to overuse.

Reasons for this being in the standard library include:

  • A standard name for generally useful functionality.
  • Compile-time resolution to a plain function call. (@inlinable may handle this already
)

Reasons not to:

  • Many possible variants, some likely controversial: (how does one indent lists properly on Discourse?)
  • Input-returning, for functions with side-effects or dealing with reference-types (EG: view setup).
  • Taking a function with an inout parameter, returning the mutated copy of the input having called the function (EG: one liners for ‘give me this value but having changed a property’).
  • Mutating the input then returning the input, for symmetry with the reference-type/side-effect variant (proponents of ++ might like this one).
  • Optional-propagating versions of all variants.
  • No best spelling - the discussion will be entirely bikeshedding.

I'm all in favor of adding this, as an operator, as I think it helps a lot with readability in many cases, and think borrowing F#'s pipe forward |> is a good candidate for a spelling.

21 Likes

Yes, I've used a similar operator in Elixir.

def authenticate_user(conn, user) do
    conn
    |> Guardian.Plug.remember_me(user, %{}, key: "guardian_token")
    |> redirect(to: "/")
    |> halt()
end

I'm not sure if you'd be able to do something quite as nice in Swift without some special support for that though.

1 Like

I am surprised that the only thread bringing that up has almost no activity. I miss this feature, too. Of course, one can easily write |>, but where to put it? Boilerplate it into every project? Make an extra framework just for that and boilerplate an otherwise useless import everywhere? The option that I went with was putting it into a framework that provides a lot of compositional functionality, but most of the time I end up using only |>. This should really be in the standard library.

1 Like

Imho the operator would be a really good fit for Combine.

4 Likes

This would be great if we were to add first-class language support for reactive streams (which we should consider doing - many languages have added it to their standard libraries already, such as Java).

2 Likes

good to have this applicative but I think Swift should stick to functions with names not those fancy functional language symbolic operators

2 Likes

+1 for this. Kotlin has a function called apply except in Kotlin's case, it's more of a mutation on self that returns self.
reference: https://kotlinlang.org/docs/reference/scope-functions.html#apply

If apply is implemented in Swift like it is in Kotlin it would be great if the closure drops the need for $0 since its implied that self would be returned. $0 in this closure would I guess be inout. Otherwise, you can ignore this.

This proposal seems closer to Kotlin's let function. where it takes the transform function and returns the result of that. In this case, the syntax of closures as is would suffice.
reference: https://kotlinlang.org/docs/reference/scope-functions.html#let

2 Likes

I agree with this in general, but in this case it being a function is key to its usefulness, since you can’t put a function on the right-hand side of a result, and can’t put a method on every type.

|> is also a fairly familiar concept from the command line (albeit with an extra character to avoid confusion with other meanings of |.

4 Likes

I think |> would be a great addition to the Swift standard library: it's a fairly established operator for its purpose, doesn't need compiler magic (you can't write an extension that works on every type), and its very presence in the standard library would probably encourage usage of operators in general, which is actually a strength of Swift.

2 Likes

Is the disambiguation really necessary? I’m not particularly opposed to |>, but off the top my head it doesn’t seem likely that someone would confuse “function application” with “bitwise or”.

Also, is this strictly for function application, or can we overload it to give us common mechanism for passing a T? to a f(_: T) -> U and getting back a U? (I’m pretty sure there’s a word for this, I just can’t remember what it is)?

With compiler support we could make this available on all types. Implementing it as something that basically is a nameless method avoids conflicts with regular methods:

let includedProductIdentifiers = puzzleLibrary.volumes
    .compactMap { volume in volume.productIdentifier }
    .(Set.init)

or:

let includedProductIdentifiers = puzzleLibrary.volumes
    .compactMap { volume in volume.productIdentifier }
    .{ Set($0) }

It's nicely consistent to be able to continue chaining without requiring an operator for the last step. An operator might be more useful in different use cases though.

3 Likes

Nice imagination. This syntax also deals nicely with optional chaining.

2 Likes

I use |> for reverse function application and ?> for optional chaining in my project. Basically it looks like this:

func |><T, R>(arg: T, body: (T) throws -> R) rethrows -> R {
    return try body(arg)
}

func |><T, U, R>(arg: (T, U), body: (T, U) throws -> R) rethrows -> R {
    return try body(arg.0, arg.1)
}
...

func ?><T, R>(arg: T?, body: (T) throws -> R?) rethrows -> R? {
    guard let arg = arg else { return nil }
    return try body(arg)
}

func ?><T, U, R>(arg: (T?, U?), body: (T, U) throws -> R?) rethrows -> R? {
    guard case let (arg0?, arg1?) = arg else { return nil }
    return try body(arg0, arg1)
}
...
1 Like

Is the disambiguation really necessary? I’m not particularly opposed to |> , but off the top my head it doesn’t seem likely that someone would confuse “function application” with “bitwise or”.

I did write an or operator in the past to glue together functions of type (X) -> Bool. If X happens to be itself Bool, there will be some confusion. To clarify, I do not advocate putting an or operator for boolean functions into the standard library, but I guess I am not the only one occasionally using such constructs as syntactic sugar in filter/allSatisfy/... .

I didn't even think of that, but yes, since it's just a method (kind of) it should work fine:

let firstWord = "foo bar".lazy.split(separator: " ").first?.(String.init)

Does anyone see any glaring problems with this syntax? If not, I'm willing to pitch, propose and implement it.

5 Likes

I think the idea of writing something like value.(someFunction) and value.{ someFunction($0) } for function application looks great, and super Swifty :wink:

4 Likes

I think this idea of "inline method definition" is worth exploring, yes, in order to check if it fits well with the rest of Swift syntax and constructs, and yet allows the flexibility needed by the wildest developers.

For me the main advantage of .{ ... } over |> is the built-in support for optional chaining.

I also foresee people focus a lot on the parenthesis in .(Set.init). Those are not regular parenthesis, and be ready to think hard about their exact role in the grammar.

1 Like