How Is a Type-Erased Generic Wrapper Implemented

How is a type-erased generic wrapper implemented?

Try:

struct MySequenceOf<T> : SequenceType {
private let _generate:() -> MyGeneratorOf<T>

init<G : GeneratorType where G.Element == T>(_ makeUnderlyingGenerator: () -> G) {
_generate = { MyGeneratorOf(makeUnderlyingGenerator()) }
}

init<S : SequenceType where S.Generator.Element == T>(_ base: S) {
_generate = { MyGeneratorOf(base.generate()) }
}

func generate() -> MyGeneratorOf<T> {
return _generate()
}
}

struct MyGeneratorOf<T> : GeneratorType, SequenceType {

private let _next:() -> T?

init(_ nextElement: () -> T?) {
_next = nextElement
}

init<G : GeneratorType where G.Element == T>(var _ base: G) {
_next = { base.next() }
}

mutating func next() -> T? {
return _next()
}

func generate() -> MyGeneratorOf<T> {
return self
}
}

The basic strategy of implementing ProtocolOf<T> is, like this:

protocol ProtocolType {
typealias Value
func methodA() -> Value
func methodB(arg:Value) -> Bool
}

struct ProtocolOf<T>:ProtocolType {
private let _methodA: () -> T
private let _methodB: (T) -> Bool

init<B:ProtocolType where B.Value == T>(_ base:B) {
_methodA = { base.methodA() }
_methodB = { base.methodB($0) }
}

func methodA() -> T { return _methodA() }
func methodB(arg:T) -> Bool { return _methodB(arg) }
}

Added to answering @MartinR in comment.

Is there a special reason that _generate is a closure and not the generator itself?

First of all, I think, It's a matter of specification or semantics.

Needless to say, the difference is "when to create the generator".

Consider this code:

class Foo:SequenceType {
var vals:[Int] = [1,2,3]
func generate() -> Array<Int>.Generator {
return vals.generate()
}
}

let foo = Foo()
let seq = MySequenceOf(foo)
foo.vals = [4,5,6]
let result = Array(seq)

The problem is: result should be [1,2,3] or [4,5,6]? My MySequenceOf and built-in SequenceOf results the latter. I just matched the behaviors with built-in one.

How to compose multiple type-erased modules in swift?

I think you're over-complicating this somewhat (unless I'm missing something) – your PipelineImpl doesn't appear to be anything more than a wrapper for a function that takes data from a Source and passes it to a Procedure.

As such, it doesn't need to be generic, as the outside world doesn't need to know about the type of data being passed – it just needs to know that it can call exec(). As a consequence, this also means that (for now at least) you don't need the AnySource or AnyProcedure type erasures.

A simple implementation of this wrapper would be:

struct PipelineImpl : Pipeline {

private let _exec : () -> Void

init<S : Source, P : Procedure>(source: S, procedure: P) where S.DataType == P.DataType {
_exec = { procedure.process(data: source.newData()) }
}

func exec() {
// do pre-work here (if any)
_exec()
// do post-work here (if any)
}
}

This leaves you free to add the initialiser to your Pipeline protocol, as it need not concern itself with what the actual DataType is – only that the source and procedure must have the same DataType:

protocol Pipeline {
init<S : Source, P : Procedure>(source: S, procedure: P) where S.DataType == P.DataType
func exec() // The execution may differ
}

Avoiding Type Erasure when implementing the Repository Pattern

It's not quite clear what you're planning to use this Repository for. Repository is designed for environments that have certain characteristics (such as strong linkages to row-style databases). I'll discuss other patterns that often work better in common iOS apps (and even smaller Mac apps).

I usually strongly dislike type erasure, and it often indicates a design problem. But in this case I think type erasure might be a reasonable answer.

So we'll start with the kinds of items we can store. They probably are going to need to have some kind of identifier, and be hashable for many common backends (but maybe you won't need hashing; if not, take it out).

protocol Identified {
associatedtype ID
var id: ID { get }
}

typealias Storable = Identified & Hashable

And then there are things that can act as storage. There is no such thing as a "RepositoryStorage." This is just saying "if you comply with these requirements, then Repository can use you."

protocol RepositoryStorage {
associatedtype Item: Storable

func get(identifier: Item.ID) -> Item?
func add(item: Item)
func delete(identifier: Item.ID)
func allItems() -> [Item]
}

And then the standard, somewhat tedious, type-eraser pattern (there's another pattern that stdlib uses that even more tedious, but this one is good enough for most cases).

// I'm making it a class because I assume it'll have reference semantics.
final class Respository<Item: Storable>: RepositoryStorage {

init<Storage: RepositoryStorage>(storage: Storage) where Storage.Item == Item {
self._get = storage.get
self._add = storage.add
self._delete = storage.delete
self._allItems = storage.allItems
}

let _get: (Item.ID) -> Item?
func get(identifier: Item.ID) -> Item? { return _get(identifier) }

let _add: (Item) -> Void
func add(item: Item) { _add(item) }

let _delete: (Item.ID) -> Void
func delete(identifier: Item.ID) { _delete(identifier) }

let _allItems: () -> [Item]
func allItems() -> [Item] { return _allItems() }
}

So that's fine, it's a general-purpose Repository. And this makes reasonable sense if you're dealing with a large set of items that are probably going to be stored in a SQLite database. But in my experience it is often both too much and too little. Too much if it's just a few items, and too little if you have a lot of items and so probably have to do a lot more than just CRUD. You probably need Query and Join, and then this isn't enough. (Making something flexible in one direction often cuts you off in other directions. There's no universal "generic.")

So can we make it simpler for the case when it's really just a few items? Here's an approach I use regularly:

class DataStore<Key: Hashable & Codable, Value: Codable> {
let identifier: String
private(set) var storage: DataStorage

var dictionary: [Key: Value] {
didSet {
storage[identifier] = try? PropertyListEncoder().encode(dictionary)
}
}

init(identifier: String, storage: DataStorage = UserDefaults.standard) {
self.identifier = identifier
self.storage = storage

let data = storage[identifier] ?? Data()
self.dictionary = (try? PropertyListDecoder().decode([Key: Value].self,
from: data)) ?? [:]
}

subscript(key: Key) -> Value? {
get { return dictionary[key] }
set { dictionary[key] = newValue }
}
}

A DataStore acts as dictionary that you can store key/value pairs in:

let ds = DataStore<String: Item>(identifier: "Item")
ds["first"] = item

It can store anything Codable. With a little modification, you could switch it from a dictionary-like interface to an array or set-like interface; I just usually want a dictionary.

When it's updated, it encodes the entire data store to its storage as Data:

protocol DataStorage {
subscript(identifier: String) -> Data? { get set }
}

This is very fast and efficient for dozens of items. I might rethink if if there were over a hundred items, and it would be inappropriate for hundreds or more items. But for small sets, this is very, very fast.

A very common DataStorage is UserDefaults:

extension UserDefaults: DataStorage {
subscript(identifier: String) -> Data? {
get { return data(forKey: identifier) }
set { set(newValue, forKey: identifier) }
}
}

The key lesson is that this gets rid of all the type-erasure hoop-jumping by creating a common currency (Data) that lower layers work exclusively with. Any time you can do that, separating the upper-layer generic interface from a lower-layer non-generic interface, you'll save yourself a lot of tedium.

This may or may not work for your situation. It's tailored to key/value stores rather than databases, and it's designed to be read much more often than written. But for that usage, it's much simpler and generally faster than the Repository pattern. That's the kind of trade-offs I mean when I say that it's important to know your use case.

What are Reified Generics? How do they solve Type Erasure problems and why can't they be added without major changes?

The whole point is that reified generics have support in the compiler for preserving type information, whereas type erased generics don't. AFAIK, the whole point of having type erasure in the first place was to enable backwards compatibility (e.g. lower versioned JVMs could still understand generic classes).

You can explicitly add the type information in the implementation, as you have above, but that requires additional code every time the list is used, and is pretty messy in my opinion. Also, in this case, you still don't have runtime type checking for all of the list methods unless you add the checks yourself, however reified generics will ensure the runtime types.

How can I erase generics from a type in Swift?

For question Is there a better way to declare the cups array?:

You can use another protocol called DrinkGeneric like this and implement it by Cup Struct:

protocol DrinkGeneric {
func sample()
func typOfDrink() -> Drink.Type
}

struct Cup<T: Drink>: DrinkGeneric {
public var drink: T?
mutating func bottomUp() {
drink = nil
}

public func typeOfDrink() -> Drink.Type {
return type(of: drink!)
}

func sample() {
print("sample")
}

}

Then create an array with type DrinkGeneric like this:

var cups: [DrinkGeneric] = [Cup(drink: Water()), Cup(drink: Tea())]

For check type:

if cups[0].typeOfDrink() is Water.Type {
// Any work
}

Type erasure of generic types, better alternative to dynamic

Another approach that does not involve holding on to the delegate or task completion source is via lambda expressions

public class WorkerHub {
private readonly ConcurrentQueue<TaskWrapper> _tasks;
private readonly Timer _timer;

public WorkerHub() {
_timer = new Timer();
_tasks = new ConcurrentQueue<TaskWrapper>();
}

public Task<TResult> Post<TResult>(Func<TResult> func) {
var cts = new TaskCompletionSource<TResult>();

Action handler = () => {
cts.SetResult(func());
};

var wrapper = new TaskWrapper { Invoke = handler };
_tasks.Enqueue(wrapper);
return cts.Task;
}

public Task Post(Action action) {
var cts = new TaskCompletionSource<bool>();
Action handler = () => {
action();
cts.SetResult(true);
};
var wrapper = new TaskWrapper { Invoke = handler };
_tasks.Enqueue(wrapper);
return cts.Task;
}

private TaskWrapper Pop()
{
_tasks.TryDequeue(out var wrapper);
return wrapper;
}


public void Start() {
_timer.Enabled = true;
_timer.AutoReset = true;

_timer.Interval = 2500;
_timer.Elapsed += (sender, args) => {
var wrapper = Pop();
if (wrapper != null)
wrapper.Invoke();
};
_timer.Start();
}

public void Stop() {
}

private class TaskWrapper {
public Action Invoke { get; set; }
}
}

an Action delegate is created to handle the desired behavior and that is given to the wrapper to be invoked when needed.

The wrapper now becomes redundant and can be removed altogether

public class WorkerHub {
private readonly ConcurrentQueue<Action> _tasks;
private readonly Timer _timer;

public WorkerHub() {
_timer = new Timer();
_tasks = new ConcurrentQueue<Action>();
}

public Task<TResult> Post<TResult>(Func<TResult> func) {
var cts = new TaskCompletionSource<TResult>();
Action handler = () => {
cts.SetResult(func());
};
_tasks.Enqueue(handler);
return cts.Task;
}

public Task Post(Action action) {
var cts = new TaskCompletionSource<bool>();
Action handler = () => {
action();
cts.SetResult(true);
};
_tasks.Enqueue(handler);
return cts.Task;
}

public void Start() {
_timer.Enabled = true;
_timer.AutoReset = true;

_timer.Interval = 2500;
_timer.Elapsed += (sender, args) => {
Action handler = null;
if (_tasks.TryDequeue(out handler) && handler != null)
handler.Invoke();
};
_timer.Start();
}

public void Stop() {
}
}

Yes, there are more refactors that can be done to improve this design, but this should be enough to get the general idea across

Why does this type erasure implementation (simplified boost:any) gives segmentation fault?

The implicitly defined copy constructor of your any type just copies the IValue pointer, so both the original object and the copy will delete the same pointer. You need to write a copy constructor that actually copies the stored object.



Related Topics



Leave a reply



Submit