6

I have two questions, stemming from observed behavior of C# static methods (which I may be misinterpretting):

First: Would a recursive static method be tail call optimized in a sense by the way the static method is implemented under the covers?

Second: Would it be equivalent to functional programming to write an entire application with static methods and no variables beyond local scope? I am wondering because I still haven't wrapped my head around this "no side effects" term I keep hearing about functional programming..

Edit: Let me mention, I do use and understand why and when to use static methods in the normal C# OO methodology, and I do understand tail call optimization will not be explicitly done to a recursive static method. That said, I understand tail call optimization to be an attempt at stopping the creation of a new stack frame with each pass, and I had at a couple points observed what appeared to be a static method executing within the frame of it's calling method, though I may have misinterpreted my observation.

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
Jimmy Hoffa
  • 5,909
  • 30
  • 53
  • 6
    No, writing only static methods in C# isn't functional programming. It's a gross misuse of a multi-paradigm, primarily object-oriented, language. –  Aug 01 '11 at 13:45
  • 5
    @delnan: I wasn't suggesting I do it, I'm trying to understand the meaning of "no side effects" and if that would be achieved in the manner I spoke of. I write plenty of happy slappy OO C# every day, I don't need a lecture on why an all static app would make no sense in C#. C# is just a frame of reference for me to understand other things. – Jimmy Hoffa Aug 01 '11 at 13:51
  • 2
    If you really want to wrap your head around the notion of programming with 'no side-effects', you should start experimenting with a pure functional programming language like Haskell. You can certainly program in a pure functional style in C#, but Haskell (unlike C#) strongly encourages this style of programming, as opposed to merely making it possible. – Daniel Pratt Aug 01 '11 at 14:00
  • What's the link between tail-recursion and the functional paradigm? – H H Aug 01 '11 at 14:00
  • Functional paradigm depends on tail recursion to eliminate the stack when doing recursive functions so that it never overflows. This is because functional languages tend not to have any local state so they do iteration via recursion and some iterations are unbound and greater than the theoretical maximum stack size on a machine. – Deleted Aug 01 '11 at 14:09
  • @Henk Functional programs typically use recursive functions to solve problems that are typically solved with loops in non-functional programs. Tail recursion allows a recursive function to be defined in a way that does not use more stack space for each recursive invocation. Although I'm a bit fuzzy on the details, I believe that tail recursion is not as relevant to lazy functional programming languages like Haskell. – Daniel Pratt Aug 01 '11 at 14:10
  • 3
    @Jimmy: If you have a static method, which takes an array as its argument and then sets the array's first element to 42, that's still a side-effect even though you did not involve any non-local variables. Also there is no reason why a method should be static to be free of side-effects. You can certainly write purely functional, side-effect free code, while still using OO concepts. – sepp2k Aug 01 '11 at 14:12
  • @sepp2k: that's an invalid case. you should copy the array and return the modified array. Static is easier to manage FP within as not all concepts are adequately encapsulated by objects (think the typical map function). – Deleted Aug 01 '11 at 14:14
  • 1
    @Chris: How is that invalid in the given context. All Jimmy said was static and no global variables. My method certainly meets those requirement, so I'm pretty sure it's a perfectly valid counter-example to the thesis that static and global-free necessarily means side-effect free. As for map, I don't see why it couldn't be an instance method on lists (it is in scala for example). – sepp2k Aug 01 '11 at 14:17
  • @sepp2k: good point! Any form of reference type in the params would allow for side effects. I'm starting to understand the definition of "side effects", something like state changes that are not directly induced at the local scope.. – Jimmy Hoffa Aug 01 '11 at 14:19
  • @sepp2k - that specialises map to lists. map can be applied to anything iterable. As for invalid case, types that are mutable should be copied and returned rather than modified in situ. Should have been more clear. – Deleted Aug 01 '11 at 14:27
  • I just edited [my answer](http://stackoverflow.com/questions/6899268/c-static-methods-underneath/6899542#6899542) to match your edit. Hope it is clear now. – Dan Abramov Aug 01 '11 at 14:33
  • @Chris: Fine, then define it as an instance method on iterable (and actually that's the case in Scala as well). And as for copying mutable types: The OP said nothing about that - that's a restriction you added. So while the statement "static methods that only use local variables and don't mutate mutable values without copying" might be perfectly true, that statement is not the topic of discussion. And it wouldn't be any less true if you remove the "static" from it. – sepp2k Aug 01 '11 at 15:01

4 Answers4

6

Would a recursive static method be tail call optimized in a sense by the way the static method is implemented under the covers?

Static methods have nothing to do with tail recursion optimization. All the rules equally apply to instance and static methods, but personally I would never rely on JIT optimizing away my tail calls. Moreover, C# compiler doesn't emit tail call instruction but sometimes it is performed anyway. In short, you never know.

F# compiler supports tail recursion optimization and, when possible, compiles recursion to loops.
See more details on C# vs F# behavior in this question.

Would it be equivalent to functional programming to write an entire application with static methods and no variables beyond local scope?

It's both no and yes.

Technically, nothing prevents you from calling Console.WriteLine from a static method (which is a static method itself!) which obviously has side-effects. Nothing also prevents you from writing a class (with instance methods) that does not change any state (i.e. instance methods don't access instance fields). However from the design point of view, such methods don't really make sense as instance methods, right?

If you Add an item to .NET Framework List<T> (which has side effects), you will modify its state.
If you append an item to an F# list, you will get another list, and the original will not be modified.

Note that append indeed is a static method on List module. Writing “transformation” methods in separate modules encourages side-effect free design, as no internal storage is available by definition, even if the language allows it (F# does, LISP doesn't). However nothing really prevents you from writing a side-effect free non-static method.

Finally, if you want to grok functional language concepts, use one! It's so much more natural to write F# modules that operate immutable F# data structures than imitate the same in C# with or without static methods.

Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • Thanks Dan, you seem to be giving the most comprehensive and question-oriented answer here. Also thanks for editing the question title, is more clear now. Wish I could just decide to write in F# our data processing code, but we're all C# here so oh well. Still curious about those times I saw static methods executing in their caller's frame. Maybe I was imagining things.. – Jimmy Hoffa Aug 01 '11 at 15:00
  • You can write perfectly sensible instance methods with no side effects that really *do* need to be instance methods. If they only *inspect* fields, rather than modify them, then they can be side-effect free. Loads of pure functional Scala code works exactly this way. "Side-effect-free" programming just means that the results of your calls depend only on their inputs, and specifically are **unaffected by which other calls have been made before-hand**. And as you say, you can easily write static methods that have side effects. Static-ness has very little to do with being side-effect-free. – Ben Aug 03 '11 at 02:00
3

The CLR does do some tail call optimisations but only in 64-bit CLR processes. See the following for where it is done: David Broman's CLR Profiling API Blog: Tail call JIT conditions.

As for building software with just static variables and local scope, I've done this a lot and it's actually fine. It's just another way of doing things that is as valid as OO is. In fact because there is no state outside the function/closure, it's safer and easier to test.

I read the entire SICP book from cover to cover first however: http://mitpress.mit.edu/sicp/

No side effects simply means that the function can be called with the same arguments as many times as you like and always return the same value. That simply defines that the result of the function is always consistent therefore does not depend on any external state. Due to this, it's trivial to parallelize the function, cache it, test it, modify it, decorate it etc.

However, a system without side effects is typically useless, so things that do IO will always have side effects. It allows you to neatly encapsulate everything else though which is the point.

Objects are not always the best way, despite what people say. In fact, if you've ever used a LISP variant, you will no doubt determine that typical OO does sometimes get in the way.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Deleted
  • 4,804
  • 1
  • 22
  • 17
  • So from your explanation, it would be programming with "no side effects" to have only static methods with only local scope variables? – Jimmy Hoffa Aug 01 '11 at 13:59
  • That is correct within the scope of the C# programming language. You can get some extra mileage with immutable types (types that always copy and return new instances on every operation) but they are not that common in .Net other than the string type. – Deleted Aug 01 '11 at 14:02
  • 1
    Just curious: If you're programming using just static variables and local scope, why use C# instead of a language more suited to functional programming, like F# or many non-CLR languages? – Matthew Aug 01 '11 at 14:21
  • 1
    @Matthew: I do (I use my own scheme implementation written in C) but unfortunately where I work mandates C# only, not even F#. – Deleted Aug 01 '11 at 14:26
  • @Chris: I would argue that side-effect programming is unnatural in C# and leads to code your coworkers will probably not appreciate. I would never do it for production code. – Dan Abramov Aug 01 '11 at 14:36
  • @Dan: it's used where deemed approprate rather than everywhere and is supported by several staff members. The use case is to maintain very high request concurrency levels using work queues without resorting to threading directly. – Deleted Aug 01 '11 at 15:11
  • @Chris: Oh. This makes perfect sense. – Dan Abramov Aug 01 '11 at 19:41
1

Regarding second question: I believe you mean "side effects" of mutable data structures, and obviously this is not a problem for (I believe) most functional languages. For instance, Haskel mostly (or even all!?) uses immutable data structures. So there is nothing about "static" behaviour.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
sll
  • 61,540
  • 22
  • 104
  • 156
1

There's a pretty good book written on this subject, http://www.amazon.com/Real-World-Functional-Programming-Examples/dp/1933988924.

And in the real world using F# unfortunately isn't an option due to team skills or existing codebases, which is another reason I do love this book, as it has shows many ways to implement F# features in the code you use day to day. And to me at least the vast reduction in state bugs, which take far longer to debug than simple logic errors, is worth the slight reduction in OOP orthodoxy.

For the most part having no static state and operating in a static method only on the parameters given will eliminate side-effects, as you're limiting yourself to pure functions. One point to watch out for though is retrieving data to be acted on or saving data to a database in such a function. Combining OOP and static methods, though, can help here, by having your static methods delegate to lower level objects commands to manipulate state.

Also a great help in enforcing function purity is to keep objects immutable whenever possible. Any object acted on should return a new modified instance, and the original copy discarded.

Mathieson
  • 11
  • 1