First, let me remind you that in my new ongoing quest to read source code to be a better developer, Dear Reader, I present to you thirty-fourth in a infinite number of posts of "The Weekly Source Code."
F#, everyone is agog over F# and getting all functional. Again. ;) The F# September 2008 CTP came out a few weeks back and folks I'm talking to are digging it. Here's the goal:
"F# developed as a research programming language to provide the much sought-after combination of type safety, succinctness, performance, expressivity and scripting, with all the advantages of running on a high-quality, well-supported modern runtime system."
Looks like it's more than a research language as it's inside Visual Studio and looking pretty with syntax highlighting and intellisense and everything. This might be old news to you, Dear Reader, but F# is reaching a very significant level of polish. This release is moving F# to the level of being a peer of C# and VB. It's got its own F# DevCenter at MSDN. How's that for legit?
Here's a great one paragraph explanation from Vertigo's Rick Taylor about what/why/how F#:
"Unless you specify otherwise, everything in F# is immutable, much like the string construct in C#. This extends to areas that you might not expect. For instance, once you set an int to some specific value, that's it – you can't change it, unless you have marked it as mutable. The reason for this is because the language is primarily a functional one. Programs in functional languages contain functions which return values, which are then further used, etc – but each value or set of values is its own entity, and to change it while within a function is to produce a side effect, something undesirable in a functional language. In the strictest sense, functions return values but do not alter their parameters, or the outside world (which, incidentally, gives rise to the monad pattern mentioned earlier). F# follows these rules of functional programming, but also allows you to break those rules with the mutable keyword."
What's the best way to jump into F#? Well there's a few great ways. First, a little blatant self-promotion. I've done two F# podcasts with smart F# fanboys and there's great .NET Rocks and Herding Code episodes too.
Books
Code
However, as a F# newbie with a few years of Haskell in the back of my head, the presentation that has clicked with me the most was Leon's F# eye for the C# guy PowerPoint. Scandalous, I know, as you may know Leon as the cruel bully who called me Hanselgirl in public recently at TechEd Australia. I promptly boxed his ears (it was more of a slap-fight actually) and there was also a public arm-wrestling. Between the two of us I'm sure there was the potential to splice together one normal-sized male arm, but I digress. You can download his deck here, but I've also taken the liberty to put it on SlideShare and embedded it here.
I had intellectualized slides 42 through 45, but the way he described it clicked. He did what I would do:
"I worked this out using Reflector. I did not work this out from reading books or papers or listening to podcasts or quizzing intelligent people. A simple let statement is a static function, under the hood. When you see let, think function."
Check out those few slides,. It's a great deck, and I'm sure Leon wouldn't mind YOU, Dear Reader, showing his talk at your local User Group or having a Programmer's Lunch at work and taking complete credit for it. That's what I fully plan on doing. ;) Suck it Bambrick!*
Let's see three bits of code.
Remember there are no variables. No side-effects.
#lightlet sqr x = x * xlet multiply x y = x * yprint_int (sqr 3)print_int (multiply 3 4)// recursive function fibonacci series using pattern matchinglet rec fib x = match x with | x when x <= 0 -> failwith "An integer greater than 0 is required." | 1 -> 1 | 2 -> 1 | x -> fib (x - 1) + fib (x - 2) print_int (fib 15)// functions as valueslet add x y = x + ylet a1 = add 3let a2 = a1 4print_int a2
Units of Measure in F#. What? Andrew Kennedy's PhD thesis from 13 years ago just happens to be a feature in F#. He's done a three part series:
Basically they've added static checking and inferences for units-of-measure. Not any specific ones, but the concept itself.
"As far as F# is concerned, ft and m have nothing to do with each other. It's up to you, the programmer, to define appropriate conversion factors."
Here's a screenshot from his blog that says a thousand words and showcases the FSharp.Math.PhysicalConstants namespace, along with the International System of Units (SI) namespace.
Also from Andrew's blog:
"You can define mutually recursive measures using "and" to connect them and placing the Measure attribute immediately before the name of the measure:"
type [<Measure>] km = static member toM = 1.0/1000.0<m/km>and [<Measure>] m = static member toKm = 1000.0<km/m>
A fun little 2D Tron-Clone game from last year, written in only 182 lines of F# by Phil Trelford. You can download it from hubFS and read how he wrote it on the Applied Games Group Blog. The game even supports Xbox 360 controllers! Sweet.
Here's all 182 lines, not counting the first 4 lines of comments.
//-----------------------------------------------------------------------------// LightCycles.fs Mini game using windows forms// 2007 written by Phillip Trelford//-----------------------------------------------------------------------------#light#if DIRECTX#R @"C:\WINDOWS\assembly\GAC_32\Microsoft.DirectX\2.0.0.0__31bf3856ad364e35\Microsoft.DirectX.dll" // Feb 206open Microsoft.DirectX.XInput // Required to read XBox 360 controllers#endifopen Systemopen System.Drawingopen System.Windows.Forms/// Game statestype GameState = | Start | Play | Over/// Form key handler typetype KeyHandler (form:Form) = do form.KeyPreview <- true let keys = Enum.GetValues (type Keys) :?> (Keys []) let keysDown = Array.create keys.Length false let FindKeyIndex code = keys |> Array.find_index (fun x -> code = x) do form.KeyDown.Add (fun e -> keysDown.[FindKeyIndex e.KeyCode] <- true) do form.KeyUp.Add (fun e -> keysDown.[FindKeyIndex e.KeyCode] <- false) member this.IsKeyDown (keyCode:Keys) = keysDown.[FindKeyIndex keyCode] member this.AnyKeyDown () = keysDown |> Array.exists (fun x -> x) /// Player direction typetype Direction = | Left | Right | Up | Down /// Player typetype Player (color,startX,startY,direction,keys,keyHandler:KeyHandler) = let mutable x = startX let mutable y = startY let mutable d = direction member this.Color = color member this.X = x member this.Y = y member this.Keys = keys /// Reset player to start values member this.Reset () = x <- startX; y <- startY; d <- direction /// Updates player position member this.Update i = // Read keyborad let mutable newD = d let up, down, left, right = keys if keyHandler.IsKeyDown(up) then newD <- Up if keyHandler.IsKeyDown(down) then newD <- Down if keyHandler.IsKeyDown(left) then newD <- Left if keyHandler.IsKeyDown(right) then newD <- Right#if DIRECTX // Read XBox 360 controller let state = Controller.GetState(i) if state.IsConnected then let pad = state.GamePad if pad.UpButton then newD <- Up if pad.DownButton then newD <- Down if pad.LeftButton then newD <- Left if pad.RightButton then newD <- Right#endif /// Don't allow suicide move match (d,newD) with | (Left, Right) | (Right, Left) | (Up, Down) | (Down, Up) -> () | _ -> d <- newD /// Update position with direction match d with | Up -> y <- y - 1 | Down -> y <- y + 1 | Left -> x <- x - 1 | Right -> x <- x + 1 /// Main form let form = new Form (Text="Light Cycles", Width=680, Height=544) do /// Layout for game window and status panel let layout = new TableLayoutPanel(Dock=DockStyle.Fill, ColumnCount = 2) layout.ColumnStyles.Add( ColumnStyle(SizeType = SizeType.Percent, Width = 100.0f ) ) |> ignore layout.ColumnStyles.Add( ColumnStyle(SizeType = SizeType.Absolute, Width = 128.0f) ) |> ignore /// Play area in pixels let playArea = 500 /// Game play area bitmap let bm = new Bitmap(playArea, playArea) /// Clears screen let ClearScreen () = using (Graphics.FromImage(bm)) (fun graphics -> graphics.Clear(Color.Black)) /// Draws text to screen let DrawText s = using (Graphics.FromImage(bm)) (fun graphics -> let rect = new RectangleF(0.0f,0.0f,float32 playArea,float32 playArea) let align = new StringFormat(Alignment=StringAlignment.Center, LineAlignment=StringAlignment.Center) graphics.DrawString(s, form.Font, Brushes.White, rect, align) ) // Initialise screen ClearScreen () DrawText "Press any key to start" /// PictureBox to contain game bitmap let pictureBox = new PictureBox(Dock=DockStyle.Fill) pictureBox.Image <- bm layout.Controls.Add(pictureBox) let keyHandler = KeyHandler (form) /// Players array let players = [| Player (Color.Red,playArea/2+20,playArea/2,Down,(Keys.Q,Keys.A,Keys.Z,Keys.X),keyHandler); Player (Color.LightBlue,playArea/2-20,playArea/2,Up,(Keys.P,Keys.L,Keys.N,Keys.M),keyHandler) |] players |> Array.iter (fun player -> bm.SetPixel(player.X,player.Y,player.Color)) /// Display player controls let statusPanel = new TableLayoutPanel(Dock=DockStyle.Fill, ColumnCount=1, BackColor=Color.DarkGray) players |> Array.iteri (fun i player -> let name = [| ((new Label (Text=sprintf "Player %d" i, ForeColor=player.Color)) :> Control) |] let up, down, left, right = player.Keys let controls = Array.combine [|"Up";"Down";"Left";"Right"|] [|up;down;left;right|] |> Array.map (fun (name,key) -> (new Label (Text=sprintf "%s '%O'" name key)) :> Control ) Array.append name controls |> statusPanel.Controls.AddRange ) layout.Controls.Add(statusPanel) form.Controls.Add(layout) /// Game play - returns true if there has been a collision otherwise false let PlayGame () = let collisions = players |> Array.mapi (fun i player -> player.Update i let x, y = (player.X, player.Y) let wall = x < 0 || x >= playArea || y < 0 || y >= playArea if wall then true else let bgColor = bm.GetPixel(x, y) bm.SetPixel (x, y, player.Color) players |> Array.exists (fun player -> let c = player.Color in c.R = bgColor.R && c.G = bgColor.G && c.B = bgColor.B ) ) pictureBox.Refresh () match collisions |> Array.tryfind_index (fun x -> x = true) with | Some(i) -> i | None -> (-1) /// Current game state let gameState = ref GameState.Start let gameOverWaitCount = ref 200 let r = new Random() /// Timer instance let timer = new Timer() timer.Interval <- 1000/50 // Timer event timer.Tick.Add (fun _ -> match !gameState with | Start -> if keyHandler.AnyKeyDown () then ClearScreen () gameState := GameState.Play | Play -> let i = PlayGame () if i>=0 then gameState := GameState.Over gameOverWaitCount := 200 DrawText (sprintf "Game Over - Play %d Lost" i) pictureBox.Refresh () | Over -> // Shake screen form.Left <- form.Left + if !gameOverWaitCount > 150 then r.Next(5) - 2 else 0 // Decrement Game Over wait decr gameOverWaitCount if !gameOverWaitCount <= 0 then gameState := GameState.Start players |> Array.iter (fun player -> player.Reset ()) ClearScreen () DrawText "Press any key to start" pictureBox.Refresh () ) timer.Start () [<STAThread>] do Application.Run(form)
Try to learn ONE new language each year!
Related Posts
* Oh, relax, Dear Reader. Leon and I are, like, totally, BFF. We're peas and carrots. Truly. The bastard.
let add x y = x + y let a1 = add 3 let a2 = a1 4 print_int a2
let x = [1;2;3;4];;let x_plus_3 = List.map (add 3) x;;
let x = [1;2;3;4];;let x_plus_3 = List.map ((+) 3) x;;
let incBy value list = List.map ((+) value) list;;
let incBy value = List.map ((+) value);;
incBy
Ads by The Lounge