[swift-evolution] [Pitch] Type Behaviors

Letanyan Arumugam letanyan.a at gmail.com
Thu Jun 22 01:00:49 CDT 2017


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.


More information about the swift-evolution mailing list