Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I restrict an enum to certain cases of another enum?

Say I have a bakery and an inventory of ingredients:

enum Ingredient {
    case flower     = 1
    case sugar      = 2
    case yeast      = 3
    case eggs       = 4
    case milk       = 5
    case almonds    = 6
    case chocolate  = 7
    case salt       = 8
}

A case's rawValue represents the inventory number.

Then I have two recipes:

Chocolate Cake:

  • 500g flower
  • 300g sugar
  • 3 eggs
  • 200ml milk
  • 200g chocolate

Almond Cake:

  • 300g flower
  • 200g sugar
  • 20g yeast
  • 200g almonds
  • 5 eggs
  • 2g salt

Now I define a function

func bake(with ingredients: [Ingredient]) -> Cake

Of course I trust my employees, but I still want to make sure they only use the right ingredients to bake a cake. 😉

I could do this by defining two separate enums like this:

enum ChocolateCakeIngredient {
    case flower
    case sugar
    case eggs
    case milk
    case chocolate
}

enum AlmondCakeIngredient {
    case flower
    case sugar
    case yeast
    case eggs
    case almonds
    case salt
}

and bake a cake like this:

// in chocolate cake class / struct:
func bake(with ingredients: [ChocolateCakeIngredient]) -> ChocolateCake
// in almond cake class / struct:
func bake(with ingredients: [AlmondCakeIngredient]) -> AlmondCake

But then I would have to redefine the same ingredients over and over again as many ingredients are used for both cakes. I really don't want to do that - especially as there are inventory numbers attached to the enum cases as rawValues.

That leads me to the question if there is a way in Swift to restrict an enum to certain cases of another enum? Something like (pseudo code):

enum ChocolateCakeIngredient: Ingredient {
    allowedCases:
        case flower
        case sugar
        case eggs
        case milk
        case chocolate
}

enum AlmondCakeIngredient: Ingredient {
    allowedCases:
        case flower
        case sugar
        case yeast
        case eggs
        case almonds
        case salt
}

Is a composition like this possible? How can I do it?

Or maybe there is another pattern I can use for this scenario?


Update

From all the comments and answers to this question I figured that the example I picked for this question was a little inappropriate as it didn't boil down the essence of the problem and left a loophole regarding type safety.

As all posts on this page relate to this particular example, I created a new question on Stackoverflow with an example that's easier to understand and hits the nail on its head:

➡️ Same question with a more specific example

like image 589
Mischa Avatar asked Nov 16 '16 15:11

Mischa


People also ask

Can enum have methods Swift?

Swift's Enum can have methods. It can have instance methods and you can use it to return expression value for the UI. Let's look at the code above.

Can enums conform to protocols?

Enums can conform to protocolsYes, enums can conform protocols. You can use Swift's own protocols or custom protocols. By using protocols with Enums you can add more capabilities. Sometimes we need to use similar but different object types in the same place.

Can enums inherit Swift?

In Swift language, we have Structs, Enum and Classes. Struct and Enum are passed by copy but Classes are passed by reference. Only Classes support inheritance, Enum and Struct don't.

What is associated enum?

An enum cannot have both raw values and associated values at the same time. The raw values of an enum must be of the same data type. But associated values can be of any type.


2 Answers

I think you should list ingredients for specific recipes as:

let chocolateCakeIngredients: [Ingredient] = [.flower, ...]

and then just check if that list contains the required ingredient.

like image 117
sdasdadas Avatar answered Oct 27 '22 14:10

sdasdadas


You could do something like this in Swift:

enum Ingredients {
    struct Flower { }
    struct Sugar { }
    struct Yeast { }
    struct Eggs { }
    struct Milc { }
}

protocol ChocolateCakeIngredient { }
extension Sugar: ChocolateCakeIngredient { }
extension Eggs: ChocolateCakeIngredient { }
...

func bake(ingredients: [ChocolateCakeIngredient]) { }

In this example i am using the enum Ingredients as a namespace for all my ingedients. This also helps with code completion.

Then, create a protocol for each Recipe and conform the ingredients that go in that recipe to that protocol.

While this should solve your question, I am not sure that you should do this. This (and also your pseudo-code) will enforce that no one can pass a ingredient that does not belong into a chocolate cake when baking one. It will, however, not prohibit anyone to try and call bake(with ingredients:) with an empty array or something similar. Because of that, you will not actually gain any safety by your design.

like image 20
naglerrr Avatar answered Oct 27 '22 13:10

naglerrr