Hi,
First off, I believe your bstree_add is NOT tail recursive, regardless of laziness. Consider your bstree_add with laziness removed:
In the HERE line you do not immediately return a result of a recursive call from bstree_add, but use it to create a new Node object. This means that the stack frame should still be around after the return from the recursive call (bstree_add (R.Force()) n).
>compiler creates a closure around the lazy (expr) which to my limited
understanding means the stack frame
> is still left in place, even though
a value is returned immediately.
I think you are rather confused here. Compare the following two functions (I distill lazy to closures here):
To evaluate f <something>, you need to call actually call g, get its value and then add x to it. To do that, you need to keep stack frame from f on stack while you call g to be able to do the addition.
OTOH, when f' <something> is evaluated, no actual call to g is taking place. All that happens is that a closure is created in heap that contains a value of x (and an entry point for code evaluating g x + x).
One way to see this is f' is a version of f that allocates its stack on heap.
Now, lazy expr is essentially Lazy.create
Hope this helps,
Friendly,
Dmitry
First off, I believe your bstree_add is NOT tail recursive, regardless of laziness. Consider your bstree_add with laziness removed:
let rec bstree_add tree n = match tree with ... | Node(L,x,R) -> ... Node(L, x, (bstree_add ...)) // HERE
In the HERE line you do not immediately return a result of a recursive call from bstree_add, but use it to create a new Node object. This means that the stack frame should still be around after the return from the recursive call (bstree_add (R.Force()) n).
>compiler creates a closure around the lazy (expr) which to my limited
understanding means the stack frame
> is still left in place, even though
a value is returned immediately.
I think you are rather confused here. Compare the following two functions (I distill lazy to closures here):
let f x = g x + x let f' x = (fun () -> g x + x)
To evaluate f <something>, you need to call actually call g, get its value and then add x to it. To do that, you need to keep stack frame from f on stack while you call g to be able to do the addition.
OTOH, when f' <something> is evaluated, no actual call to g is taking place. All that happens is that a closure is created in heap that contains a value of x (and an entry point for code evaluating g x + x).
One way to see this is f' is a version of f that allocates its stack on heap.
Now, lazy expr is essentially Lazy.create
(fun () -> expr). So returning to your lazy bstree_add: it is NOT tail recursive, but it essentially evaluates in O(1) stack space. When you wrap up your recursive calls in 'lazy ...' you create a closure, and in doing so, allocate bstree_add's stack on heap.Hope this helps,
Friendly,
Dmitry
Yes it does. I appologize for seeming confused I was juggling a few things at the time I authored that post. However, when I said the bstree_Add was technically tail recursive I was using the term VERY loosely. What I meat was the resulting closure created by lazy(expr) would lead to the function operating in O(1) space(like you had said).
Your answer does force me to ask the following question. Does the compiled/jitted code reuse the space on the heap created by the closure a function like my bstree_add creates? Or does the next recursive call create a new closure on the heap ad leave the old one to be garbage collected?
Your answer does force me to ask the following question. Does the compiled/jitted code reuse the space on the heap created by the closure a function like my bstree_add creates? Or does the next recursive call create a new closure on the heap ad leave the old one to be garbage collected?
> Does the compiled/jitted code reuse the space on the heap created by
the closure a function like my bstree_add creates? Or does the next
recursive call create a new closure on the heap ad leave the old one to
be garbage collected?
It creates a new closure, leaving the old one to be garbage collected (closures in F# are essentialy immutable instances of inheritors of FastFunc base class)
Actually creating a closure (= allocating a block in Gen0) is fairly cheap (on par with stack allocation actually).
As a matter of fact, in generational GC, allocating a new object may even be cheaper that modifying an old object - if the modification adds a "wrong way" inter-generational reference.
Hope this helps,
Friendly,
Dmitry
The original code is interesting - I think it's a very good piece of code for a first venture into F# functional coding.
The question you ask is also very interesting. Avoiding stack overflows when you consume the tree looks like it might require the use of continuations. Some of this is covered in Expert F# chapter 8.
However, it won't be so common for trees to get enomously deep if you keep them balanced.
One fun and helpful reference point in this domain is Wadler's classic on adding laziness to a strict language: [link:citeseer.ist.psu.edu]
Kind regards
don
The question you ask is also very interesting. Avoiding stack overflows when you consume the tree looks like it might require the use of continuations. Some of this is covered in Expert F# chapter 8.
However, it won't be so common for trees to get enomously deep if you keep them balanced.
One fun and helpful reference point in this domain is Wadler's classic on adding laziness to a strict language: [link:citeseer.ist.psu.edu]
Kind regards
don
Topic tags
- f# × 3660
- compiler × 263
- functional × 199
- c# × 119
- websharper × 114
- classes × 96
- web × 94
- book × 84
- .net × 82
- async × 72
- parallel × 43
- server × 43
- parsing × 41
- testing × 41
- asynchronous × 30
- monad × 28
- ocaml × 26
- tutorial × 26
- haskell × 25
- workflows × 22
- html × 21
- linq × 21
- introduction × 19
- silverlight × 19
- wpf × 19
- fpish × 18
- collections × 14
- pipeline × 14
- templates × 12
- monads × 11
- opinion × 10
- reactive × 10
- plugin × 9
- scheme × 9
- sitelets × 9
- solid × 9
- basics × 8
- concurrent × 8
- deployment × 8
- how-to × 8
- python × 8
- complexity × 7
- javascript × 6
- jquery × 6
- lisp × 6
- real-world × 6
- workshop × 6
- xaml × 6
- conference × 5
- dsl × 5
- java × 5
- metaprogramming × 5
- ml × 5
- scala × 5
- visual studio × 5
- formlets × 4
- fsi × 4
- lift × 4
- sql × 4
- teaching × 4
- alt.net × 3
- aml × 3
- enhancement × 3
- list × 3
- reflection × 3
- blog × 2
- compilation × 2
- computation expressions × 2
- corporate × 2
- courses × 2
- cufp × 2
- enterprise × 2
- entity framework × 2
- erlang × 2
- events × 2
- f# interactive × 2
- fsc × 2
- google maps × 2
- html5 × 2
- http × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- keynote × 2
- mvc × 2
- numeric × 2
- obfuscation × 2
- oop × 2
- packaging × 2
- pattern matching × 2
- pipelines × 2
- rx × 2
- script × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- type × 2
- type provider × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- addin × 1
- agents × 1
- agile × 1
- android × 1
- anonymous object × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net mvc × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- b-tree × 1
- bistro × 1
- bug × 1
- camtasia studio × 1
- canvas × 1
- class × 1
- client × 1
- clojure × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- combinator × 1
- confirm × 1
- constructor × 1
- continuation-passing style × 1
- coords × 1
- coursera × 1
- csla × 1
- css × 1
- data × 1
- database × 1
- declarative × 1
- delete × 1
- dhtmlx × 1
- discriminated union × 1
- distance × 1
- docs × 1
- documentation × 1
- dol × 1
- domain × 1
- du × 1
- duf-101 × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- error × 1
- etw × 1
- euclidean × 1
- event × 1
- example × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- fantomas × 1
- fear × 1
- float × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- function × 1
- functional style × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- google × 1
- group × 1
- hash × 1
- history × 1
- hosting × 1
- httpcontext × 1
- https × 1
- hubfs × 1
- ie 8 × 1
- if-doc × 1
- inheritance × 1
- installer × 1
- interpreter × 1
- io × 1
- ios × 1
- ipad × 1
- kendo × 1
- learning × 1
- licensing × 1
- macro × 1
- macros × 1
- maps × 1
- markup × 1
- marshal × 1
- math × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- multidimensional × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- nested × 1
- nested loops × 1
- node × 1
- object relation mapper × 1
- object-oriented × 1
- offline × 1
- option × 1
- orm × 1
- osx × 1
- owin × 1
- paper × 1
- parameter × 1
- performance × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- programming × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- ptvs × 1
- quant × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- real-time × 1
- reference × 1
- restful × 1
- round table × 1
- runtime × 1
- scriptcs × 1
- scripting × 1
- service × 1
- session-state × 1
- sitelet × 1
- stickynotes × 1
- stress × 1
- strong name × 1
- structures × 1
- tdd × 1
- template × 1
- tracing × 1
- tsunamiide × 1
- type inference × 1
- type providers × 1
- upload × 1
- vb × 1
- vb.net × 1
- vector × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio shell × 1
- visualstudio × 1
- web api × 1
- webapi × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- xml × 1
|
Copyright (c) 2011-2012 IntelliFactory. All rights reserved. Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us |
Built with WebSharper |
type BSTree = |Empty |Leaf of int |Node of BSTree Lazy * int * BSTree Lazy let rec bstree_add tree n = match tree with |Empty -> Leaf(n) |Leaf(x) -> if n < x then Node(lazy(Leaf(x)),x,lazy(Empty)) else Node(lazy(Empty),x,lazy(Leaf(x))) |Node(L,x,R) -> if n < x then Node(lazy(bstree_add (L.Force()) n), x, R) else Node(L, x, lazy(bstree_add (R.Force()) n))I have the basics of what would be needed worked out and after stairing at my white board for a few minutes I wondered what would happen to the function stack with the above definition if I looped through and built up a lazy BSTree from the numbers 1 ... 1000.
Technically the bstree_add function is not tail recursive but the recursive call is set to be lazily evaluated. Would I still get a stack overflow if I tried to build a BSTree from the stated definition with 100000 sorted numbers? If not, why?
I am rather conflicted over this question because I beleive the compiler creates a closure around the lazy (expr) which to my limited understanding means the stack frame is still left in place, even though a value is returned immediately.