Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration closures keypath #3

Open
siideffect opened this issue Apr 7, 2020 · 8 comments
Open

Configuration closures keypath #3

siideffect opened this issue Apr 7, 2020 · 8 comments

Comments

@siideffect
Copy link

Hello Vincent, this repo is so useful if you are interested about keypaths.
I have been digging into the argument for the past weeks, but i still cant master the topic as i would like to.
Supposing you have a function to customize an object by using a closure:

func configure<T: UIView>(block: (T)->())-> T{
   let obj = T()
   block(obj)
   return obj
}

using it, will result in something like:

let label: UILabel = configure {
      $0.text = "Some"
      $0.backgroundColor = .white
   }

Although useful, i was wondering if keypaths could help making the syntax more concise.
Would be possible to achieve something like this?

let label: UILabel = configure {
      \.text = "Some"
      \.backgroundColor = .white
}

#07 Implementing the builder pattern with keypaths
This section is absolutely interesting, and is very close to my concept of clean and concise syntax.

@vincent-pradeilles
Copy link
Owner

Hi, yes you could achieve this with custom operators and function builders.

Here is the code for the function builder part :

(From https://twitter.com/v_pradeilles/status/1138555570980626432/)

D8z2P-hWwAEH7xn

Then, you just need to define a custom operator that returns a Configuration, with a key path as left hand side and the value as the right hand side, and that should do the trick 🙂

@siideffect
Copy link
Author

Complex but beautiful!
Although i did not get the 100% of the above code, your hint about defining a custom operator will be a good start.
Thank you so much for the provided code.

@astrokin
Copy link

astrokin commented Apr 9, 2020

2020-04-09 20 51 40
do the sam but much clear and require less code

@siideffect
Copy link
Author

siideffect commented Apr 9, 2020

Thank you @astrokin i have already used the example provided in
#07 Implementing the builder pattern with keypaths
And it looks really similar to the solution you proposed.
Although your reply may be close to what i m trying to achieve, i m trying to achieve this specific syntax inside the closure

//let label = Configurator(UILabel){ this loook good
let label: UILabel = configure { 
      \.text = "Some" // this is the desired result
      \.backgroundColor = .white
}

@astrokin
Copy link

astrokin commented Apr 9, 2020

you can achieve it this way too https://gist.github.com/nicklockwood/9b4aac87e7f88c80e932ba3c843252df

@siideffect
Copy link
Author

My actual implementation is almost identical to with function @astrokin
Its good, but far from what i want: i am looking for something different, just to push my skills using keypaths, and in terms of readability, by my personal point of view.
Thanks for your help!

@siideffect
Copy link
Author

@vincent-pradeilles i have been usign the code you provided to achieve, for first, the DSL like syntax in order to be able to write

let aVar = UILabel{
     set(\.backgroundColor, to: .white)
     set(\.text, to: "Some")
     //and so on
}

However, this doesnt compile. It does until the instruction is one, but not with multi line assignments, complaining is not possible to infer the T type. I have checked the syntax hundreds of time, and i understood most of the code which is being used.
Using the convenience init, you initialize the UILabel with a builder closure.
This closure, is expected to return a single Configuration object: using the with function concatenation, however, is like writing additional instructions not needed by compiler, as it works as expected with a single line assignment.

As workaround to make compiler happy, i ve been using the following syntax:

let label = UILabel{
   set(\.backgroundColor, to: .yellow)
   .set(\.text, to: "Some")
   .set(\.frame, to: CGRect(x: 100, y: 0, width: 100, height: 200))
   .set(\.textAlignment, to: .center)
   //2 set functions, 1 declared inside struct Configuration<T: AnyObject>
   // which uses the combined function. The problem? DRY 😖
}
///OR
let label = UILabel {
   ConfigurationBuilder.buildBlock(
      set(\.backgroundColor, to: .white),
      set(\.text, to: "Some")
   )
}

but as you can see, it required explicit call to buildBlock function and comma separation between instructions. Im pretty sure this is the purpose of @_functionBuilder, which is identical to your code:

@_functionBuilder
struct ConfigurationBuilder{
   static func buildBlock<T>(_ children: Configuration<T>...) -> Configuration<T>{
      children.reduce(.empty, {$0.combined(with: $1)})
   }
}

extension UILabel{
   convenience init(@ConfigurationBuilder  builder: ()-> Configuration<UILabel>){
      self.init()
      builder().configurator(self)
   }
}

What is wrong?

@vincent-pradeilles
Copy link
Owner

That's indeed very weird! The code I gave you was from the Xcode 11 beta, and now it seems that on current versions it's not working anymore.

For the moment I don't have an explanation as to why it no longer works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants