Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.

Proposal: change structural representation to avoid StructuralEmpty. #5

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ This module includes benchmarks done using
You can run benchmarks using the following command:

```
$ swift run -c release StructuralBenchmarksMain
$ swift run -c release -Xswiftc -cross-module-optimization StructuralBenchmarksMain
```

An example output of the benchmark run can be found in [benchmark.results] file.
34 changes: 34 additions & 0 deletions Sources/StructuralBenchmarks/SequentialTransformerBenchmarks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Benchmark
import StructuralCore
import StructuralExamples

let sequentialTransformerBenchmarks = BenchmarkSuite(name: "SequentialTransformer") { suite in

for sequenceSize in [1, 10, 100, 1000, 10000, 100_000] {
suite.benchmark("swift lazy transform (count: \(sequenceSize))") {
let s = Array((0..<sequenceSize).lazy.map { $0 * 2 })
precondition(s.count == sequenceSize)
}
}

for sequenceSize in [1, 10, 100, 1000, 10000, 100_000] {
suite.benchmark("structural lazy transform (count: \(sequenceSize))") {
let s = Array((0..<sequenceSize).lazyTransform(SequentialDouble()))
precondition(s.count == sequenceSize)
}
}
}
1 change: 1 addition & 0 deletions Sources/StructuralBenchmarks/Suites.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ public let suites = [
encodeJSONBenchmarks,
inplaceAddBenchmarks,
scaleByBenchmarks,
sequentialTransformerBenchmarks,
]
225 changes: 225 additions & 0 deletions Sources/StructuralExamples/SequentialTransformer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// This file contains an oversimplified implementation of a value-semantic alternative to closures
/// and shows (in concert with the corresponding unit tests and benchmarks) the potential benefits.

import StructuralCore

/// An arbitrary, stateful transformation from `Input` to `Output` with value semantics.
///
/// You can think of SequentialTransformer as a way of building up simple data structures that are
/// similar to closures, however instead adhere to value semantics and preserve static type
/// information to enable inlining.
///
/// Useful applications of such a type might be in place of an escaping closure.
public protocol SequentialTransformer {
/// The [domain](https://en.wikipedia.org/wiki/Domain_of_a_function) to the function represented
/// by `self`.
associatedtype Input
/// The [range](https://en.wikipedia.org/wiki/Range_of_a_function) of the function represented
/// by `self`.
associatedtype Output

/// Returns the result of applying the function represented by `self` to `i`.
mutating func callAsFunction(_ i: Input) -> Output
}

extension Sequence {
/// Returns a lazy transforming sequence, preserving type information of `transformation`.
public func lazyTransform<T: SequentialTransformer>(_ transformer: T) -> LazyTransformSequence<Self, T> {
LazyTransformSequence(u: self, t: transformer)
}

/// Returns a value that can be used to consume `self` exactly once, computing a result with
/// `transformer`.
public func lazyFold<T: SequentialTransformer>(_ transformer: T) -> LazyFoldingSequence<Self, T> {
LazyFoldingSequence(u: self, t: transformer)
}
}

public struct LazyTransformSequence<
Underlying: Sequence,
Transformer: SequentialTransformer
>: Sequence where Transformer.Input == Underlying.Element {
public struct Iterator: IteratorProtocol {
fileprivate var u: Underlying.Iterator
fileprivate var t: Transformer

public mutating func next() -> Transformer.Output? {
guard let i = u.next() else { return nil }
return t(i)
}
}

public func makeIterator() -> Iterator {
Iterator(u: u.makeIterator(), t: t)
}

fileprivate let u: Underlying
fileprivate let t: Transformer
}

public struct LazyFoldingSequence<
Underlying: Sequence,
Transformer: SequentialTransformer
> where Transformer.Input == Underlying.Element {

private var u: Underlying?
private var t: Transformer

fileprivate init(u: Underlying, t: Transformer) {
self.u = u
self.t = t
}

public var result: Transformer {
mutating get {
consume()
return t
}
}

public mutating func consume() {
if let u = u {
var itr = u.makeIterator()
while let n = itr.next() {
_ = t(n)
}
self.u = nil
}
}
}

// TODO: Implement a lazy consumer, lazy filter, etc.

// Sugar.

extension SequentialTransformer where Self: Structural, Self.StructuralRepresentation: SequentialTransformer, Input == Self.StructuralRepresentation.Input, Output == Self.StructuralRepresentation.Output {
public mutating func callAsFunction(_ i: Input) -> Output {
self.structuralRepresentation(i)
}
}

// Inductive cases

extension StructuralProperty: SequentialTransformer where Value: SequentialTransformer {
public mutating func callAsFunction(_ i: Value.Input) -> Value.Output {
value(i)
}
}

extension StructuralCons: SequentialTransformer
where Value: SequentialTransformer, Next: SequentialTransformer, Value.Output == Next.Input {
public mutating func callAsFunction(_ i: Value.Input) -> Next.Output {
let tmp = value(i)
return next(tmp)
}
}

extension StructuralStruct: SequentialTransformer where Properties: SequentialTransformer {
public mutating func callAsFunction(_ i: Properties.Input) -> Properties.Output {
properties(i)
}
}

// Base cases.

// Below are some silly pre-written transformations that can be composed with structural generic
// programming.

public struct Duplicate<Input>: SequentialTransformer {
public init() {}

public func callAsFunction(_ i: Input) -> (Input, Input) {
(i, i)
}
}

public struct Merge<Elem: AdditiveArithmetic>: SequentialTransformer {
public init() {}
public func callAsFunction(_ i: (Elem, Elem)) -> Elem {
i.0 + i.1
}
}

public struct ApplyFirst<Underlying: SequentialTransformer, Ignored>: SequentialTransformer {
var underlying: Underlying

public init(_ underlying: Underlying) {
self.underlying = underlying
}

public mutating func callAsFunction(_ i: (Underlying.Input, Ignored)) -> (Underlying.Output, Ignored) {
(underlying(i.0), i.1)
}
}

public struct EnumerateTransformer<PassThrough>: SequentialTransformer {
var counter = 0
public init() {}

public mutating func callAsFunction(_ i: PassThrough) -> (Int, PassThrough) {
let returnValue = (counter, i)
counter += 1
return returnValue
}
}

public struct SumTransformer: SequentialTransformer {
public private(set) var sum = 0
public init() {}
public mutating func callAsFunction(_ i: Int) -> Int {
sum += i
return sum
}
}

///// Examples of use of structural generic programming. ///////////////////////////////////////

/// An incredibly inefficient way of multiplying a value by 2.
public struct SequentialDouble<Elem: AdditiveArithmetic>: Structural, SequentialTransformer {
var d = Duplicate<Elem>()
var m = Merge<Elem>()

public init() {}
}

// Generated by the compiler (eventually).
extension SequentialDouble {
public typealias Input = Elem
public typealias Output = Elem

public typealias StructuralRepresentation =
StructuralStruct<
StructuralCons<StructuralProperty<Duplicate<Elem>>,
StructuralProperty<Merge<Elem>>>>

public init(structuralRepresentation: StructuralRepresentation) {
self.d = structuralRepresentation.properties.value.value
self.m = structuralRepresentation.properties.next.value
}

public var structuralRepresentation: StructuralRepresentation {
get {
return StructuralStruct(Self.self,
StructuralCons(StructuralProperty("d", d, isMutable: true),
StructuralProperty("m", m, isMutable: true)))
}
set {
self.d = newValue.properties.value.value
self.m = newValue.properties.next.value
}
}
}
36 changes: 36 additions & 0 deletions Tests/StructuralTests/SequentialTransformerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import XCTest

@testable import StructuralExamples

final class SequentialTransformerTests: XCTestCase {
func testLazyTransform() {
let seq = (0..<Int.max).lazyTransform(SequentialDouble()).prefix(10)
XCTAssertEqual([0, 2, 4, 6, 8, 10, 12, 14, 16, 18], Array(seq))
}

func testLazyConsume() {
var lazySum = (0..<10).lazyTransform(SequentialDouble()).lazyFold(SumTransformer())
// TODO: Test to ensure the sequence is only traversed once!
XCTAssertEqual(90, lazySum.result.sum)
XCTAssertEqual(90, lazySum.result.sum)
}

static var allTests = [
("testLazyTransform", testLazyTransform),
("testLazyConsume", testLazyConsume),
]
}
1 change: 1 addition & 0 deletions Tests/StructuralTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import XCTest
testCase(EncodeJSONTests.allTests),
testCase(InplaceAddTests.allTests),
testCase(StructuralRepresentationTests.allTests),
testCase(SequentialTransformerTests.allTests),
]
}
#endif