Hello Swift Evolution

Please have a look and tell me what you think.
note: it’s a bit long.

Like many others "property behaviours" was something that I found quite 
interesting, but what got
me really peaked my interest was what it could do. So now as it's been deferred 
for a while I would like to 
either resurrect it or talk about a different solution. The idea I've had is 
rather different, but 
also really similar, I'm no expert at all but I think the internal 
implementation would be quite similar.

So as with the proposal doc of SE-0030 I'll be going through prodominantly use 
cases with explanations as 
we go in this rough sketch.

property behaviours proposal: 
https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md
property behaviours thread: 
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/003148.html

-------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------

# Type (Wrappers|Behaviours) 
        // Type Wrapper is more explanatory of what it does, but Type Behaviour 
sounds cooler :)
        
This feature doesn't require any new 'explicit' functionality be added to 
Swift, but rather 
adds sugar to certain marked types that conform to a behaviour protocol. I 
think it would be best 
to explain the sugar as they appear in the examples 

```
protocol Behaviour {
        associatedtype InitType
        associatedtype SetterType
        associatedtype GetterType
        
        init(_ initialValue: @autoclosure @escaping () -> InitType)
        
        mutating func set(_ value: SetterType)
        mutating func get() -> GetterType
}
```

        The sugared syntax is added below code usage. 
        All the sample code works in Swift 3.1
        

# Some Informalish Rules:

1. A type must conform to the Behaviour protocol to be used as a behaviour
2. A type will have to be explicitly defined at the use site, as a behaviour, 
to be treated as one.
        It will be marked in with some syntax, such as, 
~BehaviourConformingType<T>
3. From (2) a type can still be used as a normal type
4. 'Behaviour' types can be used anywhere a 'normal' type can be used and is 
represent internally as a 
        'normal' type


# Examples

## First Example Lazy:

```
struct Lazy<Value> : Behaviour {
        typealias InitType = Value
        typealias SetterType = Value
        typealias GetterType = Value
        
        var value: Value?
        private var initialValue: () -> Value
        
        init(_ initialValue: @autoclosure @escaping () -> Value) {
                value = nil
                self.initialValue = initialValue
        }
        
        mutating func get() -> Value {
                guard let result = value else {
                        let initial = initialValue()
                        value = initial
                        return initial
                }
                return result
        }
        
        mutating func set(_ value: Value) {
                self.value = value
        }
}
print("-----------------------------Lazy")
var l = Lazy(10)
print(l) 
print(l.get())
print(l)
```

Sugar:
        [1.][2.] var number: ~Lazy = 10
        [3.] print(number as ~Lazy)
        [4.] print(number)
        [3.] print(number as ~Lazy)
        
1. Initializers are inferred.
2. Generic parameters are also inferred for Behaviours.
        If they can't then we can use ~Lazy<Int> as an example here
3. Getting the wrapping object is done with a cast 
        // returns Lazy<Int>. will be a compile time error if it's not a Lazy 
'behaviour'
4. When a 'Behaviour' object is called normally it's get() is called


## Second Example Observed

```
struct Observed<Value> : Behaviour {
        typealias InitType = Value
        typealias GetterType = Value
        typealias SetterType = Value
        private var value: Value
        
        /* @accessor */ var willSet: (Value) -> () = { _ in }
        /* @accessor */ var didSet: (Value) -> () = {_ in }
        
        init(_ initialValue: @autoclosure @escaping () -> Value) {
                value = initialValue()
        }
        
        func get() -> Value {
                return value
        }
        
        mutating func set(_ value: Value) {
                willSet(value)
                let oldValue = self.value
                self.value = value
                didSet(oldValue)
        }
}
print("-----------------------------Observer")
var o = Observed(10)

o.didSet = { old in 
        print("I changed:", old, "to", o.get())
}
o.willSet = { new in
        print("I will change:", new, "to", o.get())
}
o.set(5)

print(o.get())
```

Sugar:
        var o: Observed = 10
        [1.] o.didSet = { old in
                print("I changed:", old, "to", o)
        }
        [1.] o.willSet = { new in
                print("I will change:", new, "to", o)
        }
        [2.] o = 5
        
1. didSet and willSet are only available directly becuase they have been marked 
with @accessor
2. directly setting an object calls the behaviours set method


## Third Example ChangeObserver

```
struct ChangeObserver<Value: Equatable> : Behaviour {
        typealias InitType = Value
        typealias GetterType = Value
        typealias SetterType = Value
        private var value: Value
        
        /* @accessor */ var willChange: (Value) -> () = { _ in }
        /* @accessor */ var didChange: (Value) -> () = {_ in }
        
        init(_ initialValue: @autoclosure @escaping () -> Value) {
                value = initialValue()
        }
        
        func get() -> Value {
                return value
        }
        
        mutating func set(_ value: Value) {
                let oldValue = self.value
                if self.value != value {
                        willChange(value)
                        self.value = value
                        didChange(oldValue)
                }
        }
}

print("-----------------------------Change Observer")
var co = ChangeObserver(1)

co.willChange = { new in
        print("new value will be:", new)
}
co.didChange = { old in
        print("old value was:", old)
}

co.set(1)
co.set(5)
```

Sugar:
        var co: ~ChangeObserver = 1
        co.willChange = { new in 
                print("new value will be:", new)
        }
        co.didChange = { old in
                print("old value was:", old)
        }
        co = 1
        co = 5
        
#. Nothing new here just showing for completeness



## Fourth Example Sychronized Property Access

```
func with<R>(lock: AnyObject, body: () -> R) -> R {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        
        return body()
}

final class Synchronized<Value> : Behaviour {
        typealias InitType = Value
        typealias GetterType = Value
        typealias SetterType = Value
        private var value: Value
        
        init(_ initialValue: @autoclosure @escaping () -> Value) {
                value = initialValue()
        }
        
        func get() -> Value {
                return with(lock: self) {
                        return value
                }
        }
        
        func set(_ value: Value) {
                with(lock: self) {
                        self.value = value 
                }  
        }
}

print("-----------------------------Synchronized Property Access")
func fibonacci(_ n: Int) -> Int {
        if n < 2 {
                return 1
        }
        return fibonacci(n - 2) + fibonacci(n - 1)
}

var spa = Synchronized(1)

DispatchQueue(label: "queueueueue1").async {
        spa.set(fibonacci(40))
        print("fib(40): ", spa.get())
}
DispatchQueue(label: "queueueueue2").async {
        spa.set(fibonacci(1))
        print("fib(1): ", spa.get())
}
```

Sugar:
        var spa: ~Synchronized = 1
        spa = 1
        DispatchQueue(label: "queueueueue1").async {
                spa = fibonacci(40)
                print("fib(40): ", spa)
        }
        DispatchQueue(label: "queueueueue2").async {
                spa = fibonacci(1)
                print("fib(1): ", spa)
        }
        
#. Again nothing new just showing another use



## Fifth Example Copying

//---------------------------------------------------------NSCopying

```
struct Copying<Value: NSCopying> : Behaviour {
        typealias InitType = Value
        typealias GetterType = Value
        typealias SetterType = Value
        var value: Value
        
        init(_ initialValue: @autoclosure @escaping () -> Value) {
                value = initialValue().copy() as! Value
        }
        
        func get() -> Value {
                return value
        }
        
        mutating func set(_ value: Value) {
                self.value = value.copy() as! Value
        }
}

final class Point : NSCopying, CustomStringConvertible {
        var x, y: Int
        
        init(x: Int, y: Int) {
                (self.x, self.y) = (x, y)
        }
        
        func copy(with zone: NSZone? = nil) -> Any {
                return type(of: self).init(x: x, y: y)
        }
        
        var description: String {
                return "(\(x), \(y))"
        }
}

print("-----------------------------NSCopying")
let p = Point(x: 1, y: 1)
let q = Point(x: 2, y: 2)

var a = Copying(p)
var b = Copying(q)
a.set(b.get())
a.value.x = 10

print(a.get())
print(b.get())
```

Sugar:
        let p = Point(x: 1, y: 1)
        let q = Point(x: 2, y: 2)
        
        var a: ~Copying = p
        var b: ~Copying = q
        a = b
        a.x = 10
        
        print(a)
        print(b)
        
#. Another example, nothing new

## Sixth Example

```
//---------------------------------------------------------Reference

final class Reference<Value> : Behaviour {
        typealias InitType = Value
        typealias SetterType = Value
        typealias GetterType = Reference<Value>
        
        var value: Value
        
        init(_ initialValue: @autoclosure @escaping () -> Value) {
                value = initialValue()
        }
        
        [1.] func get() -> Reference<Value> {
                return self
        }
        
        func set(_ value: Value) {
                self.value = value
        }
}

print("-----------------------------Reference")

var refa = Reference(10)
var refb = refa.get()
refa.set(10)
print(refa.get().value, "==", refb.get().value)
```

Sugar:
        var refa: ~Reference = 10
        var refb = refa
        refa = 10
        print(refa.value, "===", refb.value)
        
1. Okay theres a bit to this namely as stated above 'Behaviours' can be used as 
normal types
        such as used here with the getter returning a "Reference", note the 
difference between "Reference"
        and "~Reference", this is where, I think, the beauty of this solution 
comes in as 'Behaviour' 
        types are just types with getter, setter and init sugar.
        
## Seventh Example Timed Functions
//---------------------------------------------------------Timed

```
struct Timed<InputType, ReturnType> : Behaviour {
        [1.] typealias InitType = (InputType) -> ReturnType
        [1.] typealias SetterType = (InputType) -> ReturnType
        [1.] typealias GetterType = (InputType) -> (TimeInterval, ReturnType)
                
        var value: (InputType) -> ReturnType
        
        init(_ initialValue: @autoclosure @escaping () -> InitType) {
                value = initialValue()
        }
        
        func get() -> GetterType {
                return  { input in
                        let start = Date()
                        let result = self.value(input)
                        let time = Date().timeIntervalSince(start)
                        return (time, result)
                }
        }
        
        mutating func set(_ value: @escaping SetterType) {
                self.value = value
        }
}

func compareTimes<T, U>(for ops: [Timed<T, U>], against value: T) {
        for op in ops {
                let (t, r) = op.get()(value)
                print("Time it took to calculate", r, "was", t, "ms")
        }
}

print("-----------------------------Timed")

let fib = Timed(fibonacci)
let fact = Timed(factorial)
compareTimes(for: [fib, fact], against: 16)
```

Sugar:
        func compareTimes<T, U>(for ops: [2.] [~Timed<T, U>], against value: T) 
{
                for op in ops {
                        let (t, r) = op(value)
                        print("Time it took to calculate", r, "was", t, "ms")
                }
        }
        
        let fib: ~Timed = fibonacci
        let fact: ~Timed = factorial
        compareTimes(for: [fib, fact], against: 16)
        
1. An example of function wrapping
2. Showing how 'Behaviour' types can be used as parameters

# Tentative

## Composition

A type can be wrapped by multiple behaviours and act in the order of appearance 
such as

```     
let a: ~T<~U<Int>> = 10

print(a) // wil be equivalant to ((a as ~T).get() as ~U).get()
```

this is for me in a tentative position because even that simple example is 
rather confusing,
so possible making a single behaviour that has the multiple behaviours you 
require should be done 
seperatly instead

## Another Benefit?

Another benefit of modeling the system this way gives us 'free' features 
whenever classes, structs
and even enums (possible any types that can conform to a protocol? so tuples in 
the future?) get new
features.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to