3

I know, that Swift does not allow redefinition of the access level of a case in a enum, meaning the following is not possible

public enum Foo {
    private case Bar
    private indirect case Ind(Foo)
    public init() {
        self = Bar
    }
}

Can anyone tell me why this is not allowed? There are some scenarios, where it is practical to hide cases of a enum and providing a initializer that initiates to such a case, therefore I don't really see the motivation to disallow this feature.

Edit:

Consider the following example of a purely functional tree structure:

public enum Tree<T: Comparable> {
    case Leaf
    indirect case Node(T, Tree, Tree, Int)
    public init() {
        self = .Leaf
    }
    
    public func height() -> Int {
        switch self {
        case .Leaf:
            return 0
        case let .Node(_, _, _, h):
            return h
        }
    }
    // functions for add and remove, etc.
}

For better runtime (which is desirable when implementing self-balancing binary trees), one might want to include the height of the tree as an associated value in the Node case. But this gives encapsulation problems, as one might now construct Node cases with illegal heights. This problem would be solved if the access level of the case could be overwritten, or if stored constants in enums would be allowed.

Community
  • 1
  • 1
tierriminator
  • 587
  • 5
  • 19
  • Can you [edit] and give an example use case? Personally, I've never had a situation where I wanted this. There might be another way to accomplish what you want if you explain. – jscs Nov 13 '17 at 01:03
  • Included an example. A possibility to solve the problem is to wrap the enum in a struct, but this is very painful. – tierriminator Nov 13 '17 at 02:16
  • 1
    In practice of building a lot of these kinds of data structures in Swift, the better solution is to embed it in a class with left/right as optionals. I know enums feel like the natural way to build it, and if not enums then structs, but once you go down this path just a little bit, it tends to explode really fast. Even if you want to expose it as a value type, you wind up hiding a private class internally (like Array and Dictionary both do). – Rob Napier Nov 13 '17 at 16:38

2 Answers2

2

Swift switch statements must be exhaustive. Suppose a consumer of your public enum Foo tries to use it in a switch. If the .bar case is private, how is the switch supposed to handle it?

Cases are part of the public API of an enum. If you wish to make them private, wrap them in a struct that exposes only public operations.

Update: With the implementation of SE-0192 in Swift 5, the @unknown default: syntax was introduced. This is useful in situations where you have a enum for which you've handled all currently existing cases, but want to protect yourself from future cases being added, by specifying the default behaviour.

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Ok, I see your point. The problem normally occurs for initialization. Reading the cases would be perfectly fine, but you might not want to give control over all associated values in a case during initialization (see my added example above). This would also be solved when allowing stored constants with an enum, but this is off topic... – tierriminator Nov 13 '17 at 02:36
  • 1
    @tierriminator Well you wouldn't really want to model a node of a tree as a `Tree` enum in the first place. Some operations only make sense to perform on an entire tree, not on subtrees (for example, copy-on-write, tree balancing), so you would probably want to use some sort of struct/class wrapper that provides these things. The enum can be private, and used internally to provide the storage within this struct/class – Alexander Nov 13 '17 at 05:29
  • 1
    @tierriminator Linkedlist is a similar case. You would never want to hand over just a reference to some node to a client. You would provide a wrapper which holds the start reference (and end reference, if it's a doubly linked list), stores a memoized `count` variable, and encapsulates the nodes underneath Diagram: http://www.codeofhonor.com/blog/wp-content/uploads/2012/09/stl-list.png – Alexander Nov 13 '17 at 05:33
  • I would agree for OOP, but in functional programming, it looks a bit different. You don't want to work with references at all in FP, this would break the most basic principle of FP, that it is stateless. Here I am just searching for a way to optimize the `height` query of a PF tree. If swift supported pure functions, it would know that once calculated, `height` won't change anymore and there wouldn't be a need for this optimization. Maybe the disadvantage of multiple paradigm programming. – tierriminator Nov 13 '17 at 15:56
  • 2
    "You don't want to work with references at all in FP." This isn't correct. Most FP languages *only* have references. You just don't notice it because they're immutable. Make your data structures immutable classes, and it'll follow the patterns you expect much more closely. Swift is not a a functional language; trying to force it to be leads invariably to tears and hair pulling. http://robnapier.net/swift-is-not-functional – Rob Napier Nov 13 '17 at 16:40
  • Well, this is part of the definition of FP: https://en.wikipedia.org/wiki/Functional_programming Of course the implementation of a language does make use of references, but this does not mean that the programming language allows it. There is a difference between implementation and language specification. But still, thanks for the responses, I'll accept the answer. – tierriminator Nov 13 '17 at 18:34
  • But by definition, a pure function is a function, that always produces the same output for the same input, and does therefore not depend on the state. Because a pure functional program only consists of pure functions, a state has no effect on a pure functional program and is therefore not part of it (a pure functional program is as we see nothing else, than a composition of pure functions and therefore a pure function as well). – tierriminator Nov 13 '17 at 19:19
0

You can 'implement' that behaviour by adding type with private constructor.

Here is an example:

public enum Foo {
    case a(Barrier)
    case b(Int, Int, Barrier)

    public init(x: X?) {
        if let x: X = x {
            self = .b(x.y, x.z, Barrier())
        } else {
            self = .a(Barrer())
        }
    }

    /// Use Foo constructor!
    public struct Barrier {
        fileprivate init() { }
    }
}

Someone still can create instance that doesn't make any sense but will look tedious:

func createB() -> Foo {
    switch Foo(x: nil) {
    case .a(let barrier): return Foo.b(1, 2, barrier)
    case .b(_, _, let barrier): return Foo.b(1, 2, barrier)
}

Also make sure that enums are really better than structs or objects.

Monolith
  • 1,067
  • 1
  • 13
  • 29
Pikacz
  • 431
  • 1
  • 5
  • 10
  • In my case we are creating enterprise product (both application and backend). Some objects has additional fields for newer backends and we want a library that will handle every possible backend. Lets say that every object has list of possible Fields that backend will return if we ask for them (fields are implemented as enums). Most of the fields are shared between all backends, for some we have static function getField(api: ..) -> Field? By making that cases private we are protected that handling api versions is locked inside library – Pikacz Oct 24 '19 at 15:31