Nice effort!
You can save yourself 20 odd lines of code by creating the form in a scripting style rather than an OO style. I think there maybe some other places in the algorithm itself were you can save quite a few lines but that's not as easy to spot. It maybe a good exercise to go thought the code and just make it as short as possible, this is something of an obession of functional programmers. Anyway here's my effort:
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
#light // F# Mandelbrot Set Generator, 11.19.08, Matt Valerio open System.Drawing open System.Windows.Forms open Microsoft.FSharp.Math let ShowForm (f : Form) = #if INTERACTIVE f.Show() #endif #if COMPILED Application.Run(f) #endif let BitmapForm bitmap = let picBox = new PictureBox(BorderStyle = BorderStyle.Fixed3D, Image = bitmap, Size = bitmap.Size, Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.StretchImage) let form = new Form(Text = "F# Mandelbrot", Size = bitmap.Size) form.Controls.Add(picBox) form let MagnitudeSquared (c : complex) = c.r * c.r + c.i * c.i type Extents<'a> = { Xmin : 'a; Xmax : 'a; Ymin : 'a; Ymax : 'a } with override t.ToString() = sprintf "(%A,%A)-(%A,%A)" t.Xmin t.Ymin t.Xmax t.Ymax let inline interp y1 y2 x1 x2 x = let ymax = max y1 y2 let ymin = min y1 y2 let xmax = max x1 x2 let xmin = min x1 x2 ((ymax-ymin)/(xmax-xmin))*(x-xmin)+ymin let MapX (size : Size) (extents : Extents<float>) x = interp extents.Xmax extents.Xmin 0.0 (float size.Width) x let MapY (size : Size) (extents : Extents<float>) y = interp extents.Ymax extents.Ymin 0.0 (float size.Height) y let Mandelbrot (maxIter : int) (c : complex) = let rec MandelbrotInner z iter = if (iter = maxIter) || (z |> MagnitudeSquared >= 4.0) then iter else MandelbrotInner (z*z+c) (iter+1) MandelbrotInner c 0 let colorMap = [(0, Color.Black); (3, Color.Red); (6, Color.Blue); (10, Color.Chartreuse); (20, Color.Magenta); (50, Color.DarkTurquoise); (100, Color.White)] let PickColor (map : (int * Color) list) (n : int) = let InterpolateColor i1 (c1:Color) i2 (c2:Color) = let r = interp (int c1.R) (int c2.R) i1 i2 n let g = interp (int c1.G) (int c2.G) i1 i2 n let b = interp (int c1.B) (int c2.B) i1 i2 n Color.FromArgb(r,g,b) let rec PickColorInternal map = match map with | [] -> Color.Black | [(_,c)] -> c | (i1,c1)::((i2,c2)::_ as t) -> if (i1 <= n) && (n <= i2) then InterpolateColor i1 c1 i2 c2 else PickColorInternal t PickColorInternal map let MakeMandelbrotBitmap (maxIter : int) (size : Size) (extents : Extents<float>) = let b = new Bitmap(size.Width, size.Height, Imaging.PixelFormat.Format24bppRgb) for w in [0 .. size.Width-1] do for h in [0 .. size.Height-1] do let x = MapX size extents (float w) let y = MapY size extents (float h) let c = Complex.Create (x,y) b.SetPixel(w, h, c |> Mandelbrot maxIter |> PickColor colorMap) b let maxIter = 100; let size = new Size(700,600) let extents = {Xmin = -2.0; Xmax = 1.0; Ymin = -2.0; Ymax = 2.0} do MakeMandelbrotBitmap maxIter size extents |> BitmapForm
Cheers,
Rob
Oh ok, that makes sense. That is a lot cleaner.
I was just copy/pasting the WinForms designer generated code into F# and changing all the "=" to "<-" (for the most part) :P
Thanks for taking the time to comment!
here's my rendition. some things are shorter than i would have them in a normal app, for purposes of screwing around
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
#light open System.Drawing open System.Windows.Forms open Microsoft.FSharp.Math let max_iter = 100; let width, height = 700,600 let xmin, xmax = -2.0, 1.0 let ymin, ymax = -2.0, 2.0 let color_map = seq [ (0, Color.Black); (3, Color.Red); (6, Color.Blue); (10, Color.Chartreuse); (20, Color.Magenta); (50, Color.DarkTurquoise); (100, Color.White) ] |> Seq.pairwise |> Seq.cache let bitmap = new Bitmap( width, height, Imaging.PixelFormat.Format24bppRgb ) let form = new Form( Text = "F# Mandelbrot", Size = bitmap.Size) let pic_box = new PictureBox( BorderStyle = BorderStyle.Fixed3D, Image = bitmap, Size = bitmap.Size, Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.StretchImage) form.Controls.Add( pic_box ) let mandelbrot c = let rec curse iter (z: complex) = if (iter = max_iter) || (z.Magnitude > 2.0) then iter else curse (iter + 1) (z*z + c) curse 0 c let inline map (x1min, x1max) (x2min, x2max) = let ratio = (x2max - x2min) / (x1max - x1min) fun value -> (value - x1min) * ratio + x2min let pick_color n = color_map |> Seq.find (fun ((a, _), (b, _)) -> (a <= n) && (n <= b)) |> (fun ((a, c1), (b, c2)) -> let f (c, d) = map (a, b) (int c, int d) n Color.FromArgb( f (c1.R, c2.R), f (c1.G, c2.G), f (c1.B, c2.B) )) let mapx = float >> map (0.0, float width) (xmin, xmax) let mapy = float >> map (0.0, float height) (ymin, ymax) for x = 0 to width - 1 do for y = 0 to height - 1 do bitmap.SetPixel( x, y, Complex.Create(mapx x, mapy y) |> mandelbrot |> pick_color ) #if INTERACTIVE form.Show() #else Application.Run(form) #endif
one of the things i noticed is that by using the inline keyword on a function it becomes less polymorphically challenged
i was trying to do
1
let i = Complex.Create(0,1)
for a more natural notation, but except for scalars the Complex class lacks operations with floats. quite pathetic
Hi amr,
Thanks for that! Very clean and concise (and short).
I really like what you did with the map function, making it return a function that can be easily composed (e.g. applying the "float" function and then the map function using "float >> map"). That makes a lot of sense. It also makes sense to group things together into tuples that must (should?) be applied together (like x-y coordinates for point pairs).
I'd forgotten about Seq.pairwise. Nice. Why did you Seq.cache?
Thanks again.
since F# has currying/partial application, that explicit returning of a function isn't really necessary. the map function could have been written
1 2
let inline map (x1min, x1max) (x2min, x2max) value = (x2max - x2min) / (x1max - x1min) * (value - x1min) + x2min
and it would work the same. in this case the compiler might even be smart enough to memoize the function up to before 'value' is reached. i explicitly returned a function because i'm not sure about how it might be optimized. for simple arithmetic like this though it's probably not something to worry about either way
i use tuples because it helps me get a grasp of what the parameters are when i'm using the function, so it's really a readability thing for me. in the case of the map function, i see it as a mapping from one range to another, and so the tuples let me "visualize" that a little bit
from what i understand, sequences are evaluated only when necessary, which is useful for very large sequences. For example, if you do this:
1 2 3 4
let my_seq = seq [ 1; 2; 3; 4; 5 ] my_seq |> Seq.map ((+) 1) // increment each value by 1
nothing will actually happen. the original my_seq might not even be "actualized." nothing happens until you request a value from the sequence, like if you print it out. the issue is that the sequence isn't memoized (again, for purposes of large sequences,) and so every time you iterate over it, the sequence will be recalculated. in this example case, the incrementation will be applied over and over. in the case of the color map, the Seq.pairwise would be repeatedly recalculated
that's my understanding at least, but the cached version is indeed faster than the uncached, by 2x on my computer. i would have liked to use a plain list but i couldn't find a pairwise for lists. the non-uniform API's for the different data structures is annoying
Cool, thanks.
Makes sense about the tuples. Functions taking tuples, in general, are more restrictive than those that allow currying of every parameter, but in this case it definitely makes sense. Not very often do you deal with x,y pairs independently, and even then, how do you decide whether x or y goes first? Tuples just make sense here as a generalization of a Point struct/class like in C#.
Yeah, Seq is just the F# version of IEnumerable in C#, so it makes sense to cache it.
By the way, Seq is just an IEnumerable and the F# List (not to be confused with the C# List = ResizeArray, though this also holds true for that) implements IEnumerable under the covers, it's perfectly legal to use Seq.pairwise on an F# list:
<!--
{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Consolas;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;}??\fs20 > [1;2;3;4] |> Seq.pairwise;;\par ??\cf3 val\cf0 it : seq<int * int> = seq [(1, 2); (2, 3); (3, 4)]}
-->
> [1;2;3;4] |> Seq.pairwise;;
val it : seq<int * int> = seq [(1, 2); (2, 3); (3, 4)]
Or you could define your own List.pairwise -- it's pretty easy:
<!--
{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Consolas;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;}??\fs20 \cf1 let\cf0 \cf1 rec\cf0 pairwise l =\par ?? \cf1 match\cf0 l \cf1 with\par ??\cf0 | [] | [_] \cf1 ->\cf0 []\par ?? | h1::(h2::_ \cf1 as\cf0 t) \cf1 ->\cf0 (h1,h2) :: pairwise t}
-->
let rec pairwise l =
match l with
| [] | [_] -> []
| h1::(h2::_ as t) -> (h1,h2) :: pairwise t
(playing my usual role of tail-recursion police)
Careful, that 'pairwise' is not tail-recursive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#light let rec pairwise l = match l with | [] | [_] -> [] | h1::(h2::_ as t) -> (h1,h2) :: pairwise t let pairwiseTR l = let rec pairwise l acc = match l with | [] | [_] -> acc | h1::(h2::_ as t) -> pairwise t ((h1,h2)::acc) pairwise l [] |> List.rev pairwise [1..5] |> printfn "%A" pairwiseTR [1..5] |> printfn "%A" let r = pairwiseTR [1..100000] // ok let r2 = pairwise [1..100000] // kaboom
Oh SNAP :)
Thanks for catching that, Brian. I meant to double-check that before I posted but forgot.
That's a very tricky point that would make a good blog post -- tips about how to make absolutely sure that your recursive function can be optimized by the compiler to be tail recursive so there are no "kabooms" at runtime.
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 |
"Hello World" is too simplistic, so whenever I learn a new language, I always implement a "Hello Mandelbrot" program. I realize there are probably implementations already out there (who doesn't like making fractals? :P), but figured I'd give it a try without hunting around first.
I'd really appreciate comments from everyone here as to the implementation and style. Do you see any room for improvements? Did I do something wrong? Or naive? Or good? :) Any constructive criticism is greatly appreciated.
<!--
{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Consolas;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green128\blue0;\red0\green0\blue0;\red128\green128\blue128;\red128\green0\blue0;}??\fs20 \cf1 #light\par ??\cf3 // F# Mandelbrot Set Generator, 11.19.08, Matt Valerio, [link:thevalerios.net] ??\par ??\cf1 open\cf0 System.Drawing\par ??\cf1 open\cf0 System.Windows.Forms\par ??\cf1 open\cf0 Microsoft.FSharp.Math\par ??\par ??\cf1 let\cf0 ShowForm (f : Form) =\par ??\cf1 #if \cf0 INTERACTIVE\par ??\cf5 f.Show()\par ??\cf1 #endif\par ??#if \cf0 COMPILED\par ?? Application.Run(f)\par ??\cf1 #endif\cf0 \par ??\par ??\cf1 type\cf0 BitmapForm = \par ?? \cf1 inherit\cf0 Form \par ?? \cf1 val\cf0 \cf1 private\cf0 pictureBox : PictureBox\par ?? \cf1 new\cf0 (size : Size) \cf1 as\cf0 this = \par ?? \{\par ?? pictureBox = \cf1 new\cf0 PictureBox()\par ?? \} \cf1 then\par ??\cf0 this.SuspendLayout()\par ?? this.pictureBox.BorderStyle <- System.Windows.Forms.BorderStyle.Fixed3D\par ?? this.pictureBox.Name <- \cf6 "pictureBox"\par ??\cf0 this.pictureBox.Size <- size\par ?? this.pictureBox.Dock <- System.Windows.Forms.DockStyle.Fill\par ?? this.pictureBox.SizeMode <- PictureBoxSizeMode.StretchImage\par ?? this.Controls.Add(this.pictureBox) \par ?? this.Name <- \cf6 "BitmapForm"\cf0 \par ?? this.Size <- size \par ?? this.ResumeLayout() \par ?? \cf1 member\cf0 this.Bitmap \cf1 with\cf0 set value = this.pictureBox.Image <- value\par ??\par ??\cf1 let\cf0 SetBitmap (bitmap : #Image) (form : #BitmapForm) =\par ?? form.Bitmap <- bitmap\par ?? form\par ?? \par ??\cf1 let\cf0 MagnitudeSquared (c : complex) = c.r * c.r + c.i * c.i\par ??\par ??\cf1 type\cf0 Extents<'a> = \{ Xmin : 'a; Xmax : 'a; Ymin : 'a; Ymax : 'a \} \cf1 with\par ??\cf0 \cf1 override\cf0 t.ToString() = sprintf \cf6 "(%A,%A)-(%A,%A)"\cf0 t.Xmin t.Ymin t.Xmax t.Ymax\par ??\par ??\cf1 let\cf0 \cf1 inline\cf0 interp y1 y2 x1 x2 x =\par ?? \cf1 let\cf0 ymax = max y1 y2\par ?? \cf1 let\cf0 ymin = min y1 y2\par ?? \cf1 let\cf0 xmax = max x1 x2\par ?? \cf1 let\cf0 xmin = min x1 x2\par ?? ((ymax-ymin)/(xmax-xmin))*(x-xmin)+ymin\par ??\par ??\cf1 let\cf0 MapX (size : Size) (extents : Extents<float>) x =\par ?? interp extents.Xmax extents.Xmin 0.0 (float size.Width) x\par ?? \par ??\cf1 let\cf0 MapY (size : Size) (extents : Extents<float>) y =\par ?? interp extents.Ymax extents.Ymin 0.0 (float size.Height) y \par ??\par ??\cf1 let\cf0 Mandelbrot (maxIter : int) (c : complex) =\par ?? \cf1 let\cf0 \cf1 rec\cf0 MandelbrotInner z iter =\par ?? \cf1 if\cf0 (iter = maxIter) || (z |> MagnitudeSquared >= 4.0) \cf1 then\cf0 iter \par ?? \cf1 else\par ??\cf0 MandelbrotInner (z*z+c) (iter+1) \par ?? MandelbrotInner c 0\par ??\par ??\cf1 let\cf0 colorMap = [(0, Color.Black); (3, Color.Red); (6, Color.Blue); (10, Color.Chartreuse); \par ?? (20, Color.Magenta); (50, Color.DarkTurquoise); (100, Color.White)]\par ??\par ??\cf1 let\cf0 PickColor (map : (int * Color) list) (n : int) = \par ?? \cf1 let\cf0 InterpolateColor i1 (c1:Color) i2 (c2:Color) = \par ?? \cf1 let\cf0 r = interp (int c1.R) (int c2.R) i1 i2 n\par ?? \cf1 let\cf0 g = interp (int c1.G) (int c2.G) i1 i2 n\par ?? \cf1 let\cf0 b = interp (int c1.B) (int c2.B) i1 i2 n\par ?? Color.FromArgb(r,g,b) \par ?? \cf1 let\cf0 \cf1 rec\cf0 PickColorInternal map =\par ?? \cf1 match\cf0 map \cf1 with\par ??\cf0 | [] \cf1 ->\cf0 Color.Black\par ?? | [(_,c)] \cf1 ->\cf0 c\par ?? | (i1,c1)::((i2,c2)::_ \cf1 as\cf0 t) \cf1 ->\cf0 \cf1 if\cf0 (i1 <= n) && (n <= i2) \cf1 then\cf0 \par ?? InterpolateColor i1 c1 i2 c2\par ?? \cf1 else\cf0 \par ?? PickColorInternal t\par ?? PickColorInternal map\par ?? \par ??\cf1 let\cf0 MakeMandelbrotBitmap (maxIter : int) (size : Size) (extents : Extents<float>) = \par ?? \cf1 let\cf0 b = \cf1 new\cf0 Bitmap(size.Width, size.Height, Imaging.PixelFormat.Format24bppRgb)\par ?? \cf1 for\cf0 w \cf1 in\cf0 [0 .. size.Width-1] \cf1 do\par ??\cf0 \cf1 for\cf0 h \cf1 in\cf0 [0 .. size.Height-1] \cf1 do\par ??\cf0 \cf1 let\cf0 x = MapX size extents (float w)\par ?? \cf1 let\cf0 y = MapY size extents (float h) \par ?? \cf1 let\cf0 c = Complex.Create (x,y)\par ?? b.SetPixel(w, h, c |> Mandelbrot maxIter |> PickColor colorMap)\par ?? b\par ?? \par ??\cf1 let\cf0 MakeForm size = \par ?? \cf1 let\cf0 f = \cf1 new\cf0 BitmapForm(size)\par ?? f.Text <- \cf6 "F# Mandelbrot"\cf0 \par ?? f\par ?? \par ??\cf1 let\cf0 maxIter = 100;\par ??\cf1 let\cf0 size = \cf1 new\cf0 Size(700,600) \par ??\cf1 let\cf0 extents = \{Xmin = -2.0; Xmax = 1.0; Ymin = -2.0; Ymax = 2.0\} \par ??\par ??MakeForm size\par ??|> SetBitmap (MakeMandelbrotBitmap maxIter size extents) \par ??|> ShowForm\par ??}
-->
#light
// F# Mandelbrot Set Generator, 11.19.08, Matt Valerio
open System.Drawing
open System.Windows.Forms
open Microsoft.FSharp.Math
let ShowForm (f : Form) =
#if INTERACTIVE
f.Show()
#endif
#if COMPILED
Application.Run(f)
#endif
type BitmapForm =
inherit Form
val private pictureBox : PictureBox
new (size : Size) as this =
{
pictureBox = new PictureBox()
} then
this.SuspendLayout()
this.pictureBox.BorderStyle <- System.Windows.Forms.BorderStyle.Fixed3D
this.pictureBox.Name <- "pictureBox"
this.pictureBox.Size <- size
this.pictureBox.Dock <- System.Windows.Forms.DockStyle.Fill
this.pictureBox.SizeMode <- PictureBoxSizeMode.StretchImage
this.Controls.Add(this.pictureBox)
this.Name <- "BitmapForm"
this.Size <- size
this.ResumeLayout()
member this.Bitmap with set value = this.pictureBox.Image <- value
let SetBitmap (bitmap : #Image) (form : #BitmapForm) =
form.Bitmap <- bitmap
form
let MagnitudeSquared (c : complex) = c.r * c.r + c.i * c.i
type Extents<'a> = { Xmin : 'a; Xmax : 'a; Ymin : 'a; Ymax : 'a } with
override t.ToString() = sprintf "(%A,%A)-(%A,%A)" t.Xmin t.Ymin t.Xmax t.Ymax
let inline interp y1 y2 x1 x2 x =
let ymax = max y1 y2
let ymin = min y1 y2
let xmax = max x1 x2
let xmin = min x1 x2
((ymax-ymin)/(xmax-xmin))*(x-xmin)+ymin
let MapX (size : Size) (extents : Extents<float>) x =
interp extents.Xmax extents.Xmin 0.0 (float size.Width) x
let MapY (size : Size) (extents : Extents<float>) y =
interp extents.Ymax extents.Ymin 0.0 (float size.Height) y
let Mandelbrot (maxIter : int) (c : complex) =
let rec MandelbrotInner z iter =
if (iter = maxIter) || (z |> MagnitudeSquared >= 4.0) then iter
else
MandelbrotInner (z*z+c) (iter+1)
MandelbrotInner c 0
let colorMap = [(0, Color.Black); (3, Color.Red); (6, Color.Blue); (10, Color.Chartreuse);
(20, Color.Magenta); (50, Color.DarkTurquoise); (100, Color.White)]
let PickColor (map : (int * Color) list) (n : int) =
let InterpolateColor i1 (c1:Color) i2 (c2:Color) =
let r = interp (int c1.R) (int c2.R) i1 i2 n
let g = interp (int c1.G) (int c2.G) i1 i2 n
let b = interp (int c1.B) (int c2.B) i1 i2 n
Color.FromArgb(r,g,b)
let rec PickColorInternal map =
match map with
| [] -> Color.Black
| [(_,c)] -> c
| (i1,c1)::((i2,c2)::_ as t) -> if (i1 <= n) && (n <= i2) then
InterpolateColor i1 c1 i2 c2
else
PickColorInternal t
PickColorInternal map
let MakeMandelbrotBitmap (maxIter : int) (size : Size) (extents : Extents<float>) =
let b = new Bitmap(size.Width, size.Height, Imaging.PixelFormat.Format24bppRgb)
for w in [0 .. size.Width-1] do
for h in [0 .. size.Height-1] do
let x = MapX size extents (float w)
let y = MapY size extents (float h)
let c = Complex.Create (x,y)
b.SetPixel(w, h, c |> Mandelbrot maxIter |> PickColor colorMap)
b
let MakeForm size =
let f = new BitmapForm(size)
f.Text <- "F# Mandelbrot"
f
let maxIter = 100;
let size = new Size(700,600)
let extents = {Xmin = -2.0; Xmax = 1.0; Ymin = -2.0; Ymax = 2.0}
MakeForm size
|> SetBitmap (MakeMandelbrotBitmap maxIter size extents)
|> ShowForm
I'm particularly fond of the color picking algorithm using a list of (int,Color) tuples as a "color map". This really illustrates the power of F# pattern matching.
Any comments? :)