Why How to Make Same-Type Requirement in Swift with Generics? Is There Any Way

Why can i make same-type requirement in swift with generics? Is there any way?

In Swift 4 and later, you can write:

public final class Process<InputType, OutputType, Memory> {
// ...
}

extension Process where InputType == OutputType {
func bypass() -> Process<InputType, OutputType, Memory> {
// ...
}
}

Original answer (Swift 3):

You can't constraint types on generic classes yet even though some changes are coming in Swift 4. However, you can constraint types on a protocol. You can make a protocol that only Process conforms to like this:

protocol ProcessProtocol {
// I haven't found a way to name these associated type identically to
// those in the class. If anyone discover a way, please let me know
associatedtype IT
associatedtype OT
associatedtype MT
}

final public class Process<InputType, OutputType, MemoryType>: ProcessProtocol {
typealias IT = InputType
typealias OT = OutputType
typealias MT = MemoryType

// your code
}

// Note that this is an extension on the protocol, not the class
extension ProcessProtocol where IT == OT {
func foo() {
// this function is only available when InputType = OutputType
}
}

Swift: Same-Type requirement makes generic parameters equivalent?

It seems like type erasure is the answer to your problem. The key idea to the type erasure pattern is to put your strongly typed but incompatible data (like an AItem<String> and an AItem<Data>) inside of another data structure which stores them with "less precise" types (usually Any).

A major drawback of type erasure is that you're discarding type information—if you need to recover it later on to figure out what you need to do with each element in your array, you'll need to try to cast your data to each possible type, which can be messy and brittle. For this reason, I've generally tried to avoid it where possible.

Anyways, here's an example of type erasure based on your pseudo code:

protocol ConstraintProtocol {}

extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}

struct AItem<T: ConstraintProtocol> {
var aPara: T

init(aPara: T) {
self.aPara = aPara
}
}

struct AnyAItem {
// By construction, this is always some kind of AItem. The loss of type
// safety here is one of the costs of the type erasure pattern.
let wrapped: Any

// Note: all the constructors always initialize `wrapped` to an `AItem`.
// Since the member variable is constant, our program is "type correct"
// even though type erasure isn't "type safe."
init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
self.wrapped = wrapped
}

init<T: ConstraintProtocol>(aPara: T) {
self.wrapped = AItem(aPara: aPara);
}

// Think about why AnyAItem cannot expose any properties of `wrapped`...
}

var anArray: [AnyAItem] = [
AnyAItem(aPara: "String"),
AnyAItem(aPara: 1234),
AnyAItem(aPara: "a path".data(using: .utf8)!)
]

for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}

// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
if (item.wrapped is AItem<String>) {
return "String"
} else if (item.wrapped is AItem<Data>) {
return "Data"
} else if (item.wrapped is AItem<Int>) {
return "Int"
}
return "unknown"
}

An alternative to type erasure you could consider, which works well if there's a small, finite set of concrete types your generic could take on, would be to use an enum with associated values to define a "sum type". This might not be a good choice if the protocol you're interested in is from a library that you can't change. In practice, the sum type might look like this:

enum AItem {
case string(String)
case data(Data)
case int(Int)
}

var anArray: [AItem] = [
.string("String"),
.int(1234),
.data("a path".data(using: .utf8)!)
]

for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}

func handleItem(item: AItem) -> String {
// Note that no casting is required, and we don't need an unknown case
// because we know all types that might occur at compile time!
switch item {
case .string: return "String"
case .data: return "Data"
case .int: return "Int"
}
}

Can't constrain generic types to be equivalent in Swift 5

If T == U, then you only have a single generic type, so don't declare 2 generic types. You simply need to constraint both input arguments to the same generic type, T.

func isSameNumber<T>(lhs: T, rhs: T) where T: Numeric {
if lhs == rhs {
print("true")
}
else{
print("false")
}
}

Swift require that two generics are of the same type

As @conner noted but you would never specify it that way as there is only one type. This is better:

func functionName<T> (lhs: T, rhs: T) -> Bool { ... }

How to use generic types to get object with same type

Update: For a better solution, see Rob's answer.


Similarly as in How can I create instances of managed object subclasses in a NSManagedObject Swift extension?,
this can be done with a generic helper method:

extension NSManagedObject {

func transferTo(context context: NSManagedObjectContext) -> Self {
return transferToHelper(context: context)
}

private func transferToHelper<T>(context context: NSManagedObjectContext) -> T {
return context.objectWithID(objectID) as! T
}
}

Note that I have changed the return type to Self.
objectWithID() does not return an optional
(in contrast to objectRegisteredForID(), so there is no need to
return an optional here.

Update: Jean-Philippe Pellet's suggested
to define a global reusable function instead of the helper method
to cast the return value to the appropriate type.

I would suggest to define two (overloaded) versions, to make this
work with both optional and non-optional objects (without an unwanted
automatic wrapping into an optional):

func objcast<T>(obj: AnyObject) -> T {
return obj as! T
}

func objcast<T>(obj: AnyObject?) -> T? {
return obj as! T?
}

extension NSManagedObject {

func transferTo(context context: NSManagedObjectContext) -> Self {
let result = context.objectWithID(objectID) // NSManagedObject
return objcast(result) // Self
}

func transferUsingRegisteredID(context context: NSManagedObjectContext) -> Self? {
let result = context.objectRegisteredForID(objectID) // NSManagedObject?
return objcast(result) // Self?
}
}

(I have updated the code for Swift 2/Xcode 7. The code for earlier
Swift versions can be found in the edit history.)

Can't extend generic struct for specific type

The way this roadblock is commonly encountered is when attempting to extend Array. This is legal:

extension Array where Element : Comparable {
}

But this is illegal:

extension Array where Element == Int {
}

The compiler complains:

Same-type requirement makes generic parameter 'Element' non-generic

The problem is the use of == here in combination with Array's parameterized type Element, because Array is a generic struct.

One workaround with Array is to rise up the hierarchy of Array's inheritance to reach something that is not a generic struct:

extension Sequence where Iterator.Element == Int {
}

That's legal because Sequence and Iterator are generic protocols.

Another solution, though, is to rise up the hierarchy from the target type, namely Int. If we can find a protocol to which Int conforms, then we can use the : operator instead of ==. Well, there is one:

extension CountableClosedRange where Bound : Integer {
}

That's the real difference between our two attempts to implement random on a range. The reason your attempt hits a roadblock and mine doesn't is that you are using == whereas I am using :. I can do that because there's a protocol (FloatingPoint) to which Double conforms.

But, as you've been told, with luck all this trickery will soon be a thing of the past.

How can I have 2 generics use the same T type

You can't pass the associatedtype properties in that fashion. The compiler does not understand how they should relate or how you expect them to be used.

You could refactor your AppRouter to accept a type that represents the ViewFactoryType it should expect.

final class AppRouter<T, R> {

private let viewControllerFactory: R
private let navigationController: UINavigationController

public init(navigationController: UINavigationController, viewControllerFactory: R) {
self.navigationController = navigationController
self.viewControllerFactory = viewControllerFactory
}

private func routeTo(_ viewController: UIViewController, transition: Transition) {
switch transition {
case .push: navigationController.pushViewController(viewController, animated: true)
case .present: navigationController.present(viewController, animated: true)
case .replace: navigationController.setViewControllers([viewController], animated: false)
}
}
}

extension AppRouter: RouterType where T == Scene, R == ViewFactory<T> {
func navigate(to target: Scene, as transition: Transition) {
let viewController = viewControllerFactory.create(for: target)
routeTo(viewController, transition: transition)
}
}

You can create an instance by passing the correct type now

    let viewControllerFactory = ViewFactory<Scene>()
let router = AppRouter<Scene, ViewFactory<Scene>>(navigationController: navigationController, viewControllerFactory: viewControllerFactory)

Swift, use generic property with different specific types - Reference to generic type requires arguments in

You can specify a protocol for providing Stage types like so:

protocol StageProvider {

associatedtype T: Stage

func getType() -> T.Type

}

Then make your SomethingThatKnowsAboutTheStages or any other one conform this protocol:

class SomethingThatKnowsAboutTheStages: StageProvider {

typealias T = SecondStage

func getType() -> T.Type {
SecondStage.self
}

}

Add an initializer for your FooBarController:

class FooBarController<StageType: Stage>: UIViewController {

convenience init(stage: StageType.Type) {
self.init()
}

}

And finally use all these:

func fooBarScreen<T: StageProvider>(boop: T) {
let controller = FooBarController(stage: boop.getType())
}

Constraining one generic with another in Swift

@luk2302 has hinted at much of this in the comments, but just to make it explicit for future searchers.

protocol Baz {
func bar<T>(input:T)
}

This protocol is almost certainly useless as written. It is effectively identical to the following protocol (which is also almost completely useless):

protocol Baz {
func bar(input:Any)
}

You very likely mean (and hint that you mean):

protocol Baz {
typealias T
func bar(input: T)
}

As you note, this makes the protocol a PAT (protocol with associated type), which means you cannot put it directly into a collection. As you note, the usual solution to that, if you really need a collection of them, is a type eraser. It would be nice if Swift would automatically write the eraser for you, which it likely will be able to do in the future, and would eliminate the problem. That said, though slightly tedious, writing type erasers is very straightforward.

Now while you cannot put a PAT directly into a collection, you can put a generically-constrained PAT into a collection. So as long as you wrap the collection into a type that constrains T, it's still no problem.

If these become complex, the constraint code can become tedious and very repetitive. This can be improved through a number of techniques, however.

Generic structs with static methods can be used to avoid repeatedly providing constraints on free-functions.

The protocol can be converted into a generic struct (this formalizes the type eraser as the primary type rather than "as needed").

Protocols can be replaced with functions in many cases. For example, given this:

protocol Bar {
typealias T
func bar(input: T)
}

struct Foo : Bar {
func bar(input: Int) {}
}

You can't do this:

 let bars: [Bar] = [Foo()] // error: protocol 'Bar' can only be used as a generic constraint because it has Self or associated type requirements

But you can easily do this, which is just as good:

let bars = [(Int) -> Void] = [Foo().bar]

This is particularly powerful for single-method protocols.

A mix of protocols, generics, and functions is much more powerful than trying to force everything into the protocol box, at least until protocols add a few more missing features to fulfill their promise.

(It would be easier to give specific advice to a specific problem. There is no one answer that solves all issues.)



Related Topics



Leave a reply



Submit