4 ms·
Is that what happens by default, or do you need to take care to make it be so? I understand you can create a new car-cdr -list out of existing list and a new e
by galaxyLogic 7y ago
Is that what happens by default, or do you need to take care to make it be so?
I understand you can create a new car-cdr -list out of existing list and a new element, without having to modify anything. So would implementing the game-state functionally then mostly consist of creating new lists non-destructively?
Also still wondering about the 'main loop". Is it a tail-recursive function?
- lalaithion 7y agoThe common data structures in pure languages are slightly different than what you would find in a stateful language, so you do need to take care to make it so only via what data structures you choose to use. Additionally, the author does say they use Control.ST, which depending how you look at it is either a pure wrapper over stateful operations or stateful looking syntax implemented with technically pure semantics. https://www.microsoft.com/en-us/research/wp-content/uploads/1994/06/lazy-functional-state-threads.pdf https://www.microsoft.com/en-us/research/wp-content/uploads/... The main loop is probably a tail recursive function.
- dwohnitmok 7y agoThat's not the correct link. Idris' ST is not the same as Haskell's ST. Haskell's ST is a way of introducing locally scoped mutability, which is indistinguishable from immutability to an outside observer, but can offer better performance. Idris' ST is a way of implementing a state machine with statically checked state transitions. http://docs.idris-lang.org/en/latest/st/state.html http://docs.idris-lang.org/en/latest/st/state.html
- dwohnitmok 7y agoIt happens by default in the sense that all standard library data structures in most functional programming languages implement data sharing so that new versions share any common data with the old version instead of being created by scratch. This is known as being a persistent data structure. Likewise custom types you make (the equivalent of say classes in an OO language) usually are implemented as referring to their subcomponents by reference rather than value (since everything is immutable the two are impossible to distinguish at the language level) and so your custom data structures also get this for free. The only common immutable data structure that does not have a persistent/data sharing implementation is an immutable array (as opposed to a linked list or a tree structure approximating an array), since arrays must be contiguous sequences of memory and therefore parts cannot be shared (unless you count array slicing, where the new array is always a subarray). However, unless you have strict performance requirements, you generally can get away with not using an array, since arrays generally only offer performance benefits if they are primitive arrays (rather than arrays of pointers to other objects). And if you do need a mutable array, there are ways of ensuring that its mutability doesn't spread out and "infect" other components of your system. Generally speaking, yes the main loop ultimately is a tail recursive function. This does not turn out to be much of a constraint. This is because main generally returns Unit (what is called void in e.g. C or Java), and so generally does not make sense to put anywhere except in a tail position since it does not return a useful value to be used elsewhere.
- thaumasiotes 7y agohttps://www.amazon.com/Purely-Functional-Data-Structures-Okasaki/dp/0521663504 https://www.amazon.com/Purely-Functional-Data-Structures-Oka...
- galaxyLogic 7y agoI see. So, let's say I have a tree with arbitrary number of nodes branching into further nodes and so on. (My 'state') Can I now make a single call to some API-function that gives me an almost identical (immutable) tree as a result except that some leaf somewhere has a modified value, or where some node has been deleted? And, would this internally happen without much copying?
- dwohnitmok 7y agoThe answer to all your questions is all "yes" technically, but the way you've worded them makes me think just saying "yes" will be a bit misleading to you. So let's take a real-world example. I'll use pseudo-Scala syntax since that's fairly similar to a lot of imperative languages. Let's construct a tree data structure from scratch and see how to update it. abstract class BinaryTreeOfInts final class Leaf(final value: Int) extends BinaryTreeOfInts final class Node(final value: Int, final left: BinaryTreeOfInts, final right: BinaryTreeOfInts) extends BinaryTreeOfInts All fields here are by reference. So because everything is final here, we've constructed an immutable tree. You cannot update any of these values once it's been constructed. Let's see how you might construct the following binary tree 5 / \ 6 1 / \ 2 3 myTree = Node( 5, Leaf(6) Node( 1, Leaf(2), Leaf(3) ) I want to modify 5 now to be 10 newTree = Node(10, myTree.left, myTree.right) Done. This is effectively one operation. newTree and myTree are both still immutable, but there's a ton of sharing going on. Now this is the best case scenario. Modifying a leaf is the worst case scenario, but even there only requires as many operations as there are levels in your tree, which, as long as you have a fairly balanced tree, only grows logarithmically in the number of elements you have. To illustrate let's make another change, this time changing 3 to 30. anotherTree = Node( newTree.value, newTree.left, Node( newTree.right.value, newTree.right.left, Leaf(30) ) ) Note I've had to instantiate a new Node for each level of the tree, and I've made a new Leaf, but otherwise everything else is shared. Now this is the low-level way of performing these changes. There are various APIs and tricks to make this look a lot cleaner in code so writing this out isn't as tedious, but I figured I'd present the low-level way to make things less magical. BTW, the logarithmic time factor to update a leaf is why you will often see logarithmic factors in time complexities of various operations on immutable data structures.