This document discusses obstacles to learning the F# programming language from the perspective of C# and VB.NET programmers. It outlines six main obstacles: 1) pattern matching is foreign, 2) type inference can feel like a scripting language, 3) recursion may be at odds with one's beliefs, 4) workflows are central but tough to understand, 5) one must rewire their brain to learn F#, and 6) there is a misconception that functional programming is a new fad. The document provides tips for overcoming each obstacle, such as focusing on pattern matching examples and understanding built-in workflows. It emphasizes important F# concepts like composition and immutability over complex terms, and argues that functional programming has
What the math geeks don't want you to know about F#
1. What the Math Geeks Don't Want You to Know about F# Tips for OvercomingObstacles to Learning F# August 2010 Kevin Hazzard, C# MVP
2. What This Talk Is & Is Not This talk does not intend to teach F# deeply This talk is for C# / VB.NET programmers trying to answer the questions: Why should I care about functional languages? What makes F# worth learning? What's going to slow me down on the journey?
3. What makes a language functional? Compositional and often expression-oriented Supports functions as first-class types Often promotes lazy evaluation Extensive use of higher-order functions Avoids memory and I/O side-effects Prefers recursion over iteration Excellent for concurrent programming May have polymorphic data types withextensive pattern matching capabilities
4. A Teaching Example type BinaryTree<'a> = | Leaf of 'a | Node of BinaryTree<'a> * BinaryTree<'a>
5. Obstacle #1 Pattern matching is foreign and pervasive. Pattern matching is at the core of F# When learning F#, focus on every pattern matching example you can find Start with the simple ones, e.g. discriminated unions, move up and out Treat this learning as a block and learn it deeply, in a very focused way
6. Obstacle #2 Type inference makes F# feel like a scripting language. If you're a Pythonista, you will feel at home writing F# - the types get out of your way If not… Watch carefully for inferred types, especially while you are learning – use the FSI REPL a lot Use automatic generalization to reduce code volume and increase simplicity
7. Automatic Generalization No obstacle but something to be aware of What type does this operate on? let max a b = if a > b then a else b F# automatically generalizes complete expressions when it can
8. Recursion – Port This to F# public inthcf( intx, inty ) { int result = 0; while (result == 0) { if (x < y) { y %= x; if (y == 0) result = x; } else { x %= y; if (x == 0) result = y; } } return result; }
9. Isn't This Succinct? let rechcf a b =if a = 0 then belifb = 0 then aelif a < b then hcf a (b % a)else hcf (a % b) b
10. Tail Recursion Optimization Recursive functions that do no extra work after the recursion can often be converted to while loops F# does this automatically if the code is structured to allow for it The JITter will sometimes do this on compiled code No guarantees though
11. Obstacle #3 Recursion may be at odds with your belief system. Many of us were taught to fear recursion Stop thinking that way Every time you write an iteration think,"Could this be done with recursion instead?" Write your F# code so that it is tail optimizable
13. Obstacle #4 Workflows are central to F# and tough to understand. Think of workflows like little Domain Specific Languages (DSL) Builders are like AOP-enabled classes, allowing you to hook and define those 12 core functions When the F# code in the computation expression is evaluated, your code gets to direct the work Focus on understanding the built-in async workflow
14. VS Gallery F# Templates When you first look at the F# templates in VS 2010, you may be underwhelmed The F# team and others are always adding F# templates to ease development Daniel Mohl (@dmohl) has written some excellent templates that are available in the online gallery
15. Obstacle #5 You must rewire your brain a bit to learn F#. F# is part of the .NET ecosystem Great tooling in Visual Studio 2010 Full access to all .NET types and members Seamless access to & from C# and VB.NET F# is multi-paradigm Emphasizes data immutability and the evaluation of expressions But also supports traditional OOP concepts
16. Don't Get Hung Up On Terms Lambda Calculus Turing Completeness Universal Computer Endofunctors Cata/Ana-morphism Co-algebras Category Theory "How does this help me to write better software?"
17. Stuff That Is Important Composition / Currying Pattern Matching Type inference Immutability Side-effects Recursion Workflows
18. Obstacle #6 Functional programming is a new fad. It will pass. Anyone want to guess how long functional programming has been around? OOP dominated because it was the best way to reduce complexity and promote reuse in CPU-bound systems Functional languages are leader languages Data structures Message and event passing Dynamic typing and type inferencing Generics Garbage collection
19. The History of F# Started in 2002 in Microsoft Research Based on languages like ML and OCaml Influenced great stuff in .NET Generics LINQ Visual Studio support in versions 2005 & 2008 Became a first-class language citizen inVisual Studio 2010
20. Demo Check out the F# Samplescode.msdn.microsoft.com/fsharpsamples Explore Tutorial.fs with Alt + Enter Samples101
Notas do Editor
This talk is not about teaching the F# language. We'll look at some code, for sure. This talk is for devs who know C# or VB.NET and looking at typical F# makes you say, "Huh?" I'll highlight some key concepts and tell you about stuff that will trip you up. In that context, we'll also talk about how you can approach the language in a much more manageable way, leveraging the skills you already have.
Functional languages tend to be a lot more succinct or terse than their imperative counterparts. F# is both functional and object-oriented. But the syntax is close to its functional cousins like OCaml and Haskell that are very terse. In those languages, like F#, you tend to be able to express a lot of intent with very little code. There's a tipping point, of course, where languages become so terse, so compact that the lose the ability to convey meaningful information to human readers. F# strikes a nice balance but it can be overwhelming to read if you're accustomed to languages like C#, much more so VB.NET which is praised for its "wordiness" by those who love it.The central theme to functional languages is, of course, functions. They are first class citizens. Declaring a lambda expression is easy using the fun keyword. But standalone functions can be declared in such a way that they can be connected together, or composed using a concept called currying. When I first began working with functional languages, I thought that currying had something to do with the spicy curries used in Indian and Asian foods. These are sauces made up of a lot of varying spices. I mistakenly thought that currying in functional programming got its name from mixing lots of functions together to get a result. I was naïve. We all are at some point in learning new things. Currying in functional programming gets its name from Haskell Curry, a major name in computer science whose first name is also lent to the purely functional programming language called Haskell.Currying (and composition of functions in general) are very important concepts in FP languages. Being able to pass functions to other functions and lazily evaluate results only when they are needed, immutability is possible. And when you can have truly immutable data structures, you can make the leap to expression-orientation, where you tell the compiler what you want to accomplish, not exactly how to do it. When the compiler and runtime tooling can look at an expression abstractly and farm varying parts off to different processors, you begin to take advantage of the so-called multi-core revolution that's under way right now. Writing multi-threaded code is hard. Even the best of us, who've been writing symmetric multi-processing (SMP) code for decades can make serious but subtle mistakes trying to farm out a complex operation to all of the available CPUs.Multi-processing is not the only reason to code functionally. Functional software is easier to test in many cases. If you think about it, how many of your unit tests are designed to test capabilities to mutating object state? Yeah, a lot of them.
A discriminated union that can contain a leaf node or another binary tree. Great for building binary trees of data. Notice how terse the syntax is. It might read, here's a binary tree of some unknown type. It can contain either a Leaf of that unknown type or another binary tree called Node. (create the type in FSI and talk about the output here) Now let's write a function to print out a tree:let recprintBinaryTreeValuest = match t with | Leaf x -> printfn "%i" x | Node (l, r) ->printBinaryTreeValueslprintBinaryTreeValuesrNotice the rec keyword? That means recursive. In F#, you must mark recursive functions as such. We use the let keyword to assign symbols to things. The printBinaryTree symbol is assigned to the code below it. F# is whitespace sensitive like other languages I love, e.g. Python. I think it makes the code so much more readable to get rid of all the curly braces and the BEGIN/END type keywords. The parameter to this method is also declared without a lot of decoration. That symbol is "t". The equals sign after that separates the symbol assignment from the code. The code starts with a pattern match. It says to match the parameter t to either a Leaf or a Node from our BinaryTree type. When it's a Leaf, just print out the value there. When it's a Node, recurse twice to visit the left and right trees there instead. There's no return type from this function which is why the output type is listed as "unit". Now let's create a binary tree and run this depth-first visitor to print it out:printBinaryTreeValues (Node ((Node (Leaf 1, Leaf 2)), (Node (Leaf 3, Leaf 4))))Cool. Here's a slightly more interesting function that will visit each node and print out the data with more detail and indentation:let printBinaryTreet = let recprintBinaryTreet indent = match t with | Leaf x -> printfn "%sLeaf %i" indent x | Node (l, r) ->printfn "%sLeft Branch" indentprintBinaryTreel (indent + " ")printfn "%sRight Branch" indentprintBinaryTreer (indent + " ")printBinaryTreet ""Notice that this function called printBinaryTree has another function defined within it. The inner function is limited in scope, of course. And using the same name for the inner function is OK because the signatures are different. See how the inner function takes 2 parameters while the outer one only takes one parameter? The inner function uses that new parameter to say how far to indent information that's printed out depending on how deep the traversal is in the tree. Run this with the same tree we used before:printBinaryTree (Node ((Node (Leaf 1, Leaf 2)), (Node (Leaf 3, Leaf 4))))Nice. Now, since the BinaryTree is generic, should we able pass tree of string nodes to it? Let's try this:printBinaryTree (Node ((Node (Leaf "Kevin", Leaf "Donna")), (Node (Leaf "Ted", Leaf "Charlotte"))))That didn't work. Why? The error message says something about expecting integers when we gave it strings. Go back and look at the response that FSI gave us when we created this new function. It says:valprintBinaryTree : BinaryTree<int> -> unitThat's odd. Nowhere in the definition of this function did I say that the type of the binary tree was integers did I? Look closely. Buried in the printfn statement for the Leaf type is "%i" which tells F# to interpret the parameter as an integer. This is an example of how deep F#'s type inference engine goes. Change that %i formatter and replace it with %O which tells F# that it's an object we’re passing and that it should box values as necessary and invoke ToString() on them. Save the new function into the REPL. What does it say is the type?valprintBinaryTree : BinaryTree<'a> -> unitAhhh! Much better. Now retry the sample data with strings in the nodes to see if it works. It does! Hooray!
Here are some examples:let biggestFloat = max 3.0 3.1415927let biggestInt = max 7 2let biggestString = max "Kevin" "Donna"
Some ancient code to find the highest common factor written in C#:public inthcf( intx, inty ){int result = 0; while (result == 0) { if (x < y) {y %= x; if (y == 0) result = x; } else {x %= y; if (x == 0) result = y; } } return result;}// porting the iterative code directly to F# - yuck!let hcf a b = let mutable x = a let mutable y = b let mutable result = 0 while result = 0 do if x < y theny <- y % x if y = 0 then result <- x elsex <- x % y if x = 0 then result <- y result// try this call – hcf should be 12hcf 29292 4524That's some ugly F# - mutation is a warning sign. If you have to mark something mutable, you are most likely writing bad F#. There are cases where you may be working with third party libraries that depend on mutation. And it's OK to do it then, of course. But for your code, always try to avoid data mutation. If you need to change data, copy it, changing what you need and pass that to a function. This is much better:let rechcf a b = if a = 0 then belifb = 0 then aelif a < b then hcf a (b % a) else hcf (a % b) bFor fun, try this:hcf 17350618 26025927Jenny's phone number is, in fact, a prime number.
Read this: Successively reduce the lesser parameter to the integer remainder of division with the greater parameter until one reaches zero. So much more readable than the iterative version. And recursion is the more natural way to express this idea.
A workflow builder is like an AOP-hookable class. You inject methods for implementing these 12 methods, as many or as few as you need to handle flow constructs and bindings. In this simple example, we'll create a TraceBuilder that simply traces the Bind, Delay and Result events during the workflow execution.Define 3 functions for Bind, Result and Delay:let bind value1 function1 = printfn "Binding %A." value1 function1 value1let result value1 =printfn "Returning result: %A" value1 fun () -> value1let delay function1 = fun () -> function1()Now tie those implementations into the TraceBuilder class by name:type TraceBuilder() = member x.Bind(value1, function1) = bind value1 function1 member x.Return(value1) = result value1 member x.Delay(function1) = printfn "Starting traced execution." delay function1Instantiate the builder:let trace = new TraceBuilder()Bind it to the expression code:let trace1 = trace { let x = 7 let! y = 5 let sum = x + y return sum }And execute:let output = trace1()A great way to learn the usefulness of workflows is by studying one of the built in workflows called async:open System.Netopen System.Threadingopen Microsoft.FSharp.Control.WebExtensionslet fetchAsync(name, url:string) =async { try let uri = new System.Uri(url) let webClient = new WebClient() let! html = webClient.AsyncDownloadString(uri)printfn "Thread %i: Read %d characters for %s" Thread.CurrentThread.ManagedThreadIdhtml.Length name with | ex -> printfn "%s" (ex.Message); }let urlList = [ "Kevin's Blog", "http://devjourney.com" "MSDN", "http://msdn.microsoft.com" "Bing", "http://www.bing.com" ]let runAll() =urlList |> Seq.mapfetchAsync |> Async.Parallel |> Async.RunSynchronously |> ignorerunAll()
Open Visual Studio and select the online templates when creating a new project. Filter to F# and examine the available templates.
When someone gives an FP talk and they day catamorphism, please raise you hand and say, "Isn't a catamorphism just a fold operation on a sequence?" If they say, "Well… yeah." Then ask, "Why can't you just say fold? Why do you have to use mathematics terms to explain something so simple to us?" Speakers in the FP world need to learn to speak to us on our terms and in the language that we understand.Anamorphism = unfold