Request for Discussion: Setup closures


(Erica Sadun) #1

PROBLEM: With many Apple-supplied classes, typical initializers fail to fully set up an instance for use. Here's one example:

let task = NSTask()
task.launchPath = "/usr/bin/mdfind"
task.arguments = ["kMDItemDisplayName == *.playground"]
task.standardOutput = pipe

Here's another:

let questionLabel = UILabel()
questionLabel.textAlignment = .Center
questionLabel.font = UIFont(name:"DnealianManuscript", size: 72)
questionLabel.text = currentQuestion.questionText
questionLabel.numberOfLines = 0

You end up with stodgy repetitive code that turns into a blocky hard-to-follow clump. Here are some of my complaints:
This code feels unnecessarily redundant
This code visually stacks. The task/task/task and questionLabel/questionLabel/questionLabel blocks draw attention away from the actual set-up these lines of code are intended to do.
The extra symbol verbiage goes against common Swift style. For example, when the context is clear, prefer .whitespaceAndNewlineCharacterSet to NSCharacterSet.whitespaceAndNewlineCharacterSet.
Further, if you have many instances to set up there's no clear way to differentiate unrelated set-up groups other than inserting whitespace gaps or building custom factory functions.

PROPOSED SOLUTION: What do you think about creating setup closures that modify initializers and automatically introduce self-references. For example the NSTask() initialization might look something like this instead:

let task = NSTask()>>{
    launchPath = "/usr/bin/mdfind"
    arguments = ["kMDItemDisplayName == *.playground"]
    standardOutput = pipe
}

In this example, the braces are scoped to the instance as self, enabling the properties to entirely drop their prefixes and be grouped together for set-up.

ADVANTAGES AND DISADVANTAGES:

The advantages I see are as follows:
additional setup becomes integrated into initialization
code is streamlined, a general Swift ideal
the indented scope provides a visual emphasis of the single task being addressed
allows easier top-level initialization for global values, especially for Swift playgrounds.

The disadvantages I imagine would include:
the need for a new operator to disambiguate trailing closures from setup
compiler tweaking so `self`-references within the closure work without constant copies for value types

Thoughts?


(Joe Groff) #2

In Smalltalk and Dart, you can do this with method cascades, which apply multiple methods to the same 'self'. In Dart they use '..' for this:

let task = NSTask()
  ..launchPath = "..."
  ..arguments = [...]
  ..standardOutput = pipe

The nice thing about that is that it's a bit more generally applicable than just initialization.

-Joe

···

On Dec 4, 2015, at 1:24 PM, Erica Sadun <erica@ericasadun.com> wrote:

PROBLEM: With many Apple-supplied classes, typical initializers fail to fully set up an instance for use. Here's one example:

let task = NSTask()
task.launchPath = "/usr/bin/mdfind"
task.arguments = ["kMDItemDisplayName == *.playground"]
task.standardOutput = pipe

Here's another:

let questionLabel = UILabel()
questionLabel.textAlignment = .Center
questionLabel.font = UIFont(name:"DnealianManuscript", size: 72)
questionLabel.text = currentQuestion.questionText
questionLabel.numberOfLines = 0

You end up with stodgy repetitive code that turns into a blocky hard-to-follow clump. Here are some of my complaints:
This code feels unnecessarily redundant
This code visually stacks. The task/task/task and questionLabel/questionLabel/questionLabel blocks draw attention away from the actual set-up these lines of code are intended to do.
The extra symbol verbiage goes against common Swift style. For example, when the context is clear, prefer .whitespaceAndNewlineCharacterSet to NSCharacterSet.whitespaceAndNewlineCharacterSet.
Further, if you have many instances to set up there's no clear way to differentiate unrelated set-up groups other than inserting whitespace gaps or building custom factory functions.

PROPOSED SOLUTION: What do you think about creating setup closures that modify initializers and automatically introduce self-references. For example the NSTask() initialization might look something like this instead:

let task = NSTask()>>{
    launchPath = "/usr/bin/mdfind"
    arguments = ["kMDItemDisplayName == *.playground"]
    standardOutput = pipe
}

In this example, the braces are scoped to the instance as self, enabling the properties to entirely drop their prefixes and be grouped together for set-up.


(Sean Heber) #3

I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:

with let task = NSTask() {
    launchPath = "/usr/bin/mdfind"
    arguments = ["kMDItemDisplayName == *.playground"]
    standardOutput = pipe
}

Also valid:

with someVariable {
    func1()
    func2(“etc")
}

Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).

l8r
Sean

···

On Dec 4, 2015, at 3:27 PM, Joe Groff <jgroff@apple.com> wrote:

On Dec 4, 2015, at 1:24 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

PROBLEM: With many Apple-supplied classes, typical initializers fail to fully set up an instance for use. Here's one example:

let task = NSTask()
task.launchPath = "/usr/bin/mdfind"
task.arguments = ["kMDItemDisplayName == *.playground"]
task.standardOutput = pipe

Here's another:

let questionLabel = UILabel()
questionLabel.textAlignment = .Center
questionLabel.font = UIFont(name:"DnealianManuscript", size: 72)
questionLabel.text = currentQuestion.questionText
questionLabel.numberOfLines = 0

You end up with stodgy repetitive code that turns into a blocky hard-to-follow clump. Here are some of my complaints:
This code feels unnecessarily redundant
This code visually stacks. The task/task/task and questionLabel/questionLabel/questionLabel blocks draw attention away from the actual set-up these lines of code are intended to do.
The extra symbol verbiage goes against common Swift style. For example, when the context is clear, prefer .whitespaceAndNewlineCharacterSet to NSCharacterSet.whitespaceAndNewlineCharacterSet.
Further, if you have many instances to set up there's no clear way to differentiate unrelated set-up groups other than inserting whitespace gaps or building custom factory functions.

PROPOSED SOLUTION: What do you think about creating setup closures that modify initializers and automatically introduce self-references. For example the NSTask() initialization might look something like this instead:

let task = NSTask()>>{
    launchPath = "/usr/bin/mdfind"
    arguments = ["kMDItemDisplayName == *.playground"]
    standardOutput = pipe
}

In this example, the braces are scoped to the instance as self, enabling the properties to entirely drop their prefixes and be grouped together for set-up.

In Smalltalk and Dart, you can do this with method cascades, which apply multiple methods to the same 'self'. In Dart they use '..' for this:

let task = NSTask()
  ..launchPath = "..."
  ..arguments = [...]
  ..standardOutput = pipe

The nice thing about that is that it's a bit more generally applicable than just initialization.

-Joe


(Matthew Johnson) #4

let task = NSTask()>>{

    launchPath = "/usr/bin/mdfind"
    arguments = ["kMDItemDisplayName == *.playground"]
    standardOutput = pipe
}

I think something like this would be extremely useful, however I think the proposal as it is written is a bit more limited than I would like to see as it only applies to mutable properties.

I don't have a concrete idea of exactly what I would prefer, but it would take the form of a general solution that would allow an initializer to accept initial values for some or all public stored properties from the caller. The simplest case might look a lot like the compiler generated memberwise initializer for structs. Slightly more complex cases might allow the caller to omit some properties if a default is specified by the type or by the initializer. Going a step further, some initializers for a type may need to initialize some of the stored priorities directly and thus not allow those specific properties to be specified by the caller.

The goal is to allow flexible initialization for callers without sacrificing immutability and without requiring a ton of initializer overloads.

If others like this basic idea I will try to make it more concrete and write a proposal.

Matthew


(Lily Ballard) #5

I'd be in favor of a solution like the '..' operator. Using a block-
based syntax for this seems like it's already close enough to defining
something like

func with<T>(x: T, @noescape _ f: inout T -> Void) -> T { var value =
x f(&value) return value }

that it would be better to just go with the function instead of
complicating the Swift syntax. But the '..' operator has some nice
properties, such as being able to say something like

someLayoutConstraint =
label.leftAnchor.constraintEqualToAnchor(view.leftAnchor)..active = true

(I'm assuming here that '..' works by returning the receiver as the
value of the subsequent expression, rather than merely allowing several
'..' operators to be chained together, but I'm not actually familiar
with how this works in Smalltalk and Dart)

-Kevin Ballard

···

On Fri, Dec 4, 2015, at 01:27 PM, Joe Groff wrote:

In Smalltalk and Dart, you can do this with method cascades, which
apply multiple methods to the same 'self'. In Dart they use '..'
for this:

let task = NSTask() ..launchPath = "..." ..arguments = [...]
..standardOutput = pipe

The nice thing about that is that it's a bit more generally applicable
than just initialization.

-Joe

_________________________________________________
swift-evolution mailing list swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #6

I really like both approaches, although I prefer the elegance of Sean's.

-- E

···

On Dec 4, 2015, at 2:35 PM, Sean Heber <sean@fifthace.com> wrote:

On Dec 4, 2015, at 3:27 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Dec 4, 2015, at 1:24 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

PROBLEM: With many Apple-supplied classes, typical initializers fail to fully set up an instance for use. Here's one example:

let task = NSTask()
task.launchPath = "/usr/bin/mdfind"
task.arguments = ["kMDItemDisplayName == *.playground"]
task.standardOutput = pipe

Here's another:

let questionLabel = UILabel()
questionLabel.textAlignment = .Center
questionLabel.font = UIFont(name:"DnealianManuscript", size: 72)
questionLabel.text = currentQuestion.questionText
questionLabel.numberOfLines = 0

You end up with stodgy repetitive code that turns into a blocky hard-to-follow clump. Here are some of my complaints:
This code feels unnecessarily redundant
This code visually stacks. The task/task/task and questionLabel/questionLabel/questionLabel blocks draw attention away from the actual set-up these lines of code are intended to do.
The extra symbol verbiage goes against common Swift style. For example, when the context is clear, prefer .whitespaceAndNewlineCharacterSet to NSCharacterSet.whitespaceAndNewlineCharacterSet.
Further, if you have many instances to set up there's no clear way to differentiate unrelated set-up groups other than inserting whitespace gaps or building custom factory functions.

PROPOSED SOLUTION: What do you think about creating setup closures that modify initializers and automatically introduce self-references. For example the NSTask() initialization might look something like this instead:

let task = NSTask()>>{
   launchPath = "/usr/bin/mdfind"
   arguments = ["kMDItemDisplayName == *.playground"]
   standardOutput = pipe
}

In this example, the braces are scoped to the instance as self, enabling the properties to entirely drop their prefixes and be grouped together for set-up.

In Smalltalk and Dart, you can do this with method cascades, which apply multiple methods to the same 'self'. In Dart they use '..' for this:

let task = NSTask()
..launchPath = "..."
..arguments = [...]
..standardOutput = pipe

The nice thing about that is that it's a bit more generally applicable than just initialization.

-Joe

I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:

with let task = NSTask() {
   launchPath = "/usr/bin/mdfind"
   arguments = ["kMDItemDisplayName == *.playground"]
   standardOutput = pipe
}

Also valid:

with someVariable {
   func1()
   func2(“etc")
}

Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).

l8r
Sean


(Matthew Johnson) #7

I would also be in favor of something more generally applicable rather than making initialization more special.

Initialization is inherently special - it is the only opportunity to provide an value for immutable stored properties. IMO that means an adequate solution *must* at least consider initialization in depth and must be able to facilitate initializing immutable stored properties (which method cascade would not be able to do).

Matthew


#8

Agreed, the
with variable {
statements
}
Format seems quite elegant and descriptive

Thanks for your time,
Cole Kurkowski

···

On Dec 4, 2015, 15:43 -0600, Erica Sadun<erica@ericasadun.com>, wrote:

I really like both approaches, although I prefer the elegance of Sean's.

-- E

> On Dec 4, 2015, at 2:35 PM, Sean Heber<sean@fifthace.com(mailto:sean@fifthace.com)>wrote:
> >
> > On Dec 4, 2015, at 3:27 PM, Joe Groff<jgroff@apple.com(mailto:jgroff@apple.com)>wrote:
> >
> >
> > > On Dec 4, 2015, at 1:24 PM, Erica Sadun<erica@ericasadun.com(mailto:erica@ericasadun.com)<mailto:erica@ericasadun.com>>wrote:
> > >
> > > PROBLEM: With many Apple-supplied classes, typical initializers fail to fully set up an instance for use.Here's one example:
> > >
> > > let task = NSTask()
> > > task.launchPath = "/usr/bin/mdfind"
> > > task.arguments = ["kMDItemDisplayName == *.playground"]
> > > task.standardOutput = pipe
> > >
> > > Here's another:
> > >
> > > let questionLabel = UILabel()
> > > questionLabel.textAlignment = .Center
> > > questionLabel.font =UIFont(name:"DnealianManuscript", size: 72)
> > > questionLabel.text = currentQuestion.questionText
> > > questionLabel.numberOfLines = 0
> > >
> > > You end up with stodgy repetitive code that turns into a blocky hard-to-follow clump. Here are some of my complaints:
> > > This code feels unnecessarily redundant
> > > This code visually stacks. The task/task/task and questionLabel/questionLabel/questionLabel blocks draw attention away from the actual set-up these lines of code are intended to do.
> > > The extra symbol verbiage goes against common Swift style. For example, when the context is clear, prefer .whitespaceAndNewlineCharacterSet to NSCharacterSet.whitespaceAndNewlineCharacterSet.
> > > Further, if you have many instances to set up there's no clear way to differentiate unrelated set-up groups other than inserting whitespace gaps or building custom factory functions.
> > >
> > > PROPOSED SOLUTION: What do you think about creating setup closuresthat modify initializers and automatically introduce self-references. For example the NSTask() initialization might look something like this instead:
> > >
> > > let task = NSTask()>>{
> > > launchPath = "/usr/bin/mdfind"
> > > arguments = ["kMDItemDisplayName == *.playground"]
> > > standardOutput = pipe
> > > }
> > >
> > > In this example, the braces are scoped to the instance as self, enabling the properties to entirely drop their prefixes and be grouped together for set-up.
> >
> > In Smalltalk and Dart, you can do this with method cascades, which apply multiple methods to the same 'self'. In Dart they use '..' for this:
> >
> > let task = NSTask()
> > ..launchPath = "..."
> > ..arguments = [...]
> > ..standardOutput = pipe
> >
> > The nice thing about that is that it's a bit more generally applicable than just initialization.
> >
> > -Joe
>
> I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:
>
> with let task = NSTask() {
> launchPath = "/usr/bin/mdfind"
> arguments = ["kMDItemDisplayName == *.playground"]
> standardOutput = pipe
> }
>
> Also valid:
>
> with someVariable {
> func1()
> func2(“etc")
> }
>
> Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).
>
> l8r
> Sean
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Gaelan Bright Steele) #9

JavaScript had a `with` operator for a while, but it was deprecated due to some edge cases, specifically something like this:

    // Codebase 1 (a library)
    class A {}
    internal func foo() {
        print("correct result")
    }
    func doSomethingWithA(a: A) {
        with a {
            /* do something with A */
            foo()
            /* do something else with A */
        }
    }
    // Codebase 2 (Application code depending on CB1)
    class B: A {
        // presumably written without knowledge of the global foo
        func foo() {
            print("completely different result”)
        }
    }
    doSomethingWithA(B()) //What happens here? Does the foo() call the global foo or B’s foo?

I think that in a static programming language we could avoid this problem by having the `with` operator use the knowledge of the type it is passed at compile-time (ignoring the runtime type). Still, we need to be careful about edge cases here.

···

On Dec 4, 2015, at 1:35 PM, Sean Heber <sean@fifthace.com> wrote:

On Dec 4, 2015, at 3:27 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Dec 4, 2015, at 1:24 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

PROBLEM: With many Apple-supplied classes, typical initializers fail to fully set up an instance for use. Here's one example:

let task = NSTask()
task.launchPath = "/usr/bin/mdfind"
task.arguments = ["kMDItemDisplayName == *.playground"]
task.standardOutput = pipe

Here's another:

let questionLabel = UILabel()
questionLabel.textAlignment = .Center
questionLabel.font = UIFont(name:"DnealianManuscript", size: 72)
questionLabel.text = currentQuestion.questionText
questionLabel.numberOfLines = 0

You end up with stodgy repetitive code that turns into a blocky hard-to-follow clump. Here are some of my complaints:
This code feels unnecessarily redundant
This code visually stacks. The task/task/task and questionLabel/questionLabel/questionLabel blocks draw attention away from the actual set-up these lines of code are intended to do.
The extra symbol verbiage goes against common Swift style. For example, when the context is clear, prefer .whitespaceAndNewlineCharacterSet to NSCharacterSet.whitespaceAndNewlineCharacterSet.
Further, if you have many instances to set up there's no clear way to differentiate unrelated set-up groups other than inserting whitespace gaps or building custom factory functions.

PROPOSED SOLUTION: What do you think about creating setup closures that modify initializers and automatically introduce self-references. For example the NSTask() initialization might look something like this instead:

let task = NSTask()>>{
   launchPath = "/usr/bin/mdfind"
   arguments = ["kMDItemDisplayName == *.playground"]
   standardOutput = pipe
}

In this example, the braces are scoped to the instance as self, enabling the properties to entirely drop their prefixes and be grouped together for set-up.

In Smalltalk and Dart, you can do this with method cascades, which apply multiple methods to the same 'self'. In Dart they use '..' for this:

let task = NSTask()
..launchPath = "..."
..arguments = [...]
..standardOutput = pipe

The nice thing about that is that it's a bit more generally applicable than just initialization.

-Joe

I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:

with let task = NSTask() {
   launchPath = "/usr/bin/mdfind"
   arguments = ["kMDItemDisplayName == *.playground"]
   standardOutput = pipe
}

Also valid:

with someVariable {
   func1()
   func2(“etc")
}

Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).

l8r
Sean

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Michel Fortin) #10

The problem with this approach is that it can become ambiguous with the outer scope pretty easily, if not to the compiler at least for the reader:

  let launchPath = "a.out"
  let description = "blah blah"
  with let task = NSTask() {
    launchPath = launchPath // eh, what?
    arguments = [launchPath, description] // is that task.description?
  }

I think it's important that things referring to the "task" in the above example be syntactically distinguishable.

It can also lead to code breakage:

  let path = "a.out"
  with let task = NSTask() {
    launchPath = path
  }

That would work fine one day, but what if in the next OS release NSTask gets a new "path" property? That code breaks silently once you recompile with the newer SDK.

···

Le 4 déc. 2015 à 16:35, Sean Heber <sean@fifthace.com> a écrit :

I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:

with let task = NSTask() {
  launchPath = "/usr/bin/mdfind"
  arguments = ["kMDItemDisplayName == *.playground"]
  standardOutput = pipe
}

Also valid:

with someVariable {
  func1()
  func2(“etc")
}

Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).

--
Michel Fortin
michel.fortin@michelf.ca
https://michelf.ca


(Erica Sadun) #11

I suspect this is an easy compiler catch requiring a forced self for disambiguation the same way as in init

-- E

···

On Dec 4, 2015, at 2:51 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 4 déc. 2015 à 16:35, Sean Heber <sean@fifthace.com> a écrit :

I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:

with let task = NSTask() {
launchPath = "/usr/bin/mdfind"
arguments = ["kMDItemDisplayName == *.playground"]
standardOutput = pipe
}

Also valid:

with someVariable {
func1()
func2(“etc")
}

Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).

The problem with this approach is that it can become ambiguous with the outer scope pretty easily, if not to the compiler at least for the reader:

  let launchPath = "a.out"
  let description = "blah blah"
  with let task = NSTask() {
    launchPath = launchPath // eh, what?
    arguments = [launchPath, description] // is that task.description?
  }

I think it's important that things referring to the "task" in the above example be syntactically distinguishable.

It can also lead to code breakage:

  let path = "a.out"
  with let task = NSTask() {
    launchPath = path
  }

That would work fine one day, but what if in the next OS release NSTask gets a new "path" property? That code breaks silently once you recompile with the newer SDK.
  
--
Michel Fortin
michel.fortin@michelf.ca
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Waite) #12

This seems related to my proposal to be able to bind self to a closure parameter.

Instead of:

  with let task = NSTask() {…}

you could just make ‘with' a standard function:

  func with<T>(argument:T, apply:(T) throws->()) rethrows -> T {
     try apply(argument)
    return argument
  }

  let task = with(NSTask()) {
    $0.launchPath = …
  }

Then, with my proposal:

  let task = with(NSTask()) {
    self in
    launchPath = …
    arguments = …
    standardOutput = ...
  }

-DW

···

On Dec 4, 2015, at 2:51 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 4 déc. 2015 à 16:35, Sean Heber <sean@fifthace.com> a écrit :

I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:

with let task = NSTask() {
launchPath = "/usr/bin/mdfind"
arguments = ["kMDItemDisplayName == *.playground"]
standardOutput = pipe
}

Also valid:

with someVariable {
func1()
func2(“etc")
}

Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).

The problem with this approach is that it can become ambiguous with the outer scope pretty easily, if not to the compiler at least for the reader:

  let launchPath = "a.out"
  let description = "blah blah"
  with let task = NSTask() {
    launchPath = launchPath // eh, what?
    arguments = [launchPath, description] // is that task.description?
  }

I think it's important that things referring to the "task" in the above example be syntactically distinguishable.

It can also lead to code breakage:

  let path = "a.out"
  with let task = NSTask() {
    launchPath = path
  }

That would work fine one day, but what if in the next OS release NSTask gets a new "path" property? That code breaks silently once you recompile with the newer SDK.
  
--
Michel Fortin
michel.fortin@michelf.ca
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Sean Heber) #13

Yeah, that thought occurred to me as I was writing it out. One possibility would be to limit what can go inside that block - perhaps all statements must be functions called on the value you are using in the with block? Or perhaps simply prefix them with a period? (Maybe looks too much like enum?)

with let task = NSTask() {
.launchPath = path
.arguments = []
.standardOutput = pipe
}

etc.

l8r
Sean

···

On Dec 4, 2015, at 3:51 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 4 déc. 2015 à 16:35, Sean Heber <sean@fifthace.com> a écrit :

I would also be in favor of something more generally applicable rather than making initialization more special. I think I prefer the block-style over Dart’s approach. Smalltalk uses semicolons for this, if I recall. I’m not sure I like using a special operator. I’m obviously a keyword guy.. :stuck_out_tongue:

with let task = NSTask() {
launchPath = "/usr/bin/mdfind"
arguments = ["kMDItemDisplayName == *.playground"]
standardOutput = pipe
}

Also valid:

with someVariable {
func1()
func2(“etc")
}

Which would call func1() and func2() on the someVariable instance (which I think would be quite expected).

The problem with this approach is that it can become ambiguous with the outer scope pretty easily, if not to the compiler at least for the reader:

  let launchPath = "a.out"
  let description = "blah blah"
  with let task = NSTask() {
    launchPath = launchPath // eh, what?
    arguments = [launchPath, description] // is that task.description?
  }

I think it's important that things referring to the "task" in the above example be syntactically distinguishable.

It can also lead to code breakage:

  let path = "a.out"
  with let task = NSTask() {
    launchPath = path
  }

That would work fine one day, but what if in the next OS release NSTask gets a new "path" property? That code breaks silently once you recompile with the newer SDK.
  
--
Michel Fortin
michel.fortin@michelf.ca
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution