Struct and class

That’s the point, that’s what we all want to do! That’s what we all get high on. But coding is hard, quite often ending in a dense, incomprehensible maze. And functional programming has good answers for a lot of the hard questions we face. The effort put into understanding some theoretical concepts does pay back in spades. It can be overdone, sure, but it would be a huge shame and loss to dismiss the whole approach as high-browed elitism.

2 Likes

@anon27714269

Jan, I want to note that there wasn't a single word about functional programming in this thread until you answered and literaly mentioned it...

Regardless of the answer to the main question of this topic, if you want to just code the way you want, who or what prevents you from doing so? Swift? Programming paradigms? The opinions of people here? With all due respect, give me a break.

Don't forget that computer science is a full-fledged actively developing science, just like maths. This is obvious, but if you want to go beyond solving linear equations, you will have to learn. If you don't - it's up to you. Luckily, you can keep on solving those equations.

off-topic

I do not understand why you're complaining about the need to learn to code on this forum. If you find yourself in need to learn, you are trying to self-develop. But you say you don't want to. There is a serious contradiction here.

For what it's worth, this thread has long come to a conclusion.

3 Likes

You might also consider using a class (or final class) for large objects:

From Swift optimisation tips

1 Like

You can even create a final class, make it a private member of a struct, then use isUniquelyReferenced to make it copy on write.

I'm sorry if this has already been mentioned in this thread, but it might be worth repeating.

When you see a statement like this:

In Swift, values keep a unique copy of their data. There are several advantages to using value-types, like ensuring that values have independent state.

If you interpret the third word "values" as simply being "structs", then the "data" can of course be anything, including a reference (ie a number/address) to some other data. And while the reference/address-data is a unique copy, the other data, to which it points, can be a shared mutable state, as demonstrated by Foo here:

import Foundation

struct Foo<SomeReferenceType: AnyObject> {
    public var sharedMutableState: SomeReferenceType
}

struct Bar {
    private static var sharedMutableState = 0
    var state: Int {
        get { return Bar.sharedMutableState }
        set { Bar.sharedMutableState = newValue }
    }
}

struct Baz {
    func state() -> UInt32 { return arc4random() }
}

I can recommend this talk called "Value SEMANTICS (not value types!)".

you’re misquoting the documentation. “large objects” refers to reference types that are nested within a value type and so get copied with the rest of the value type.

I think that we should take a step back and think more carefully about the question being asked here. One interpretation is: I need to create a new type with an arbitrary set of members. Should I use a struct or a class? E.g., I want to make a JournalEntry type that contains a String and a Date. For this use case, you could use either. It doesn't matter.

I believe that when it doesn't matter, you should use a struct. It's the same reason you prefer a primitive int over an NSNumber in Objective-C or an Integer object in Java. It's not because "structs are superior", but because classes are overkill and unnecessary. Think of structs as a way of making your own "primitive" values when all you need is a dumb container.

Where things get interesting is when you need more than just a dumb container. What do you do then? Do you reach for classes, inheritance and OOP composition or do you stick to immutable structs and reach for enums, functions, closures, and even more structs?

In theory, Swift is a multi-paradigm language and you get to use the right tool for the task, etc etc. Swift as a community though has been different.

In the Objective-C days, all software architecture talks were about OOP composition. If you wanted to stay informed, you'd watch talks from Ruby and Java conferences and read Martin Fowler's blog and book.

With Swift, everything got replaced by FP paradigms. You go to a Swift conference now and you wouldn't know that Swift even has classes. If you ever see a class mentioned, it's probably as a dirty little trick to achieve reference-semantics. Ruby and Java talks were replaced by Haskell and Elm. Martin Fowler has been replaced by some mathematician who did all of his work 100 years ago before the first computer was made.

From an Objective-C programmer's point of view, it might feel like Swift brought FP at the expense of OOP. I'm not the only one who feels that classes have been under valued by the Swift community. @Chris_Lattner3 recently wrote:

Some people incorrectly think that Swift hates classes: this is an opportunity to restore some of their former glory. (source)

TL;DR Yes, I believe that a struct should be preferred over a class for the most naive (and very common) scenario in which you need a dumb container. But we need to be clear that this does not imply that functional composition is considered superior to OOP composition in Swift. Preferring a struct for the naive scenario != resisting classes for the complex scenario.

2 Likes

Note that classes are used within Swift's Array struct for achieving value-semantics.

And, functions and classes are reference-types.

And as Graham Lee says in this fantastic talk:

Smalltalk started by some people asking:
How can we add dynamic binding to Lisp?
How can we have a function that chooses what functions to run at runtime?
So these were functional programmers who wanted a feature in functional programming and they invented object oriented programming.

Sometimes it's not as simple (and engaging, apparently) as X vs Y, it might be just X and Y.

But it might be more interesting and productive to try and figure out what's behind the labels of X and Y, what they really are/do.

No I’m not. Passing around a large struct (I.e. one with lots of members) is, in general, not as efficient as passing around a reference to an instance of a class. Similarly, if a struct contains lots of refcounted objects, it may be better to wrap them in a single class to reduce the number of things you have to retain/release. The documentation alludes to these tradeoffs several times, for example:

If you read the examples they give, it’s a tree data structure that holds references to its nodes. A “pure” struct has a fixed amount of data that must be copied, whereas copying the tree structure involves recursivly copying all its referenced children. The following sentence makes no sense in the context of pure value types:

When a tree is copied (passed as an argument, initialized or assigned) the whole tree needs to be copied. In the case of our tree this is an expensive operation that requires many calls to malloc/free and a significant reference counting overhead.

Also,, “pass by reference is faster than pass by value” is C++ thinking that’s maybe not so valid in 2018. Pass by reference might have been faster before CPU caches and selective inlining became a thing.

It is still potential a trade off causing cache trashing and wasting memory bandwidth (not like pointer chasing is that much fun either). Not seeing this becoming much less important especially as we move to more and more cores working in parallel.

We will see when and if high performance games for PC’s and game consoles (and VR) setups move away from C++ and into Swift or Rust anyways.

C++ is still appealing for trees. Seems more natural but more risky with pointers.

Another core dump. 8-)

If I were to use Objective-C, I would have to deal with arc. Who knows where the compiler is going to insert a retain or release call. I could go with manual reference counting to make sure but seems out of style to do that. If one were to use Objective-C, then the method messaging is too slow anyways.

Yet, if I port to Swift classes, I'll run into arc again. Can't do that either.

Alternatively, I can use structs. But I can't nest a struct node in a struct Node -- the compiler has a recursion error. (might beach ball Xcode playground too ) I suppose the tree would have to be flattened into an Array of struct Node -- I can reserve capacity up front too. But the struct Node would use Integer indexes into the Array to find its children and avoid the struct recursion issue. So, I suppose that would get around arc and Swift can call methods faster.

Though, if I have a struct node that has a subtree with 2 million nodes and I modify it, I wonder if that would trigger a big copy? ( it probably would not since its just an integer index to its children .. only the index would copy ) Also, if struct Node has a data member that is a generic ( I want to reuse my tree with generics ), is the generic a reference type or value type? I suppose that might cause me to implement cow logic within the struct Node.

the great thing about Swift is you can literally write C code in it, with Swift syntax. Everything you want to do in C, you can do in Swift, and even a bit more since Swift gives you direct access to a lot of less-common CPU operations while you would need inline assembly to do it in C.

I find indirect enum indispensable for trees but I haven’t analyzed their performance characteristics.

Ah, I'll have to try that out. Looks like the correct Swift approach for a tree. An enum is a value type so it gets around the arc issue and appears easier to express a tree with. If the enum is recursive, then I have to use the indirect keyword. Thanks! Coming from a C background, my first impulse was to use classes and then structs.

1 Like

enum trees are actually “garbage collected” (wrong term?) in the sense that the child nodes are destroyed when and only when the parent node is destroyed. The entire tree also gets copied every time the root node gets copied or even moved. Personally, I would use UnsafePointer and friends to implement any sort of tree structure, especially if you need to keep handles to particular nodes in the tree (not possible with an indirect enum because of its value semantics!). Yes, i know most Swift tutorials on the internet use indirect enums for this. There’s a lot of bad Swift advice floating around the internet.

So, a back to the future approach .... just work flow here. This has been more familiar for me.

// Make the buffer upfront
let buffer = UnsafeMutablePointer<NodeStruct>.allocate(capacity: 10000)

// Set default values
let defaults = NodeStruct()
buffer.initialize(repeating: defaults,count: 10000)

// Seek anywhere into the buffer
let offset = buffer.advanced(by: 123)

// Modify the tree nodes. Do whatever...
offset[0].data = DataStruct()
offset[0].left = offset + 1
offset[0].right = offset + 2

// Clean up
buffer.deallocate()

ok, this thread has gotten too big. I will cease and desist. :smile: