Erasure Type: Am I Missing Something?

I decided to better understand erasing styles by writing simple code. I have a general soldier protocol. Soldiers have weapons, and soldiers can fight. I would like to create an army of different types of soldiers. I thought that erasing verses would provide me with a means of boxing the adoptive parents of the Soldier, so that I could treat them like ordinary soldiers (instead of snipers, foot soldiers, etc.). But I found that the intermediate type of boxing (type of eraser) should still be made generic in the type associated with the soldier (i.e. weapon). So, I can have rifle soldiers or soldiers fighting rockets, but not just soldiers. Is there anything about using an erasable type that I missed?

import Foundation // Soldiers have weapons and soldiers can fight protocol Weapon { func fire() } protocol Soldier { associatedtype W: Weapon var weapon: W { get } func fight() } extension Soldier { func fight() { weapon.fire() } } // Here are some weapons struct Rifle : Weapon { func fire() { print("Bullets away!") } } struct Rocket : Weapon { func fire() { print("Rockets away!") } } struct GrenadeLauncher : Weapon { func fire() { print("Grernades away!") } } // Here are some soldiers struct Sniper : Soldier { var weapon = Rifle() } struct Infantryman : Soldier { var weapon = Rifle() } struct Artillaryman : Soldier { var weapon = Rocket() } struct Grenadier : Soldier { var weapon = GrenadeLauncher() } // Now I would like to have an army of soldiers but the compiler will not let me. // error: protocol 'Soldier' can only be used as a generic constraint because it has Self or associated type requirements class Army { var soldiers = [Soldier]() func join(soldier: Soldier) { soldiers.append(soldier) } func makeWar() { for soldier in soldiers { soldier.fight() } } } // So, let try the type erasure technique: struct AnySoldier<W: Weapon> : Soldier { var weapon: W private let _fight: () -> Void init<S: Soldier>(soldier: S) where SW == W { _fight = soldier.fight weapon = soldier.weapon } func fight() { _fight() } } var s1 = AnySoldier(soldier: Sniper()) print (type(of: s1)) // AnySoldier<Rifle> s1.fight() // Bullets away! s1.weapon.fire() // Bullets away! s1 = AnySoldier(soldier: Infantryman()) // Still good; Infantrymen use rifles s1 = AnySoldier(soldier: Grenadier()) // Kaboom! Grenadiers do not use rifles // So now I can have an army of Rifle wielding Soldiers class Army { var soldiers = [AnySoldier<Rifle>]() func join(soldier: AnySoldier<Rifle>) { soldiers.append(soldier) } func makeWar() { for soldier in soldiers { soldier.fight() } } } let army = Army() army.join(soldier: AnySoldier(soldier: Sniper())) army.join(soldier: AnySoldier(soldier: Infantryman())) army.join(soldier: AnySoldier(soldier: Grenadier())) // Kaboom! Rifles only army.makeWar() // But, what I really want is an army wherein the weapons are unrestricted. class Army { var soldiers = [AnySoldier]() func join(soldier: AnySoldier) { soldiers.append(soldier) } func makeWar() { for soldier in soldiers { soldier.fight() } } } 
+6
source share
1 answer

You need to also get weapons:

 struct AnyWeapon: Weapon { private let _fire: () -> Void func fire() { _fire() } init<W: Weapon>(_ weapon: W) { _fire = weapon.fire } } 

However, AnySoldier does not have to be shared.

 struct AnySoldier : Soldier { private let _fight: () -> Void let weapon: AnyWeapon init<S: Soldier>(_ soldier: S) { _fight = soldier.fight weapon = AnyWeapon(soldier.weapon) } func fight() { _fight() } } 

Do not lose sight of another approach, which is to replace the Weapon with a simple function and make the Soldier a simple structure. For instance:

 struct Soldier { private let weaponFire: () -> Void func fight() { weaponFire() } static let sniper = Soldier(weaponFire: { print("Bullets away!") }) } let sniper = Soldier.sniper sniper.fight() 

I discuss some of these later in Beyond Crusty: Real-World Protocols . Sometimes you don’t need a protocol.

+5
source

Source: https://habr.com/ru/post/1012914/


All Articles