Is it possible to write a generic apply(block:) method? (like ruby yield_self)

When chaining a bunch of methods, it's sometimes great to use a method that is like map - but for a single item
equivalent to Ruby's yield_self

writing this explicitly for String

extension String {
    func apply(_ block:(String) -> (String)) -> String {
        return block(self)
    }
}

so..

let doTheLowercase = false
let result = "MiXedCase  ".apply{doTheLowercase ? $0.lowercased() : $0}.trimmingCharacters(in: .whitespaces)

two questions:

  • is there a natural way to do this already?
  • if not, is it possible to write the apply function generically

You can't really extend Any, but I often define this function:

infix operator |>   { precedence 50 associativity left }
public func |> <T,U>(lhs: T, rhs: T -> U) -> U {
    return rhs(lhs)
}

This allows me to terminate a chain like so:

let result = array
  .filter { $0.isValid }
  .map(transform)
  .joined()
  |> someFunction

It would perhaps be better with

   // ...
   .joined()
   .apply(someFunction)

But at least you don't have to wrap the entire chain in a function call, and have your eyes alternate between parsing left-right and right-left when reading the code.

1 Like

that's a nice idea - I hadn't thought of using an operator

it still is somewhat limiting

  1. can only terminate rather than chain
  2. requires an operator rather than a word (harder to follow)

I guess Swift can only go so far in giving the best of Ruby :)

You can still chain it:

let isValid = input |> transform |> validate

I can't get it to allow chaining with standard methods (calling with dot)

infix operator |>   : MultiplicationPrecedence
public func |> <T,U>(lhs: T, rhs: (T) -> U) -> U {
    return rhs(lhs)
}

//Works
let result1 = "hi" |> { $0 + "There"} |> { $0 + "Bob"}

// Value of type '(String) -> String' has no member 'trimmingCharacters'
let result2 = "hi" |> { $0 + "There"}.trimmingCharacters(in: .whitespaces)

Is it possible to define the precedence so that it works the same as method calls?

You're attempting to calling trimmingCharacters(in:) on the closure. That doesn’t work. You’ll need to either convert the trimming method into a free function or to add parentheses around the first apply expression.

@sveinhal - that's my point.

chaining works when you use a method approach

//This Works
let result = "MiXedCase  ".apply{doTheLowercase ? $0.lowercased() : $0}.trimmingCharacters(in: .whitespaces)

it doesn't work when you use an operator approach

this seems to be an annoying limitation in Swift (in that there isn't a way to do this generically)

This works:

let result = "MiXedCase  "
    |> { doTheLowercase ? $0.lowercased() : $0 }
    |> { $0.trimmingCharacters(in: .whitespaces) }

Also, this works:

let result = ("MiXedCase  " 
    |> { doTheLowercase ? $0.lowercased() : $0 })
    .trimmingCharacters(in: .whitespaces)

I'm not sure why you would expect the dot-notation to somehow automatically apply to something other than the preceding expression. Sure, the fact that you cannot extend Any is indeed a limitation of Swift, but that you cannot arbitrarily add dots where ever you feel like without adding parentheses is obviously not. You cannot do that in any language, and I can't understand why you would want to?

Also, if you really want the .apply(_:) function to exist on all kinds of types, you can define it on a custom no-requirements protocol and add conformances to types as you need them. By making NSObject conform, you can at least have it all Apple SDK types:

protocol Applicable {}
extension Applicable {
    func apply<T>(_ function: (Self) -> T) -> T {
        return function(self)
    }
}

extension NSObject: Applicable {}
extension String: Applicable {}
// ... add more of these as needed

let doTheLowercase = true
let result = "MiXedCase  "
    .apply { doTheLowercase ? $0.lowercased() : $0 }
    .trimmingCharacters(in: .whitespaces)

I'm not sure why you would expect the dot-notation to somehow automatically apply to something other than the preceding expression

This is just a question of precedence. When you define new operators, you can set the precedence and the associativity - e.g. I can define how * , + and |> work together

so - there is no problem chaining the + operator

//Works
let result1 = "hi" |> { $0 + "There"} + " Bob"

//Doesn't work
let result1 = "Hi " |> { $0 + "There"} . lowercased()

the dot operator (in effect) has it's own precedence group, which is greater than allowed for custom operators
(note - under the hood, the dot operator may be implemented in a completely different way. In effect though, it works as a max-precedence left-associated operator)

it isn't obvious to me why it you couldn't have

infix operator |> : DotOperatorPrecedence

which would allow chaining (the dot operator acts as if it has left precedence)

Also, if you really want the .apply(_:) function to exist on all kinds of types, you can define it on a custom no-requirements protocol and add conformances to types as you need them

yup - that's probably the best available option

I'm mostly just making a practicality / aesthetic point now

extension NSObject: Applicable {}
extension String: Applicable {}
... very long list of classes and structs
... remember to add this to any new classes you define

is just uglier than

extension Any: Applicable {}

Agreed.

This is the source of your confusion: It's not an operator!

I do get that - though in many ways it functions just like an operator (it has precedence, associativity, etc).

This is a language-design choice, but I don't see why Swift couldn't allow

infix operator |> : DotOperatorPrecedence

I do get that it doesn't allow this, but it isn't clear why function calling has to be special in terms of how the expression is evaluated.

It is not.

sorry - not sure what you mean; It is not special, or it is not clear?