[Sorry about the wrong identations in the code. I am not sure how to fix it.]
Another approach is to use a "functional" "while do" (and "do .. while") loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
let funWhileDo (body : 'a -> 'a)(condition : 'a -> bool) (initial : 'a) = let s = Seq.initInfinite (fun i -> i) |> Seq.scan (fun s _ -> body s) initial |> Seq.takeWhile (fun x -> condition x) let init2 = s |> Seq.fold (fun _ state -> state) initial if (condition init2) then body init2 else init2 let funDoWhile (body : 'a -> 'a) (condition : 'a -> bool) (initial : 'a) = let init2 = body initial funWhileDo body condition init2
This approach encourages the programmer to be explicit about the state of the loop.
Then, BubbleSort can be defined as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
let bubblePass f (array : 'a []) (_, endix) = let mutable modified = false for i in 0 .. (endix-1) do let j = i+1 let ix = array.[ i ] let jx = array.[ j ] if f ix jx > 0 then array.[ i ] <- jx array.[ j ] <- ix modified <- true (modified, endix-1) let bubbleNotDone (modified, _) = (modified = true) let doBubbleSort array = let _ = funDoWhile (bubblePass (fun x y -> x-y) array) bubbleNotDone (false, array.Length-1) array
I found Sequences very expressive and powerful as well, but then also quite slow compared to normal tail recursive calls. A simple monte carlo option pricer can be very easily expressed using infinite sequences, and boy is it readable :). However, it was also an order of magnitude slower than a tail recursive implementation.
I found sequences and sequence expressions excellent for prototyping though.
Ninja edit: Obviously I should not be making claims like sequences are slow. They can be significantly faster in the right scenario (e.g. where you can save time with lazy initialization and caching already computed items).
seq {} is slow and if used incorrectly(using skip 1 to simulate head/tail), the slowdown is exponential.
LazyList in most case beats seq {} but lose the generator style(yield which can be very convenient) and is still noticeably slower than tail recursive call(which becomes loop, the fastest).
So F# is interesting in that while it should promote functional style programming, it penalize that form in terms of performance. I am not sure if it is the underlying VM(i.e. function calls and lambdas are slow).
In other words, function is first class object with a second class status in terms of performance.
I don't think it is the lambdas that are slow. In .NET 4, delegate call performance is right up there with callvirt. Afaik sequences are implemented as state machines, which means they are bound to be slower than simple function calls.
Nevertheless, I was also a bit disappointed that my delightful 4 line option pricer has to be "devolved" into a sprawling recursive function that is a lot less straightforward to read.
Nevertheless, I was also a bit disappointed that my delightful 4 line option pricer has to be "devolved" into a sprawling recursive function that is a lot less straightforward to read.
If you don't mind sharing, I'd be interested to see a short real-world example of this (both the slow elegant code and the fast ugly code), to have a real example to noodle on (in my copious free time, heh) for thinking of ways that we can keep a great programming model but improve the perf.
Brian,
I deleted the sequence based code since I had no use for it, but I tried to quickly rewrite it here. For some reason there is a tiny difference between the prices generated, but I can't immediately see where the problem is (it is definitely in CalculatePayoff2 though - PriceAsian2 works correctly). Nevertheless, the sequence based code is consistently an order of magnitude slower.
I know that the sequence based code is easier to parallelize, but then again, monte carlo methods are embarrassingly parallel. We need to price thousands of these options at a time, so we can just plop in the parallelism at the option level and cut down on overhead at the same time. I'm just saying this to clarify that there is little to gain in parallelizing the actual pricing algorithm.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
#light open System open System.Diagnostics module Main = // Pricing input type PricingInput = { Spot : float Strike : float TimeStep : float UpStepSize : float DownStepSize : float UpMovementProb : float Interest : float Expiry : float Steps : int32 Trials : int32 Seed : int } (********************************************) (*** Somewhat hard to read recursive code ***) (********************************************) let CalculatePayoff (input:PricingInput) (random:Random) = let rec EvaluatePath spot total step = match step with | 0 -> (total + spot) | _ -> match random.NextDouble() with | x when x < input.UpMovementProb -> EvaluatePath (spot * input.UpStepSize) (total + spot) (step - 1) | _ -> EvaluatePath (spot * input.DownStepSize) (total + spot) (step - 1) let averageSpot = (EvaluatePath input.Spot 0.0 (input.Steps-1))/float(input.Steps) Math.Max(averageSpot - input.Strike, 0.0) let PriceAsian (input:PricingInput) = let random = new Random(input.Seed) let rec payoffSampler total trial = match trial with | 0 -> total | _ -> payoffSampler (total + (CalculatePayoff input random)) (trial - 1) let payoffSum = payoffSampler 0.0 input.Trials payoffSum/float input.Trials (************************************) (*** The nice sequence based code ***) (************************************) let CalculatePayoff2 (input:PricingInput) (random:Random) = let GetPayoff average = Math.Max(average - input.Strike, 0.0) input.Spot |> Seq.unfold (fun spot -> match random.NextDouble() with | x when x < input.UpMovementProb -> Some(spot, spot * input.UpStepSize) | _ -> Some(spot, spot * input.DownStepSize)) |> Seq.take (input.Steps) |> Seq.average |> GetPayoff let PriceAsian2 (input:PricingInput) = let random = new Random(input.Seed) let GetAveragePrice total = total/float input.Trials Seq.initInfinite (fun i -> CalculatePayoff2 input random) |> Seq.take input.Trials |> Seq.fold (fun price total -> total+price) 0.0 |> GetAveragePrice // Set up pricing parameters let steps = 100 let expiry = 0.08 let volatility = 0.15 let interest = 0.04 let timeStep = expiry/float steps let variance = volatility * Math.Sqrt(timeStep) let upSize = Math.Exp(variance) let downSize = Math.Exp(-variance) let growth = Math.Exp(interest*timeStep) let upMovementProb = (growth - downSize)/(upSize - downSize) let input = { Spot = 50.0; Strike = 50.0; TimeStep = timeStep; UpStepSize = upSize; DownStepSize = downSize; UpMovementProb = upMovementProb; Interest = interest; Expiry = expiry; Steps = steps; Trials = 250000; Seed = 1000} let GetPreciseMilisecs (watch:Stopwatch) = float(watch.ElapsedTicks)/float(Stopwatch.Frequency)*1000.0; let rec DoWhile (watch:Stopwatch) = watch.Start() let price = PriceAsian input let discountedPrice = price * Math.Exp(-interest*expiry) watch.Stop() printfn "Single-Pricer(recursive)>>Price is %f calculated in %f miliseconds" discountedPrice (GetPreciseMilisecs watch) watch.Reset() watch.Start() let price = PriceAsian2 input let discountedPrice = price * Math.Exp(-interest*expiry) watch.Stop() printfn "Single-Pricer(sequence)>>Price is %f calculated in %f miliseconds" discountedPrice (GetPreciseMilisecs watch) watch.Reset() let choice = Console.ReadKey().KeyChar match choice with | 'q' -> () | _ -> DoWhile watch printfn "Press 'q' to quit or any other key to re-run the calculation" DoWhile (new Stopwatch())
Nevertheless, the sequence based code is consistently an order of magnitude slower.
Ah, yes, when all you're doing is floating-point arithmetic in a tight loop, then any allocation kills you (once you say 'new' or 'seq' or anything that allocates, you've lost compared to solutions that don't allocate).
I suppose the only thing I have to offer is that it may be useful to capture common abstractions into useful efficient helpers. For example, the "unfold... take... average" bit of CalculatePayoff2 seems like it might perhaps be a common pattern that you might factor into "AverageN" as shown here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/// Compute the average of [x, f(x), f(f(x)), ...(n times)...] let AverageN f x n = let mutable s = x let mutable total = 0.0 for i in 0..n-1 do total <- total + s s <- f s total/float n let CalculatePayoff2 (input:PricingInput) (random:Random) = let GetPayoff average = Math.Max(average - input.Strike, 0.0) AverageN (fun spot -> match random.NextDouble() with | x when x < input.UpMovementProb -> spot * input.UpStepSize | _ -> spot * input.DownStepSize ) input.Spot input.Steps |> GetPayoff
and maybe AverageN can be reused in other contexts. I guess there are lots of algorithms that are elegantly structured as "create a (possibly infinite) sequence, apply some function, (truncate to N elements), fold to yield a result" and the way to hand-optimize it is always to do loop fusion so that the "actual sequence" disappears: "unfold...blahblah...fold" reduces to a loop and an accumulator or two. To keep the code elegant, I guess you just need to recognize the most common idioms/patterns of "unfold...blahblah...fold" and encapsulate them in efficient functions with good names. No silver bullets here. :)
The recursive implementations run at about the same speed because the F# compiler actually compiles them into imperative loops. There are two issues which prevent the recursive implementation technique from being a general replacement for imperative loops: 1) the loop body is translated into a separate function so that you incur the overhead of passing all explicit and implicit arguments to the function every time the loop is entered and 2) you can't reference mutable variable from inside the loops. However, I suppose a future version of the F# compiler might inline the body of local loop functions into the caller, which would eliminate the calling overhead and would also make tuple unpacking optimizations applicable to the return value, so that you could replace multiple mutable variables with a single tuple return value.
Topic tags
- f# × 3705
- websharper × 1897
- compiler × 286
- functional × 201
- ui next × 139
- c# × 121
- classes × 97
- web × 97
- .net × 84
- book × 84
- async × 76
- ui.next × 67
- bug × 54
- core × 49
- website × 49
- server × 45
- parallel × 43
- ui × 43
- enhancement × 41
- parsing × 41
- testing × 41
- trywebsharper × 41
- typescript × 37
- html × 35
- javascript × 35
- owin × 35
- asynchronous × 30
- monad × 28
- ocaml × 28
- tutorial × 27
- warp × 27
- haskell × 26
- sitelet × 25
- linq × 22
- workflows × 22
- wpf × 20
- fpish × 19
- introduction × 19
- silverlight × 19
- sitelets × 19
- monodevelop × 17
- rpc × 17
- suave × 17
- piglets × 16
- collections × 15
- feature request × 15
- jquery × 15
- templates × 15
- getting started × 14
- pipeline × 14
- kendoui × 13
- reactive × 12
- 4.1.0.171 × 11
- monads × 11
- opinion × 10
- 4.0.190.100-rc × 9
- deployment × 9
- fixed × 9
- formlets × 9
- in × 9
- json × 9
- plugin × 9
- proposal × 9
- scheme × 9
- solid × 9
- basics × 8
- concurrent × 8
- highcharts × 8
- how-to × 8
- python × 8
- 4.1.1.175 × 7
- complexity × 7
- documentation × 7
- visual studio × 7
- 4.1.2.178 × 6
- lisp × 6
- real-world × 6
- released in 4.0.192.103-rc × 6
- remoting × 6
- resources × 6
- scala × 6
- websharper ui.next × 6
- workshop × 6
- xaml × 6
- 4.0.193.110 × 5
- 4.2.3.236 × 5
- aspnetmvc × 5
- authentication × 5
- azure × 5
- bootstrap × 5
- conference × 5
- dsl × 5
- formlet × 5
- java × 5
- list × 5
- metaprogramming × 5
- ml × 5
- released in Zafir.4.0.188.91-beta10 × 5
- sql × 5
- visualstudio × 5
- websharper.forms × 5
- zafir × 5
- 4.0.192.106 × 4
- 4.0.195.127 × 4
- 4.1.0.38 × 4
- 4.2.1.86 × 4
- 4.2.6.118 × 4
- css × 4
- example × 4
- extensions × 4
- fsi × 4
- fsx × 4
- html5 × 4
- jqueryui × 4
- lift × 4
- reflection × 4
- remote × 4
- rest × 4
- spa × 4
- teaching × 4
- template × 4
- websocket × 4
- wontfix × 4
- 4.0.196.147 × 3
- 4.1.0.34 × 3
- 4.1.6.207 × 3
- 4.2.1.223-beta × 3
- 4.2.11.258 × 3
- 4.2.4.114 × 3
- 4.2.4.247 × 3
- 4.2.5.115 × 3
- 4.2.6.253 × 3
- 4.2.9.256 × 3
- ajax × 3
- alt.net × 3
- aml × 3
- asp.net mvc × 3
- canvas × 3
- cloudsharper × 3
- compilation × 3
- database × 3
- erlang × 3
- events × 3
- extension × 3
- file upload × 3
- forums × 3
- inline × 3
- issue × 3
- kendo × 3
- macro × 3
- mono × 3
- msbuild × 3
- mvc × 3
- pattern × 3
- piglet × 3
- released in Zafir.4.0.187.90-beta10 × 3
- svg × 3
- type provider × 3
- view × 3
- 4.1.1.64 × 2
- 4.1.5.203 × 2
- 4.1.7.232 × 2
- 4.2.10.257 × 2
- 4.2.3.111 × 2
- 4.2.5.249 × 2
- android × 2
- asp.net × 2
- beginner × 2
- blog × 2
- chart × 2
- client × 2
- client server app × 2
- clojure × 2
- computation expressions × 2
- constructor × 2
- corporate × 2
- courses × 2
- cufp × 2
- d3 × 2
- debugging × 2
- direct × 2
- discriminated union × 2
- docs × 2
- elm × 2
- endpoint × 2
- endpoints × 2
- enterprise × 2
- entity framework × 2
- event × 2
- f# interactive × 2
- fable × 2
- flowlet × 2
- formdata × 2
- forms × 2
- fsc × 2
- google maps × 2
- hosting × 2
- http × 2
- https × 2
- iis 8.0 × 2
- install × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- jquery mobile × 2
- keynote × 2
- lens × 2
- lenses × 2
- linux × 2
- listmodel × 2
- mac × 2
- numeric × 2
- oauth × 2
- obfuscation × 2
- offline × 2
- oop × 2
- osx × 2
- packaging × 2
- pattern matching × 2
- performance × 2
- pipelines × 2
- q&a × 2
- quotation × 2
- reference × 2
- released in Zafir.4.0.185.88-beta10 × 2
- rx × 2
- script × 2
- security × 2
- self host × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- tutorials × 2
- type × 2
- url × 2
- var × 2
- websharper.charting × 2
- websharper4 × 2
- websockets × 2
- wig × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- 4.0.194.126 × 1
- 4.1.3.184 × 1
- 4.1.4.189 × 1
- 4.2.0.214-beta × 1
- 4.2.12.259 × 1
- 4.2.2.231-beta × 1
- 4.2.8.255 × 1
- Canvas Sample Example × 1
- DynamicStyle Animated Style × 1
- Fixed in 4.0.190.100-rc × 1
- Released in Zafir.UI.Next.4.0.169.79-beta10 × 1
- SvgDynamicAttribute × 1
- WebComponent × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- actor × 1
- addin × 1
- agents × 1
- aggregation × 1
- agile × 1
- alter session × 1
- animation × 1
- anonymous object × 1
- apache × 1
- api × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net core × 1
- asp.net integration × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- attributes × 1
- authorization × 1
- b-tree × 1
- back button × 1
- badimageformatexception × 1
- bash script × 1
- batching × 1
- binding-vars × 1
- bistro × 1
- body × 1
- bundle × 1
- camtasia studio × 1
- cas protocol × 1
- charts × 1
- clarity × 1
- class × 1
- cli × 1
- clipboard × 1
- clojurescript × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- color zones × 1
- combinator × 1
- combinators × 1
- compile × 1
- compile code on server × 1
- config × 1
- confirm × 1
- content × 1
- context × 1
- context.usersession × 1
- continuation-passing style × 1
- coords × 1
- cordova × 1
- cors × 1
- coursera × 1
- cross-domain × 1
- csla × 1
- current_schema × 1
- custom content × 1
- data × 1
- data grid × 1
- datetime × 1
- debug × 1
- declarative × 1
- delete × 1
- devexpress × 1
- dhtmlx × 1
- dictionary × 1
- directattribute × 1
- disqus × 1
- distance × 1
- do binding × 1
- doc elt ui.next upgrade × 1
- docker × 1
- dojo × 1
- dol × 1
- dom × 1
- domain × 1
- du × 1
- duf-101 × 1
- dynamic × 1
- eastern language × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- enums × 1
- error × 1
- etw × 1
- euclidean × 1
- eventhandlerlist × 1
- examples × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- failed to translate × 1
- fake × 1
- fantomas × 1
- fear × 1
- float × 1
- form × 1
- form-data × 1
- forum × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- fsunit × 1
- function × 1
- functional style × 1
- game × 1
- games × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- getting-started × 1
- google × 1
- google.maps × 1
- grid × 1
- group × 1
- guide × 1
- hash × 1
- headers × 1
- hello world example × 1
- heroku × 1
- highchart × 1
- history × 1
- how to × 1
- html-templating × 1
- http405 × 1
- httpcontext × 1
- hubfs × 1
- i18n × 1
- ie 8 × 1
- if-doc × 1
- iis × 1
- image × 1
- images × 1
- inheritance × 1
- initialize × 1
- input × 1
- install "visual studio" × 1
- installer × 1
- int64 × 1
- interfaces × 1
- internet explorer × 1
- interop × 1
- interpreter × 1
- io × 1
- iobservable × 1
- ios × 1
- iot × 1
- ipad × 1
- isomorphic × 1
- javascript optimization × 1
- javascript semanticui resources × 1
- jquery-plugin × 1
- jquery-ui × 1
- jquery-ui-datepicker × 1
- js × 1
- kendo datasource × 1
- kendochart × 1
- kendoui compiler × 1
- knockout × 1
- l10n × 1
- learning × 1
- library × 1
- libs × 1
- license × 1
- licensing × 1
- lineserieszonescfg × 1
- local setting × 1
- localization × 1
- logging × 1
- loop × 1
- macros × 1
- mailboxprocessor × 1
- mapping × 1
- maps × 1
- markerclusterer × 1
- markup × 1
- marshal × 1
- math × 1
- mathjax × 1
- message × 1
- message passing × 1
- message-passing × 1
- meta × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- mix × 1
- mobile installation × 1
- mod_mono × 1
- modal × 1
- module × 1
- mouseevent × 1
- mouseposition × 1
- multidimensional × 1
- multiline × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- native × 1
- nested × 1
- nested loops × 1
- node × 1
- nunit × 1
- object relation mapper × 1
- object-oriented × 1
- om × 1
- onboarding × 1
- onclick × 1
- optimization × 1
- option × 1
- orm × 1
- os x × 1
- output-path × 1
- override × 1
- paper × 1
- parameter × 1
- persistence × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- post × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- privacy × 1
- private × 1
- profile × 1
- programming × 1
- project × 1
- project euler × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- proxy × 1
- ptvs × 1
- public × 1
- pure f# × 1
- purescript × 1
- qna × 1
- quant × 1
- query sitelet × 1
- question × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- reactjs × 1
- real-time × 1
- ref × 1
- region × 1
- released in 4.0.190.100-rc × 1
- reporting × 1
- responsive design × 1
- rest api × 1
- rest sitelet × 1
- restful × 1
- round table × 1
- router × 1
- routing × 1
- rpc reverseproxy × 1
- runtime × 1
- sales × 1
- sample × 1
- sampleapp × 1
- scriptcs × 1
- scripting × 1
- search × 1
- self hosted × 1
- semanticui × 1
- sequence × 1
- serialisation × 1
- service × 1
- session-state × 1
- sharepoint × 1
- signals × 1
- sitelet website × 1
- sitelet.protect × 1
- sitlets × 1
- slickgrid × 1
- source code × 1
- sqlentityconnection × 1
- ssl × 1
- standards × 1
- static content × 1
- stickynotes × 1
- streamreader × 1
- stress × 1
- strong name × 1
- structures × 1
- submitbutton × 1
- subscribe × 1
- svg example html5 websharper.ui.next × 1
- sweetalert × 1
- system.datetime × 1
- system.reflection.targetinvocationexception × 1
- table storage × 1
- targets × 1
- tdd × 1
- templates ui.next × 1
- templating × 1
- text parsing × 1
- three.js × 1
- time travel × 1
- tls × 1
- tooltip × 1
- tracing × 1
- tsunamiide × 1
- turkish × 1
- twitter-bootstrap × 1
- type erasure × 1
- type inference × 1
- type providers × 1
- type-providers × 1
- typeprovider × 1
- ui next forms × 1
- ui-next × 1
- ui.next jqueryui × 1
- ui.next charting × 1
- ui.next formlets × 1
- ui.next forms × 1
- ui.next suave visualstudio × 1
- ui.next templating × 1
- unicode × 1
- unittest client × 1
- upload × 1
- usersession × 1
- validation × 1
- vb × 1
- vb.net × 1
- vector × 1
- view.map × 1
- visal studio × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio 2012 × 1
- visual studio shell × 1
- vs2017 compiler zafir × 1
- vsix × 1
- web api × 1
- web-scraping × 1
- webapi × 1
- webcomponents × 1
- webforms × 1
- webgl × 1
- webrtc × 1
- webshaper × 1
- websharper async × 1
- websharper codemirror × 1
- websharper f# google × 1
- websharper forms × 1
- websharper reactive × 1
- websharper rpc × 1
- websharper sitelets routing × 1
- websharper warp × 1
- websharper-interface-generator × 1
- websharper.chartsjs × 1
- websharper.com × 1
- websharper.exe × 1
- websharper.owin × 1
- websharper.ui.next × 1
- websharper.ui.next jquery × 1
- websockets iis × 1
- why-websharper × 1
- windows 7 × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- www.grabbitmedia.com × 1
- xamarin × 1
- xml × 1
- yeoman × 1
- yield × 1
- zafir beta × 1
- zafir websharper4 × 1
- zarovizsga × 1
![]() |
Copyright (c) 2011-2012 IntelliFactory. All rights reserved. Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy |
Built with WebSharper |
Was playing around with sort algorithms in F# this weekend to kill some time and again lamented the lack of repeat/until and break in F#. Then it hit me that recursive tail call functions could achieve exactly the same thing, with only a slight ding to readability. So, I threw out 'mutable bDone' and 'while not bDone' and tried writing the code without these imperative constructs. The following distills out just the looping construct and shows how to write repeat/until, do/while, while/do, break/continue, and test-in-the-middle style code using tailcalls. These all appear to run at exactly the same speed as a traditional F# 'while' statement, but your mileage may vary (some platforms may not properly implement tailcall). At the end is a (bad) sort algorithm written in both styles, for comparison.
Let's start with a 'do/while' loop, written in traditional F# imperative style, then look at functional variations which provide both the same type of loop, as well as different semantics like while/do, repeat/until, test in the middle, and even break/continue (without monads.. um, workflows!).
Ok, that's easy enough. Keep in mind that f() is always called at least once (do/while).
Here is the same code, but in a functional style. Note that we don't need to declare a mutable here.
We can spin that to a traditional do/while by putting the function call inside the if block.
How about repeating a block until some condition is true (repeat/until)? Easy enough...
What was that about a monad-less break? Well, just introduce a conditional expression which returns 'unit', as in:
How about continue? Well, that's just another call to loop! First, with a syntax crutch:
And then again without the (ugly) syntax crutch:
Easy as pie!
One nice result of these loop forms is that it makes it easier to spot and implement states in your loops. For example, a bubble sort continually loops over an entire array, swapping values that are out of place as it finds them. It keeps track of whether a pass over the array produced any exchanges. If not, then every value must be in the right place, so the sort can terminate. As an optimization, on every pass thru the array, the last value in the array ends up sorted into the correct place. So, the loop can be shortened by one each time through. Most algorithms check for a swap and update a "bModified" flag every time there is one. However, once the first swap is done, there is no need for that assignment; it's already set to true!
Here is F# code which implements a bubble sort (yes, bubble sort is terrible algorithm; quicksort rocks). At the end is an imperative implementation which does not change state; it updates the bModified flag for every exchange. Interestingly, the imperative solution is faster on tiny arrays and just a percent or two slower on large ones. (Made for a good example, though).