Why are variables allowed in F #? When are they needed?

Coming from C #, trying to plunge into the language.

From what I understand, one of the main advantages of F # is that you give up the concept of state, which should (in many cases) make things much more reliable.

If so (and correct me if it is not), why can we violate this principle with variables? It seems to me that they do not belong to the language. I understand that you do not need to use them, but it gives you the tools to go the road and think about the OOP method.

Can someone provide an example of where the change in meaning is important?

+6
source share
3 answers

Current compilers for declarative (stateless) code are not very smart. This leads to a large number of memory allocations and copy operations, which are quite expensive. Mutation of some property of an object allows reusing the object in its new state, which is much faster.

Imagine you are making a game , with 10,000 units moving at a speed of 60 ticks per second. You can do this in F #, including collisions with mutable quad or octree, on the same processor core.

Now imagine that units and quadrants are immutable. The compiler would have no idea how to allocate and build 600,000 units per second and create 60 new trees per second. And this excludes any changes in other management structures. In the real case with complex units, such a solution would be too slow.

F # is a language with several paradigms that allows a programmer to write functional, object-oriented and, in fact, imperative programs. Currently, each option has its own valid application. Perhaps at some point in the future, better compilers will improve the optimization of declarative programs, but right now we should return to imperative programming when performance becomes a problem.

+8
source

The ability to use a volatile state is often important, including for performance reasons.

Consider the implementation of the List.take: count : int -> list : 'a list -> 'a list API List.take: count : int -> list : 'a list -> 'a list , which returns a list consisting only of the first count elements from the input list.

If you are connected by immutability, lists can only be created on the reverse side. The take implementation then comes down to

  • Create a list of results back to the beginning with the first count guys from the input: O (count)
  • Inverse result and return O (count)

F # runtime for performance reasons has a magical special ability to create back and forth lists if necessary (i.e. to change the tail of the last guy to point to a new tail element). The main algorithm used for List.take is:

  • Create a list of front-to-back results using the first count guys from the input: O (count)
  • Return result

The same asymptotic performance, but in practice it uses the mutation twice as fast in this case.

An all-pervasive volatile state can be a nightmare, as it is difficult to understand the cause. But if you define your code so that the volatile state is tightly encapsulated (for example, in the details of the List.take implementation), then you can take advantage of it where it makes sense. Thus, immutability by default, but still allowing mutability, is a very practical and useful language feature.

+7
source

First of all, what makes F # powerful, in my opinion, is not just the default immutability, but a whole set of functions such as: default immutability, type inference, easy syntax, sum (DU) and product types (tuples) , pattern matching and currying by default. Perhaps more.

They make F # very functional by default, and they make your program a certain way. In particular, they make you uncomfortable when you use a mutable state, as this requires the mutable keyword. Inconvenient in this sense means more careful. And that’s exactly what you should be.

A volatile state is not forbidden or evil per se , but it must be controlled . The need to explicitly use mutable is like a warning sign warning you of danger. And good ways to manage it use it internally within a function. This way you can have your own internal mutable state and still be completely thread safe because you don't have a shared mutable state . In fact, your function may still be transparent, even if it uses the changed state internally.

As to why F # allows a volatile state; it would be very difficult to write regular real-world code without the ability for it. For example, in Haskell, something like a random number cannot be done in the same way as it can be done in F #, but rather requires streaming through the state explicitly.

When I write applications, I usually have about 95% of the code base in a very functional style that would be pretty much 1: 1 portable to say Haskell without any problems. But then a critical state is used at the boundaries of the system or in some critical critical internal circuit. Thus, you get the best of both worlds.

+6
source

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


All Articles