├── .gitattributes ├── .gitignore ├── docker-meditate.sh ├── Dockerfile ├── docker.sh ├── .gitpod.yml ├── .devcontainer └── devcontainer.json ├── FSharpKoans.Core ├── KoanAttribute.fs ├── KoanResult.fs ├── FSharpKoans.Core.fsproj ├── KoanContainer.fs ├── Helpers.fs └── KoanRunner.fs ├── .vscode ├── tasks.json └── launch.json ├── License.txt ├── FSharpKoans ├── PathToEnlightenment.fs ├── AboutUnit.fs ├── AboutTheOrderOfEvaluation.fs ├── AboutLooping.fs ├── FSharpKoans.fsproj ├── AboutAsserts.fs ├── AboutArrays.fs ├── AboutDiscriminatedUnions.fs ├── AboutModules.fs ├── AboutPipelining.fs ├── AboutClasses.fs ├── AboutFunctions.fs ├── AboutOptionTypes.fs ├── AboutRecordTypes.fs ├── AboutDotNetCollections.fs ├── AboutTuples.fs ├── AboutLet.fs ├── AboutBranching.fs ├── AboutStrings.fs ├── AboutTheStockExample.fs ├── MoreAboutFunctions.fs ├── AboutFiltering.fs └── AboutLists.fs ├── FSharpKoans.Test ├── FSharpKoans.Test.fsproj ├── GettingTheWholeOutput.fs ├── FindingKoans.fs └── RunningKoans.fs ├── README.md └── FSharpKoans.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.sh text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Bb]in/ 2 | [Oo]bj/ 3 | *.suo 4 | packages/ 5 | .ionide/ 6 | -------------------------------------------------------------------------------- /docker-meditate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | cd /koans/FSharpKoans 4 | dotnet watch run 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:6.0 2 | WORKDIR /koans 3 | CMD ["bash", "docker-meditate.sh"] 4 | 5 | -------------------------------------------------------------------------------- /docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build -t fsharp-koans . && docker run --rm -v `pwd`:/koans -ti fsharp-koans 4 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: mcr.microsoft.com/dotnet/core/sdk:3.1 2 | 3 | tasks: 4 | - command: 'dotnet watch -p FSharpKoans/FSharpKoans.fsproj run' 5 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "F# Koans", 3 | "image": "mcr.microsoft.com/vscode/devcontainers/dotnet:6.0", 4 | "extensions": [ 5 | "Ionide.Ionide-fsharp", 6 | "ms-dotnettools.csharp" 7 | ] 8 | } -------------------------------------------------------------------------------- /FSharpKoans.Core/KoanAttribute.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans.Core 2 | 3 | open System 4 | 5 | [] 6 | type KoanAttribute () = 7 | inherit Attribute() 8 | let mutable sortOrder = 0 9 | member x.Sort 10 | with get() = sortOrder 11 | and set(v) = sortOrder <- v 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet build", 9 | "type": "shell", 10 | "group": "build", 11 | "presentation": { 12 | "reveal": "silent" 13 | }, 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /FSharpKoans.Core/KoanResult.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans.Core 2 | 3 | open System 4 | 5 | type KoanResult = 6 | | Success of string 7 | | Failure of string * Exception 8 | with 9 | member this.Message = 10 | match this with 11 | | Success x -> x 12 | | Failure (x, _) -> x 13 | 14 | [] 15 | module KoanResult = 16 | let map f (x:KoanResult) = 17 | match x with 18 | | Success m -> Success <| f m 19 | | Failure (m, e) -> Failure (f m, e) 20 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010 Chris Marinos 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /FSharpKoans.Core/FSharpKoans.Core.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | Library 5 | FSharpKoans.Core 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FSharpKoans/PathToEnlightenment.fs: -------------------------------------------------------------------------------- 1 | open FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | let runner = KoanRunner() 5 | let result = runner.ExecuteKoans() 6 | 7 | match result with 8 | | Success message -> printf "%s" message 9 | | Failure (message, ex) -> 10 | printf "%s" message 11 | printfn "" 12 | printfn "" 13 | printfn "" 14 | printfn "" 15 | printfn "You have not yet reached enlightenment ..." 16 | printfn "%s" ex.Message 17 | printfn "" 18 | printfn "Please meditate on the following code:" 19 | printfn "%s" ex.StackTrace 20 | 21 | printfn "" 22 | printfn "" 23 | printfn "" 24 | printfn "" 25 | printf "Press any key to continue..." 26 | System.Console.ReadKey() |> ignore 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Koans (.Net Core, Console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceRoot}/FSharpKoans/bin/Debug/net6.0/FSharpKoans.dll", 13 | "args": [], 14 | "cwd": "${workspaceRoot}", 15 | "stopAtEntry": false, 16 | "console": "internalConsole" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /FSharpKoans.Test/FSharpKoans.Test.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library 4 | net6.0 5 | FSharpKoans.Test 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /FSharpKoans.Core/KoanContainer.fs: -------------------------------------------------------------------------------- 1 | module FSharpKoans.Core.KoanContainer 2 | 3 | open System 4 | open System.IO 5 | open System.Reflection 6 | 7 | let hasKoanAttribute (info:MethodInfo) = 8 | info.GetCustomAttributes(typeof, true) 9 | |> Seq.isEmpty 10 | |> not 11 | 12 | let findKoanMethods (container: Type) = 13 | container.GetMethods(BindingFlags.Public ||| BindingFlags.Static) 14 | |> Seq.filter hasKoanAttribute 15 | 16 | let runKoans container = 17 | let getKoanResult (m:MethodInfo) = 18 | try 19 | m.Invoke(null, [||]) |> ignore 20 | Success <| sprintf "%s passed" m.Name 21 | with 22 | | ex -> 23 | let outcome = sprintf "%s failed." m.Name 24 | Failure(outcome, ex.InnerException) 25 | 26 | container 27 | |> findKoanMethods 28 | |> Seq.map getKoanResult 29 | -------------------------------------------------------------------------------- /FSharpKoans.Core/Helpers.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpKoans.Core.Helpers 3 | 4 | open System 5 | open NUnit.Framework 6 | 7 | let inline __<'T> : 'T = failwith "Seek wisdom by filling in the __" 8 | 9 | type FILL_ME_IN = 10 | class end 11 | 12 | type FILL_IN_THE_EXCEPTION() = 13 | inherit Exception() 14 | 15 | let AssertWithMessage (x : bool) message = Assert.IsTrue(x, message) 16 | 17 | let inline AssertEquality (x:'T) (y:'T) = 18 | match box y with 19 | | :? System.Type as t when t = typeof -> failwith "Seek wisdom by correcting the type FILL_ME_IN" 20 | | :? System.Type as t when t = typeof -> failwith "Seek wisdom by correcting the type FILL_IN_THE_EXCEPTION" 21 | | _ -> Assert.AreEqual(x,y) 22 | 23 | let AssertInequality (x:'T) (y:'T) = Assert.AreNotEqual(x,y) 24 | 25 | let AssertThrows<'a when 'a :> exn> action = Assert.Throws<'a>(fun () -> action()) 26 | 27 | let Assert (x : bool) = Assert.IsTrue(x) 28 | -------------------------------------------------------------------------------- /FSharpKoans/AboutUnit.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | open Microsoft.FSharp.Reflection 4 | 5 | //--------------------------------------------------------------- 6 | // About Unit 7 | // 8 | // The unit type is a special type that represents the lack of 9 | // a value. It's similar to void in other languages, but unit 10 | // is actually considered to be a type in F#. 11 | //--------------------------------------------------------------- 12 | [] 13 | module ``about unit`` = 14 | 15 | [] 16 | let UnitIsUsedWhenThereIsNoReturnValueForAFunction() = 17 | let sendData data = 18 | //...sending the data to the server... 19 | () 20 | 21 | let x = sendData "data" 22 | AssertEquality x __ //Don't overthink this. Note also the value "()" displays as "null" in some cases. 23 | 24 | [] 25 | let ParameterlessFunctionsTakeUnitAsTheirArgument() = 26 | let sayHello() = 27 | "hello" 28 | 29 | let result = sayHello() 30 | AssertEquality result __ 31 | -------------------------------------------------------------------------------- /FSharpKoans/AboutTheOrderOfEvaluation.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About the Order of Evaluation 6 | // 7 | // Sometimes you'll need to be explicit about the order in which 8 | // functions are evaluated. F# offers a couple mechanisms for 9 | // doing this. 10 | //--------------------------------------------------------------- 11 | [] 12 | module ``about the order of evaluation`` = 13 | 14 | [] 15 | let SometimesYouNeedParenthesisToGroupThings() = 16 | let add x y = 17 | x + y 18 | 19 | let result = add (add 5 8) (add 1 1) 20 | 21 | AssertEquality result __ 22 | 23 | (* TRY IT: What happens if you remove the parenthesis?*) 24 | 25 | [] 26 | let TheBackwardPipeOperatorCanAlsoHelpWithGrouping() = 27 | let add x y = 28 | x + y 29 | 30 | let double x = 31 | x * 2 32 | 33 | let result = double <| add 5 8 34 | 35 | AssertEquality result __ 36 | -------------------------------------------------------------------------------- /FSharpKoans.Test/GettingTheWholeOutput.fs: -------------------------------------------------------------------------------- 1 | module KoansRunner.Test.GetttingTheWholeOutput 2 | open FSharpKoans.Core 3 | open NUnit.Framework 4 | 5 | type ContainerOne() = 6 | [] 7 | static member One() = 8 | "FTW!" 9 | 10 | [] 11 | static member Two() = 12 | "FTW!" 13 | 14 | [] 15 | static member Three() = 16 | "FTW!" 17 | 18 | type ContainerTwo() = 19 | [] 20 | static member Four() = 21 | "FTW!" 22 | 23 | [] 24 | static member Five() = 25 | Assert.Fail("Expected") 26 | 27 | 28 | [] 29 | static member Six() = 30 | "FTW!" 31 | 32 | [] 33 | let ``Output contains container name followed by koan results. Stops on failure`` () = 34 | let runner = KoanRunner([| typeof; typeof |]) 35 | let result = runner.ExecuteKoans() 36 | 37 | let expected = 38 | " 39 | 40 | ContainerOne: 41 | One passed 42 | Two passed 43 | Three passed 44 | 45 | ContainerTwo: 46 | Four passed 47 | Five failed." 48 | 49 | 50 | Assert.AreEqual(expected, result.Message) -------------------------------------------------------------------------------- /FSharpKoans/AboutLooping.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Looping 6 | // 7 | // While it's more common in F# to use the Seq, List, or Array 8 | // modules to perform looping operations, you can still fall 9 | // back on traditional imperative looping techniques that you may 10 | // be more familiar with. 11 | //--------------------------------------------------------------- 12 | [] 13 | module ``about looping`` = 14 | [] 15 | let LoopingOverAList() = 16 | let values = [0..10] 17 | 18 | let mutable sum = 0 19 | for value in values do 20 | sum <- sum + value 21 | 22 | AssertEquality sum __ 23 | 24 | [] 25 | let LoopingWithExpressions() = 26 | let mutable sum = 0 27 | 28 | for i = 1 to 5 do 29 | sum <- sum + i 30 | 31 | AssertEquality sum __ 32 | 33 | [] 34 | let LoopingWithWhile() = 35 | let mutable sum = 1 36 | 37 | while sum < 10 do 38 | sum <- sum + sum 39 | 40 | AssertEquality sum __ 41 | 42 | (* NOTE: While these looping constructs can come in handy from time to time, 43 | it's often better to use a more functional approach for looping 44 | such as the functions you learned about in the List module. *) 45 | -------------------------------------------------------------------------------- /FSharpKoans.Test/FindingKoans.fs: -------------------------------------------------------------------------------- 1 | module KoansRunner.Test.FindingKoans 2 | 3 | open FSharpKoans.Core 4 | open FSharpKoans.Core.KoanContainer 5 | 6 | open NUnit.Framework 7 | 8 | open System.IO 9 | 10 | type TestContainer() = 11 | [] 12 | static member Koan1 () = 13 | () 14 | 15 | [] 16 | static member Koan2() = 17 | () 18 | 19 | let getKoanNames container = 20 | container 21 | |> findKoanMethods 22 | |> Seq.map (fun x -> x.Name) 23 | |> Seq.toList 24 | 25 | [] 26 | let ``getting koans from a container`` () = 27 | let koanNames = getKoanNames typeof 28 | let expected = [ "Koan1"; "Koan2" ] 29 | Assert.AreEqual(expected, koanNames) 30 | 31 | type TestContainer2() = 32 | [] 33 | static member Z () = 34 | () 35 | 36 | [] 37 | static member A () = 38 | () 39 | 40 | [] 41 | static member a () = 42 | () 43 | 44 | [] 45 | static member _0 () = 46 | () 47 | 48 | [] 49 | static member ``0`` () = 50 | () 51 | 52 | [] 53 | let ``Koans are returned in defined order regardless of name`` () = 54 | let koanNames = getKoanNames typeof 55 | let expected = [ "Z"; "A"; "a"; "_0"; "0" ] 56 | Assert.AreEqual(expected, koanNames) 57 | -------------------------------------------------------------------------------- /FSharpKoans/FSharpKoans.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | FSharpKoans 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 | -------------------------------------------------------------------------------- /FSharpKoans/AboutAsserts.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // Getting Started 6 | // 7 | // The F# Koans are a set of exercises designed to get you familiar 8 | // with F#. By the time you're done, you'll have a basic 9 | // understanding of the syntax of F# and learn a little more 10 | // about functional programming in general. 11 | // 12 | // Answering Problems 13 | // 14 | // This is where the fun begins! Each Koan method contains 15 | // an example designed to teach you a lesson about the F# language. 16 | // If you execute the program defined in this project, you will get 17 | // a message that the AssertEquality koan below has failed. Your 18 | // job is to fill in the blank (the __ symbol) to make it pass. Once 19 | // you make the change, re-run the program to make sure the koan 20 | // passes, and continue on to the next failing koan. With each 21 | // passing koan, you'll learn more about F#, and add another 22 | // weapon to your F# programming arsenal. 23 | //--------------------------------------------------------------- 24 | [] 25 | module ``about asserts`` = 26 | 27 | [] 28 | let AssertExpectation() = 29 | let expected_value = 1 + 1 30 | let actual_value = __ //start by changing this line 31 | 32 | AssertEquality expected_value actual_value 33 | 34 | //Easy, right? Now try one more 35 | 36 | [] 37 | let FillInValues() = 38 | AssertEquality (1 + 1) __ 39 | -------------------------------------------------------------------------------- /FSharpKoans/AboutArrays.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Arrays 6 | // 7 | // Like lists, arrays are another basic container type in F#. 8 | //--------------------------------------------------------------- 9 | [] 10 | module ``about arrays`` = 11 | [] 12 | let CreatingArrays() = 13 | let fruits = [| "apple"; "pear"; "peach"|] 14 | 15 | AssertEquality fruits.[0] __ 16 | AssertEquality fruits.[1] __ 17 | AssertEquality fruits.[2] __ 18 | 19 | [] 20 | let ArraysAreDotNetArrays() = 21 | let fruits = [| "apple"; "pear" |] 22 | 23 | let arrayType = fruits.GetType() 24 | let systemArray = System.Array.CreateInstance(typeof, 0).GetType() 25 | 26 | (* Unlike List, Arrays in F# are the standard .NET arrays that 27 | you've used to if you're coming from another .NET language *) 28 | AssertEquality arrayType systemArray 29 | 30 | [] 31 | let ArraysAreMutable() = 32 | let fruits = [| "apple"; "pear" |] 33 | fruits.[1] <- "peach" 34 | 35 | AssertEquality fruits __ 36 | 37 | [] 38 | let YouCanCreateArraysWithComprehensions() = 39 | let numbers = 40 | [| for i in 0..10 do 41 | if i % 2 = 0 then yield i |] 42 | 43 | AssertEquality numbers __ 44 | 45 | [] 46 | let ThereAreAlsoSomeOperationsYouCanPerformOnArrays() = 47 | let cube x = 48 | x * x * x 49 | 50 | let original = [| 0..5 |] 51 | let result = Array.map cube original 52 | 53 | AssertEquality original __ 54 | AssertEquality result __ 55 | -------------------------------------------------------------------------------- /FSharpKoans/AboutDiscriminatedUnions.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | type Condiment = 5 | | Mustard 6 | | Ketchup 7 | | Relish 8 | | Vinegar 9 | 10 | type Favorite = 11 | | Bourbon of string 12 | | Number of int 13 | 14 | //--------------------------------------------------------------- 15 | // About Discriminated Unions 16 | // 17 | // Discriminated unions are used to represent data that can be 18 | // one of a fixed number of cases. To start off, you can think 19 | // of them like a much more powerful version of enums in other 20 | // languages. 21 | //--------------------------------------------------------------- 22 | [] 23 | module ``about discriminated unions`` = 24 | [] 25 | let DiscriminatedUnionsCaptureASetOfOptions() = 26 | 27 | let toColor condiment = 28 | match condiment with 29 | | Mustard -> "yellow" 30 | | Ketchup -> "red" 31 | | Relish -> "green" 32 | | Vinegar -> "brownish?" 33 | 34 | let choice = Mustard 35 | 36 | AssertEquality (toColor choice) __ 37 | 38 | (* TRY IT: What happens if you remove a case from the above pattern 39 | match? *) 40 | 41 | [] 42 | let DiscriminatedUnionCasesCanHaveTypes() = 43 | 44 | let saySomethingAboutYourFavorite favorite = 45 | match favorite with 46 | | Number 7 -> "me too!" 47 | | Bourbon "Bookers" -> "me too!" 48 | | Bourbon b -> "I prefer Bookers to " + b 49 | | Number _ -> "I'm partial to 7" 50 | 51 | let bourbonResult = saySomethingAboutYourFavorite <| Bourbon "Maker's Mark" 52 | let numberResult = saySomethingAboutYourFavorite <| Number 7 53 | 54 | AssertEquality bourbonResult __ 55 | AssertEquality numberResult __ 56 | -------------------------------------------------------------------------------- /FSharpKoans/AboutModules.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | module MushroomKingdom = 5 | type Power = 6 | | Mushroom 7 | | Star 8 | | FireFlower 9 | 10 | type Character = { 11 | Name: string 12 | Occupation: string 13 | Power: Power option 14 | } 15 | 16 | let Mario = { Name = "Mario"; Occupation = "Plumber"; Power = None} 17 | 18 | let powerUp character = 19 | { character with Power = Some Mushroom } 20 | 21 | //--------------------------------------------------------------- 22 | // About Modules 23 | // 24 | // Modules are used to group functions, values, and types. 25 | // They're similar to .NET namespaces, but they have slightly 26 | // different semantics as you'll see below. 27 | //--------------------------------------------------------------- 28 | [] 29 | module ``about modules`` = 30 | 31 | [] 32 | let ModulesCanContainValuesAndTypes() = 33 | 34 | AssertEquality MushroomKingdom.Mario.Name __ 35 | AssertEquality MushroomKingdom.Mario.Occupation __ 36 | 37 | let moduleType = MushroomKingdom.Mario.GetType() 38 | AssertEquality moduleType typeof 39 | 40 | [] 41 | let ModulesCanContainFunctions() = 42 | let superMario = MushroomKingdom.powerUp MushroomKingdom.Mario 43 | 44 | AssertEquality superMario.Power __ 45 | 46 | (* NOTE: In previous sections, you've seen modules like List and Option that 47 | contain useful functions for dealing with List types and Option types 48 | respectively. *) 49 | 50 | open MushroomKingdom 51 | 52 | [] 53 | module ``about opened modules`` = 54 | [] 55 | let OpenedModulesBringTheirContentsInScope() = 56 | AssertEquality Mario.Name __ 57 | AssertEquality Mario.Occupation __ 58 | AssertEquality Mario.Power __ -------------------------------------------------------------------------------- /FSharpKoans.Core/KoanRunner.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans.Core 2 | 3 | open System 4 | open System.Reflection 5 | 6 | type KoanRunner(containers) = 7 | let findFailureOrLastResult (values:seq) = 8 | let e = values.GetEnumerator() 9 | let rec helper() = 10 | let last = e.Current 11 | if e.MoveNext() then 12 | match e.Current with 13 | | Success _ -> helper() 14 | | Failure _ -> e.Current 15 | else 16 | last 17 | helper() 18 | 19 | let buildKoanResult builderString (current:KoanResult) (next:KoanResult) = 20 | next 21 | |> KoanResult.map (sprintf builderString current.Message) 22 | 23 | let getContainerResult (container: Type) = 24 | let name = container.Name 25 | let lineOne = sprintf "%s:" name 26 | 27 | container |> KoanContainer.runKoans 28 | |> Seq.scan (buildKoanResult "%s\r\n %s") (Success lineOne) 29 | |> findFailureOrLastResult 30 | 31 | new () = 32 | let containers = 33 | Assembly.GetCallingAssembly().GetTypes() 34 | |> Array.map (fun t -> t, t.GetCustomAttributes(typeof, true)) 35 | |> Array.filter (not << Seq.isEmpty << snd) 36 | |> Array.sortBy (fun (t, attrs) -> (attrs.[0] :?> KoanAttribute).Sort) 37 | |> Array.map fst 38 | KoanRunner(containers) 39 | 40 | member this.ExecuteKoans() = 41 | let result = 42 | containers 43 | |> Seq.map getContainerResult 44 | |> Seq.scan (buildKoanResult "%s\r\n\r\n%s") (Success "") 45 | |> findFailureOrLastResult 46 | 47 | match result with 48 | | Success m -> Success (m) //m.Replace("\n", Environment.NewLine)) 49 | | Failure (m, e) -> Failure (m, e) //(m.Replace("\n", Environment.NewLine), e) 50 | -------------------------------------------------------------------------------- /FSharpKoans/AboutPipelining.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //---------------------------------------------------------------- 5 | // About Pipelining 6 | // 7 | // The forward pipe operator is one of the most commonly used 8 | // symbols in F# programming. You can use it to combine operations 9 | // on lists and other data structures in a readable way. 10 | //---------------------------------------------------------------- 11 | [] 12 | module ``about pipelining`` = 13 | 14 | let square x = 15 | x * x 16 | 17 | let isEven x = 18 | x % 2 = 0 19 | 20 | [] 21 | let SquareEvenNumbersWithSeparateStatements() = 22 | (* One way to combine the operations is by using separate statements. 23 | However, this can be clumsy since you have to name each result. *) 24 | 25 | let numbers = [0..5] 26 | 27 | let evens = List.filter isEven numbers 28 | let result = List.map square evens 29 | 30 | AssertEquality result __ 31 | 32 | [] 33 | let SquareEvenNumbersWithParens() = 34 | (* You can avoid this problem by using parens to pass the result of one 35 | function to another. This can be difficult to read since you have to 36 | start from the innermost function and work your way out. *) 37 | 38 | let numbers = [0..5] 39 | 40 | let result = List.map square (List.filter isEven numbers) 41 | 42 | AssertEquality result __ 43 | 44 | [] 45 | let SquareEvenNumbersWithPipelineOperator() = 46 | (* In F#, you can use the pipeline operator to get the benefit of the 47 | parens style with the readability of the statement style. *) 48 | 49 | let result = 50 | [0..5] 51 | |> List.filter isEven 52 | |> List.map square 53 | 54 | AssertEquality result __ 55 | 56 | [] 57 | let HowThePipeOperatorIsDefined() = 58 | let (|>) x f = 59 | f x 60 | 61 | let result = 62 | [0..5] 63 | |> List.filter isEven 64 | |> List.map square 65 | 66 | AssertEquality result __ 67 | -------------------------------------------------------------------------------- /FSharpKoans/AboutClasses.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Classes 6 | // 7 | // As a full fledged Object Oriented language, F# allows you to 8 | // create traditional classes to contain data and methods. 9 | //--------------------------------------------------------------- 10 | 11 | type Zombie() = 12 | member this.FavoriteFood = "brains" 13 | 14 | member this.Eat food = 15 | match food with 16 | | "brains" -> "mmmmmmmmmmmmmmm" 17 | | _ -> "grrrrrrrr" 18 | 19 | type Person(name:string) = 20 | member this.Speak() = 21 | "Hi my name is " + name 22 | 23 | type Zombie2() = 24 | let favoriteFood = "brains" 25 | 26 | member this.Eat food = 27 | if food = favoriteFood then "mmmmmmmmmmmmmmm" else "grrrrrrrr" 28 | 29 | type Person2(name:string) = 30 | let mutable internalName = name 31 | 32 | member this.Name 33 | with get() = internalName 34 | and set(value) = internalName <- value 35 | 36 | member this.Speak() = 37 | "Hi my name is " + this.Name 38 | 39 | [] 40 | module ``about classes`` = 41 | 42 | [] 43 | let ClassesCanHaveProperties() = 44 | let zombie = new Zombie() 45 | 46 | AssertEquality zombie.FavoriteFood __ 47 | 48 | [] 49 | let ClassesCanHaveMethods() = 50 | let zombie = new Zombie() 51 | 52 | let result = zombie.Eat "brains" 53 | AssertEquality result __ 54 | 55 | [] 56 | let ClassesCanHaveConstructors() = 57 | 58 | let person = new Person("Shaun") 59 | 60 | let result = person.Speak() 61 | AssertEquality result __ 62 | 63 | [] 64 | let ClassesCanHaveLetBindingsInsideThem() = 65 | let zombie = new Zombie2() 66 | 67 | let result = zombie.Eat "chicken" 68 | AssertEquality result __ 69 | 70 | (* TRY IT: Can you access the let bound value Zombie2.favoriteFood 71 | outside of the class definition? *) 72 | 73 | [] 74 | let ClassesCanHaveReadWriteProperties() = 75 | let person = new Person2("Shaun") 76 | 77 | let firstPhrase = person.Speak() 78 | AssertEquality firstPhrase __ 79 | 80 | person.Name <- "Shaun of the Dead" 81 | let secondPhrase = person.Speak() 82 | AssertEquality secondPhrase __ 83 | -------------------------------------------------------------------------------- /FSharpKoans/AboutFunctions.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Functions 6 | // 7 | // Now that you've seen how to bind a name to a value with let, 8 | // you'll learn to use the let keyword to create functions. 9 | //--------------------------------------------------------------- 10 | [] 11 | module ``about functions`` = 12 | 13 | (* By default, F# is whitespace sensitive. 14 | For functions, this means that the last 15 | line of a function is its return value, 16 | and the body of a function is denoted 17 | by indentation. *) 18 | 19 | let add x y = 20 | x + y 21 | 22 | [] 23 | let CreatingFunctionsWithLet() = 24 | let result1 = add 2 2 25 | let result2 = add 5 2 26 | 27 | AssertEquality result1 __ 28 | AssertEquality result2 __ 29 | 30 | [] 31 | let NestingFunctions() = 32 | let quadruple x = 33 | let double x = 34 | x * 2 35 | 36 | double(double(x)) 37 | 38 | let result = quadruple 4 39 | AssertEquality result __ 40 | 41 | [] 42 | let AddingTypeAnnotations() = 43 | 44 | (* Sometimes you need to help F#'s type inference system out with an 45 | explicit type annotation *) 46 | 47 | let sayItLikeAnAuctioneer (text:string) = 48 | text.Replace(" ", "") 49 | 50 | let auctioneered = sayItLikeAnAuctioneer "going once going twice sold to the lady in red" 51 | AssertEquality auctioneered __ 52 | 53 | //TRY IT: What happens if you remove the type annotation on text? 54 | 55 | [] 56 | let VariablesInTheParentScopeCanBeAccessed() = 57 | let suffix = "!!!" 58 | 59 | let caffeinate (text:string) = 60 | let exclaimed = text.Trim() + suffix 61 | let yelled = exclaimed.ToUpper() 62 | yelled 63 | 64 | let caffeinatedReply = caffeinate "hello there" 65 | 66 | AssertEquality caffeinatedReply __ 67 | 68 | (* NOTE: Accessing the suffix variable in the nested caffeinate function 69 | is known as a closure. 70 | 71 | See http://en.wikipedia.org/wiki/Closure_(computer_science) 72 | for more about about closure. *) 73 | -------------------------------------------------------------------------------- /FSharpKoans/AboutOptionTypes.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | type Game = { 5 | Name: string 6 | Platform: string 7 | Score: int option 8 | } 9 | 10 | //--------------------------------------------------------------- 11 | // About Option Types 12 | // 13 | // Option Types are used to represent calculations that may or 14 | // may not return a value. You may be used to using null for this 15 | // in other languages. However, using option types instead of nulls 16 | // has subtle but far reaching benefits. 17 | //--------------------------------------------------------------- 18 | [] 19 | module ``about option types`` = 20 | 21 | [] 22 | let OptionTypesMightContainAValue() = 23 | let someValue = Some 10 24 | 25 | AssertEquality someValue.IsSome __ 26 | AssertEquality someValue.IsNone __ 27 | AssertEquality someValue.Value __ 28 | 29 | [] 30 | let OrTheyMightNot() = 31 | let noValue = None 32 | 33 | AssertEquality noValue.IsSome __ 34 | AssertEquality noValue.IsNone __ 35 | AssertThrows (fun () -> noValue.Value) 36 | 37 | [] 38 | let UsingOptionTypesWithPatternMatching() = 39 | let chronoTrigger = { Name = "Chrono Trigger"; Platform = "SNES"; Score = Some 5 } 40 | let halo = { Name = "Halo"; Platform = "Xbox"; Score = None } 41 | 42 | let translate score = 43 | match score with 44 | | 5 -> "Great" 45 | | 4 -> "Good" 46 | | 3 -> "Decent" 47 | | 2 -> "Bad" 48 | | 1 -> "Awful" 49 | | _ -> "Unknown" 50 | 51 | let getScore game = 52 | match game.Score with 53 | | Some score -> translate score 54 | | None -> "Unknown" 55 | 56 | AssertEquality (getScore chronoTrigger) __ 57 | AssertEquality (getScore halo) __ 58 | 59 | [] 60 | let ProjectingValuesFromOptionTypes() = 61 | let chronoTrigger = { Name = "Chrono Trigger"; Platform = "SNES"; Score = Some 5 } 62 | let halo = { Name = "Halo"; Platform = "Xbox"; Score = None } 63 | 64 | let decideOn game = 65 | 66 | game.Score 67 | |> Option.map (fun score -> if score > 3 then "play it" else "don't play") 68 | 69 | //HINT: look at the return type of the decideOn function 70 | AssertEquality (decideOn chronoTrigger) __ 71 | AssertEquality (decideOn halo) __ 72 | -------------------------------------------------------------------------------- /FSharpKoans/AboutRecordTypes.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | type Character = { 5 | Name: string 6 | Occupation: string 7 | } 8 | 9 | //--------------------------------------------------------------- 10 | // About Record Types 11 | // 12 | // Record types are lightweight ways to construct new types. 13 | // You can use them to group data in a more structured way than 14 | // tuples. 15 | //--------------------------------------------------------------- 16 | [] 17 | module ``about record types`` = 18 | 19 | [] 20 | let RecordsHaveProperties() = 21 | let mario = { Name = "Mario"; Occupation = "Plumber"; } 22 | 23 | AssertEquality mario.Name __ 24 | AssertEquality mario.Occupation __ 25 | 26 | [] 27 | let CreatingFromAnExistingRecord() = 28 | let mario = { Name = "Mario"; Occupation = "Plumber"; } 29 | let luigi = { mario with Name = "Luigi"; } 30 | 31 | AssertEquality mario.Name __ 32 | AssertEquality mario.Occupation __ 33 | 34 | AssertEquality luigi.Name __ 35 | AssertEquality luigi.Occupation __ 36 | 37 | [] 38 | let ComparingRecords() = 39 | let greenKoopa = { Name = "Koopa"; Occupation = "Soldier"; } 40 | let bowser = { Name = "Bowser"; Occupation = "Kidnapper"; } 41 | let redKoopa = { Name = "Koopa"; Occupation = "Soldier"; } 42 | 43 | let koopaComparison = 44 | if greenKoopa = redKoopa then 45 | "all the koopas are pretty much the same" 46 | else 47 | "maybe one can fly" 48 | 49 | let bowserComparison = 50 | if bowser = greenKoopa then 51 | "the king is a pawn" 52 | else 53 | "he is still kind of a koopa" 54 | 55 | AssertEquality koopaComparison __ 56 | AssertEquality bowserComparison __ 57 | 58 | [] 59 | let YouCanPatternMatchAgainstRecords() = 60 | let mario = { Name = "Mario"; Occupation = "Plumber"; } 61 | let luigi = { Name = "Luigi"; Occupation = "Plumber"; } 62 | let bowser = { Name = "Bowser"; Occupation = "Kidnapper"; } 63 | 64 | let determineSide character = 65 | match character with 66 | | { Occupation = "Plumber" } -> "good guy" 67 | | _ -> "bad guy" 68 | 69 | AssertEquality (determineSide mario) __ 70 | AssertEquality (determineSide luigi) __ 71 | AssertEquality (determineSide bowser) __ 72 | -------------------------------------------------------------------------------- /FSharpKoans.Test/RunningKoans.fs: -------------------------------------------------------------------------------- 1 | module KoansRunner.Test.RunningKoans 2 | 3 | open FSharpKoans.Core 4 | open NUnit.Framework 5 | 6 | type FailureContainer() = 7 | [] 8 | static member FailureKoan() = 9 | Assert.Fail("expected failure") 10 | 11 | type SuccessContainer() = 12 | [] 13 | static member SuccessKoan() = 14 | "FTW!" 15 | 16 | type SomeSuccesses() = 17 | [] 18 | static member One() = 19 | "YAY" 20 | 21 | [] 22 | static member Two() = 23 | "WOOT" 24 | 25 | type MixedBag() = 26 | [] 27 | static member One() = 28 | Assert.Fail("Game over") 29 | 30 | [] 31 | static member Two() = 32 | "OH YEAH!" 33 | 34 | [] 35 | let ``A failing koan returns its exception`` () = 36 | let result = 37 | typeof 38 | |> KoanContainer.runKoans 39 | |> Seq.head 40 | 41 | let ex = 42 | match result with 43 | | Failure (_, ex) -> ex 44 | | _ -> null 45 | 46 | Assert.AreEqual("expected failure", ex.Message) 47 | 48 | [] 49 | let ``A failing koan returns a failure message`` () = 50 | let result = 51 | typeof 52 | |> KoanContainer.runKoans 53 | |> Seq.head 54 | 55 | Assert.AreEqual("FailureKoan failed.", result.Message) 56 | 57 | [] 58 | let ``A successful koans returns a success message`` () = 59 | let result = 60 | typeof 61 | |> KoanContainer.runKoans 62 | |> Seq.head 63 | 64 | Assert.AreEqual("SuccessKoan passed", result.Message) 65 | 66 | [] 67 | let ``The outcome of all successful koans is returned`` () = 68 | let result = 69 | typeof 70 | |> KoanContainer.runKoans 71 | |> Seq.map (fun x -> x.Message) 72 | |> Seq.reduce (fun x y -> x + System.Environment.NewLine + y) 73 | 74 | let expected = 75 | "One passed" + System.Environment.NewLine + 76 | "Two passed" 77 | 78 | Assert.AreEqual(expected, result) 79 | 80 | [] 81 | //might want to change this behavior 82 | let ``Failed Koans don't stop the enumeration`` () = 83 | let result = 84 | typeof 85 | |> KoanContainer.runKoans 86 | |> Seq.map (fun x -> x.Message) 87 | |> Seq.reduce (fun x y -> x + System.Environment.NewLine + y) 88 | 89 | 90 | let expected = 91 | "One failed." + System.Environment.NewLine + 92 | "Two passed" 93 | 94 | Assert.AreEqual(expected, result) 95 | -------------------------------------------------------------------------------- /FSharpKoans/AboutDotNetCollections.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | open System.Collections.Generic 4 | 5 | //--------------------------------------------------------------- 6 | // About .NET Collections 7 | // 8 | // Since F# is built for seamless interop with other CLR 9 | // languages, you can use all of the basic .NET collections types 10 | // you're already familiar with if you're a C# or VB programmer. 11 | //--------------------------------------------------------------- 12 | [] 13 | module ``about dot net collections`` = 14 | 15 | [] 16 | let CreatingDotNetLists() = 17 | let fruits = new List() 18 | 19 | fruits.Add("apple") 20 | fruits.Add("pear") 21 | 22 | AssertEquality fruits.[0] __ 23 | AssertEquality fruits.[1] __ 24 | 25 | [] 26 | let CreatingDotNetDictionaries() = 27 | let addressBook = new Dictionary() 28 | 29 | addressBook.["Chris"] <- "Ann Arbor" 30 | addressBook.["SkillsMatter"] <- "London" 31 | 32 | AssertEquality addressBook.["Chris"] __ 33 | AssertEquality addressBook.["SkillsMatter"] __ 34 | 35 | [] 36 | let YouUseCombinatorsWithDotNetTypes() = 37 | let addressBook = new Dictionary() 38 | 39 | addressBook.["Chris"] <- "Ann Arbor" 40 | addressBook.["SkillsMatter"] <- "London" 41 | 42 | let verboseBook = 43 | addressBook 44 | |> Seq.map (fun kvp -> sprintf "Name: %s - City: %s" kvp.Key kvp.Value) 45 | |> Seq.toArray 46 | 47 | //NOTE: The seq type in F# is an alias for .NET's IEnumerable interface 48 | // Like the List and Array module, the Seq module contains functions 49 | // that you can combine to perform operations on types implementing 50 | // seq/IEnumerable. 51 | 52 | AssertEquality verboseBook.[0] __ 53 | AssertEquality verboseBook.[1] __ 54 | 55 | [] 56 | let SkippingElements() = 57 | let original = [0..5] 58 | let result = Seq.skip 2 original 59 | 60 | AssertEquality result __ 61 | 62 | [] 63 | let FindingTheMax() = 64 | let values = new List() 65 | 66 | values.Add(11) 67 | values.Add(20) 68 | values.Add(4) 69 | values.Add(2) 70 | values.Add(3) 71 | 72 | let result = Seq.max values 73 | 74 | AssertEquality result __ 75 | 76 | [] 77 | let FindingTheMaxUsingACondition() = 78 | let getNameLength (name:string) = 79 | name.Length 80 | 81 | let names = [| "Harry"; "Lloyd"; "Nicholas"; "Mary"; "Joe"; |] 82 | let result = Seq.maxBy getNameLength names 83 | 84 | AssertEquality result __ 85 | -------------------------------------------------------------------------------- /FSharpKoans/AboutTuples.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Tuples 6 | // 7 | // Tuples are used to easily group together values in F#. They're 8 | // another fundamental construct of the language. 9 | //--------------------------------------------------------------- 10 | [] 11 | module ``about tuples`` = 12 | 13 | [] 14 | let CreatingTuples() = 15 | let items = ("apple", "dog") 16 | 17 | AssertEquality items ("apple", __) 18 | 19 | [] 20 | let AccessingTupleElements() = 21 | let items = ("apple", "dog") 22 | 23 | let fruit = fst items 24 | let animal = snd items 25 | 26 | AssertEquality fruit __ 27 | AssertEquality animal __ 28 | 29 | [] 30 | let AccessingTupleElementsWithPatternMatching() = 31 | 32 | (* fst and snd are useful in some situations, but they only work with 33 | tuples containing two elements. It's usually better to use a 34 | technique called pattern matching to access the values of a tuple. 35 | 36 | Pattern matching works with tuples of any arity, and it allows you to 37 | simultaneously break apart the tuple while assigning a name to each 38 | value. Here's an example. *) 39 | 40 | let items = ("apple", "dog", "Mustang") 41 | 42 | let fruit, animal, car = items 43 | 44 | AssertEquality fruit __ 45 | AssertEquality animal __ 46 | AssertEquality car __ 47 | 48 | [] 49 | let IgnoringValuesWithPatternMatching() = 50 | let items = ("apple", "dog", "Mustang") 51 | 52 | let _, animal, _ = items 53 | 54 | AssertEquality animal __ 55 | 56 | (* NOTE: pattern matching is found in many places 57 | throughout F#, and we'll revisit it again later *) 58 | 59 | [] 60 | let ReturningMultipleValuesFromAFunction() = 61 | let squareAndCube x = 62 | (x ** 2.0, x ** 3.0) 63 | 64 | let squared, cubed = squareAndCube 3.0 65 | 66 | 67 | AssertEquality squared __ 68 | AssertEquality cubed __ 69 | 70 | (* THINK ABOUT IT: Is there really more than one return value? 71 | What type does the squareAndCube function 72 | return? *) 73 | 74 | [] 75 | let TheTruthBehindMultipleReturnValues() = 76 | let squareAndCube x = 77 | (x ** 2.0, x ** 3.0) 78 | 79 | let result = squareAndCube 3.0 80 | 81 | AssertEquality result __ 82 | -------------------------------------------------------------------------------- /FSharpKoans/AboutLet.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Let 6 | // 7 | // The let keyword is one of the most fundamental parts of F#. 8 | // You'll use it in almost every line of F# code you write, so 9 | // let's get to know it well! (no pun intended) 10 | //--------------------------------------------------------------- 11 | [] 12 | module ``about let`` = 13 | 14 | [] 15 | let LetBindsANameToAValue() = 16 | let x = 50 17 | 18 | AssertEquality x __ 19 | 20 | (* In F#, values created with let are inferred to have a type like 21 | "int" for integer values, "string" for text values, and "bool" 22 | for true or false values. *) 23 | [] 24 | let LetInfersTheTypesOfValuesWherePossible() = 25 | let x = 50 26 | let typeOfX = x.GetType() 27 | AssertEquality typeOfX typeof 28 | 29 | let y = "a string" 30 | let expectedType = y.GetType() 31 | AssertEquality expectedType typeof 32 | 33 | [] 34 | let YouCanMakeTypesExplicit() = 35 | let (x:int) = 42 36 | let typeOfX = x.GetType() 37 | 38 | let y:string = "forty two" 39 | let typeOfY = y.GetType() 40 | 41 | AssertEquality typeOfX typeof 42 | AssertEquality typeOfY typeof 43 | 44 | (* You don't usually need to provide explicit type annotations types for 45 | local variables, but type annotations can come in handy in other 46 | contexts as you'll see later. *) 47 | 48 | [] 49 | let FloatsAndInts() = 50 | (* Depending on your background, you may be surprised to learn that 51 | in F#, integers and floating point numbers are different types. 52 | In other words, the following is true. *) 53 | let x = 20 54 | let typeOfX = x.GetType() 55 | 56 | let y = 20.0 57 | let typeOfY = y.GetType() 58 | 59 | //you don't need to modify these 60 | AssertEquality typeOfX typeof 61 | AssertEquality typeOfY typeof 62 | 63 | //If you're coming from another .NET language, float is F# slang for 64 | //the double type. 65 | 66 | [] 67 | let ModifyingTheValueOfVariables() = 68 | let mutable x = 100 69 | x <- 200 70 | 71 | AssertEquality x __ 72 | 73 | [] 74 | let YouCannotModifyALetBoundValueIfItIsNotMutable() = 75 | let x = 50 76 | 77 | //What happens if you uncomment the following? 78 | // 79 | //x <- 100 80 | 81 | //NOTE: Although you can't modify immutable values, it is possible 82 | // to reuse the name of a value in some cases using "shadowing". 83 | let x = 100 84 | 85 | AssertEquality x __ 86 | -------------------------------------------------------------------------------- /FSharpKoans/AboutBranching.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Branching 6 | // 7 | // Branching is used to tell a program to conditionally perform 8 | // an operation. It's another fundamental part of F#. 9 | //--------------------------------------------------------------- 10 | [] 11 | module ``about branching`` = 12 | 13 | [] 14 | let BasicBranching() = 15 | let isEven x = 16 | if x % 2 = 0 then 17 | "it's even!" 18 | else 19 | "it's odd!" 20 | 21 | let result = isEven 2 22 | AssertEquality result __ 23 | 24 | [] 25 | let IfStatementsReturnValues() = 26 | 27 | (* In languages like C#, if statements do not yield results; they can 28 | only cause side effects. If statements in F# return values due to 29 | F#'s functional programming roots. *) 30 | 31 | let result = 32 | if 2 = 3 then 33 | "something is REALLY wrong" 34 | else 35 | "no problem here" 36 | 37 | AssertEquality result __ 38 | 39 | [] 40 | let BranchingWithAPatternMatch() = 41 | let isApple x = 42 | match x with 43 | | "apple" -> true 44 | | _ -> false 45 | 46 | let result1 = isApple "apple" 47 | let result2 = isApple "" 48 | 49 | AssertEquality result1 __ 50 | AssertEquality result2 __ 51 | 52 | [] 53 | let UsingTuplesWithIfStatementsQuicklyBecomesClumsy() = 54 | 55 | let getDinner x = 56 | let name, foodChoice = x 57 | 58 | if foodChoice = "veggies" || foodChoice ="fish" || 59 | foodChoice = "chicken" then 60 | sprintf "%s doesn't want red meat" name 61 | else 62 | sprintf "%s wants 'em some %s" name foodChoice 63 | 64 | let person1 = ("Chris", "steak") 65 | let person2 = ("Dave", "veggies") 66 | 67 | AssertEquality (getDinner person1) __ 68 | AssertEquality (getDinner person2) __ 69 | 70 | [] 71 | let PatternMatchingIsNicer() = 72 | 73 | let getDinner x = 74 | match x with 75 | | (name, "veggies") 76 | | (name, "fish") 77 | | (name, "chicken") -> sprintf "%s doesn't want red meat" name 78 | | (name, foodChoice) -> sprintf "%s wants 'em some %s" name foodChoice 79 | 80 | let person1 = ("Bob", "fish") 81 | let person2 = ("Sally", "Burger") 82 | 83 | AssertEquality (getDinner person1) __ 84 | AssertEquality (getDinner person2) __ 85 | -------------------------------------------------------------------------------- /FSharpKoans/AboutStrings.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // About Strings 6 | // 7 | // Most languages have a set of utilities for manipulating 8 | // strings. F# is no different. 9 | //--------------------------------------------------------------- 10 | [] 11 | module ``about strings`` = 12 | 13 | [] 14 | let StringValue() = 15 | let message = "hello" 16 | 17 | AssertEquality message __ 18 | 19 | [] 20 | let StringConcatValue() = 21 | let message = "hello " + "world" 22 | 23 | AssertEquality message __ 24 | 25 | [] 26 | let FormattingStringValues() = 27 | let message = sprintf "F# turns it to %d!" 11 28 | 29 | AssertEquality message __ 30 | 31 | //NOTE: you can use printf to print to standard output 32 | 33 | (* TRY IT: What happens if you change 11 to be something besides 34 | a number? *) 35 | 36 | [] 37 | let FormattingOtherTypes() = 38 | let message = sprintf "hello %s" "world" 39 | 40 | AssertEquality message __ 41 | 42 | [] 43 | let FormattingAnything() = 44 | let message = sprintf "Formatting other types is as easy as: %A" (1, 2, 3) 45 | 46 | AssertEquality message __ 47 | 48 | (* NOTE: For all the %formatters that you can use with string formatting 49 | see: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting *) 50 | 51 | [] 52 | let CombineMultiline() = 53 | let message = "super\ 54 | cali\ 55 | fragilistic\ 56 | expiali\ 57 | docious" 58 | 59 | AssertEquality message __ 60 | 61 | [] 62 | let Multiline() = 63 | let message = "This 64 | is 65 | on 66 | five 67 | lines" 68 | 69 | AssertEquality 70 | message __ 71 | 72 | [] 73 | let ExtractValues() = 74 | let message = "hello world" 75 | 76 | let first = message.[0] 77 | let other = message.[4] 78 | 79 | (* A single character is denoted using single quotes, example: 'c', 80 | not double quotes as you would use for a string *) 81 | 82 | AssertEquality first __ 83 | AssertEquality other __ 84 | 85 | [] 86 | let ApplyWhatYouLearned() = 87 | (* It's time to apply what you've learned so far. Fill in the function below to 88 | make the asserts pass *) 89 | let getFunFacts x = 90 | __ 91 | 92 | let funFactsAboutThree = getFunFacts 3 93 | let funFactsAboutSix = getFunFacts 6 94 | 95 | AssertEquality "3 doubled is 6, and 3 tripled is 9!" funFactsAboutThree 96 | AssertEquality "6 doubled is 12, and 6 tripled is 18!" funFactsAboutSix 97 | -------------------------------------------------------------------------------- /FSharpKoans/AboutTheStockExample.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // Apply Your Knowledge! 6 | // 7 | // Below is a list containing comma separated data about 8 | // Microsoft's stock prices during March of 2012. Without 9 | // modifying the list, programatically find the day with the 10 | // greatest difference between the opening and closing prices. 11 | // 12 | // The following functions may be of use: 13 | // 14 | // abs - takes the absolute value of an argument 15 | // 16 | // System.Double.Parse - converts a string argument into a 17 | // numerical value. 18 | // 19 | // Hint: Use CultureInfo.InvariantCulture to always parse '.' as 20 | // the decimal point. 21 | // 22 | // The following function will convert a comma separated string 23 | // into an array of the column values. 24 | // 25 | // let splitCommas (x:string) = 26 | // x.Split([|','|]) 27 | //--------------------------------------------------------------- 28 | [] 29 | module ``about the stock example`` = 30 | 31 | let stockData = 32 | [ "Date,Open,High,Low,Close,Volume,Adj Close"; 33 | "2012-03-30,32.40,32.41,32.04,32.26,31749400,32.26"; 34 | "2012-03-29,32.06,32.19,31.81,32.12,37038500,32.12"; 35 | "2012-03-28,32.52,32.70,32.04,32.19,41344800,32.19"; 36 | "2012-03-27,32.65,32.70,32.40,32.52,36274900,32.52"; 37 | "2012-03-26,32.19,32.61,32.15,32.59,36758300,32.59"; 38 | "2012-03-23,32.10,32.11,31.72,32.01,35912200,32.01"; 39 | "2012-03-22,31.81,32.09,31.79,32.00,31749500,32.00"; 40 | "2012-03-21,31.96,32.15,31.82,31.91,37928600,31.91"; 41 | "2012-03-20,32.10,32.15,31.74,31.99,41566800,31.99"; 42 | "2012-03-19,32.54,32.61,32.15,32.20,44789200,32.20"; 43 | "2012-03-16,32.91,32.95,32.50,32.60,65626400,32.60"; 44 | "2012-03-15,32.79,32.94,32.58,32.85,49068300,32.85"; 45 | "2012-03-14,32.53,32.88,32.49,32.77,41986900,32.77"; 46 | "2012-03-13,32.24,32.69,32.15,32.67,48951700,32.67"; 47 | "2012-03-12,31.97,32.20,31.82,32.04,34073600,32.04"; 48 | "2012-03-09,32.10,32.16,31.92,31.99,34628400,31.99"; 49 | "2012-03-08,32.04,32.21,31.90,32.01,36747400,32.01"; 50 | "2012-03-07,31.67,31.92,31.53,31.84,34340400,31.84"; 51 | "2012-03-06,31.54,31.98,31.49,31.56,51932900,31.56"; 52 | "2012-03-05,32.01,32.05,31.62,31.80,45240000,31.80"; 53 | "2012-03-02,32.31,32.44,32.00,32.08,47314200,32.08"; 54 | "2012-03-01,31.93,32.39,31.85,32.29,77344100,32.29"; 55 | "2012-02-29,31.89,32.00,31.61,31.74,59323600,31.74"; ] 56 | 57 | // Feel free to add extra [] members here to write 58 | // tests for yourself along the way. You can also try 59 | // using the F# Interactive window to check your progress. 60 | 61 | [] 62 | let YouGotTheAnswerCorrect() = 63 | let result = __ 64 | 65 | AssertEquality "2012-03-13" result 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/ChrisMarinos/FSharpKoans) 2 | 3 | # Functional Koans - F# # 4 | 5 | Inspired by EdgeCase's fantastic [Ruby koans](http://github.com/edgecase/ruby_koans), 6 | the goal of the F# koans is to teach you F# through testing. 7 | 8 | When you first run the koans, you'll be presented with a runtime error and a 9 | stack trace indicating where the error occurred. Your goal is to make the 10 | error go away. As you fix each error, you should learn something about 11 | the F# language and functional programming in general. 12 | 13 | Your journey towards F# enlightenment starts in the AboutAsserts.fs file. These 14 | koans will be very simple, so don't overthink them! As you progress through 15 | more koans, more and more F# syntax will be introduced which will allow 16 | you to solve more complicated problems and use more advanced techniques. 17 | 18 | ### Running with GitHub Codespaces 19 | [GitHub Codespaces](https://github.com/features/codespaces) is a free platform to run F# Koans completely in your browser, no install or setup required! Just go 20 | to https://github.com/ChrisMarinos/FSharpKoans/codespaces and click "New CodeSpace" 21 | 22 | ### Running with Docker 23 | 24 | To launch in watch mode using docker run the following command; 25 | 26 | `$ ./docker.sh` 27 | 28 | ### Prerequisites 29 | 30 | The F# Koans needs [.NET 6.0](https://dotnet.microsoft.com/download) to be built and run. Make sure that you have installed it before building the project. This is the long-term support release of .NET that many modern F# and .NET applications use. 31 | 32 | Additionally, the project provides [Visual Studio Code](https://code.visualstudio.com/) configuration for running. 33 | To be able to run F# projects in Visual Studio Code, the 34 | [Ionide plugin](https://marketplace.visualstudio.com/items?itemName=Ionide.Ionide-fsharp) should be also installed. 35 | 36 | ### Running the Koans from the command line (.Net Core) 37 | 38 | 1. To build the Koans, run `dotnet build` command in the project root. 39 | 40 | 2. To run the Koans, run `dotnet run --project FSharpKoans/FSharpKoans.fsproj` in the root directory or `dotnet run` in `FSharpKoans` project directory. 41 | 42 | ### Running the Koans in Visual Studio Code 43 | 44 | 1. Open the project directory in Visual Studio Code with Ionide-fsharp plugin installed 45 | and press F5 to build and launch the Koans (some time is needed to build the project before launch). 46 | 47 | ### Running the Koans from a Devcontainer 48 | 49 | 1. Install the Remote - Containers extension in Visual Studio Code. 50 | 51 | 2. Open the directory inside a Devcontainer. 52 | 53 | 3. Open a terminal and start using the Koans. 54 | 55 | ### Using dotnet-watch 56 | 57 | You can also use [dotnet-watch](https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/tutorials/dotnet-watch.md) to have your changes reloaded automatically. 58 | To do so, navigate into `FSharpKoans` directory and run `dotnet watch run`. Now, after you change the project code, it will be automatically reloaded and tests rerun. 59 | -------------------------------------------------------------------------------- /FSharpKoans/MoreAboutFunctions.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | //--------------------------------------------------------------- 5 | // More About Functions 6 | // 7 | // You've already learned a little about functions in F#, but 8 | // since F# is a functional language, there are more tricks 9 | // to learn! 10 | //--------------------------------------------------------------- 11 | [] 12 | module ``more about functions`` = 13 | 14 | [] 15 | let DefiningLambdas() = 16 | 17 | let colors = ["maize"; "blue"] 18 | 19 | let echo = 20 | colors 21 | |> List.map (fun x -> x + " " + x) 22 | 23 | AssertEquality echo __ 24 | 25 | (* The fun keyword allows you to create a function inline without giving 26 | it a name. These functions are known as anonymous functions, lambdas, 27 | or lambda functions. *) 28 | 29 | [] 30 | let FunctionsThatReturnFunctions() = 31 | (* A neat functional programming trick is to create functions that 32 | return other functions. This leads to some interesting behaviors. *) 33 | let add x = 34 | (fun y -> x + y) 35 | 36 | (* F#'s lightweight syntax allows you to call both functions as if there 37 | was only one *) 38 | let simpleResult = add 2 4 39 | AssertEquality simpleResult __ 40 | 41 | (* ...but you can also pass only one argument at a time to create 42 | residual functions. This technique is known as partial application. *) 43 | let addTen = add 10 44 | let fancyResult = addTen 14 45 | 46 | AssertEquality fancyResult __ 47 | 48 | //NOTE: Functions written in this style are said to be curried. 49 | 50 | [] 51 | let AutomaticCurrying() = 52 | (* The above technique is common enough that F# actually supports this 53 | by default. In other words, functions are automatically curried. *) 54 | let add x y = 55 | x + y 56 | 57 | let addSeven = add 7 58 | let unluckyNumber = addSeven 6 59 | let luckyNumber = addSeven 0 60 | 61 | AssertEquality unluckyNumber __ 62 | AssertEquality luckyNumber __ 63 | 64 | [] 65 | let NonCurriedFunctions() = 66 | (* You should stick to the auto-curried function syntax most of the 67 | time. However, you can also write functions in an uncurried form to 68 | make them easier to use from languages like C# where currying is not 69 | as commonly used. *) 70 | 71 | let add(x, y) = 72 | x + y 73 | 74 | (* NOTE: "add 5" will not compile now. You have to pass both arguments 75 | at once *) 76 | 77 | let result = add(5, 40) 78 | 79 | AssertEquality result __ 80 | 81 | (* THINK ABOUT IT: You learned earlier that functions with multiple 82 | return values are really just functions that return 83 | tuples. Do functions defined in the uncurried form 84 | really accept more than one argument at a time? *) 85 | -------------------------------------------------------------------------------- /FSharpKoans.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpKoans", "FSharpKoans\FSharpKoans.fsproj", "{ED99AB39-27F1-49EC-800D-653737DF9AC7}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpKoans.Core", "FSharpKoans.Core\FSharpKoans.Core.fsproj", "{1A35FDFE-F24D-4AA7-9835-66AEE64359E2}" 9 | EndProject 10 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpKoans.Test", "FSharpKoans.Test\FSharpKoans.Test.fsproj", "{13AA1479-CE6F-48BC-AC05-F73B387C6D9E}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Debug|x64.ActiveCfg = Debug|x64 28 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Debug|x64.Build.0 = Debug|x64 29 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Debug|x86.ActiveCfg = Debug|x86 30 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Debug|x86.Build.0 = Debug|x86 31 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Release|x64.ActiveCfg = Release|x64 34 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Release|x64.Build.0 = Release|x64 35 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Release|x86.ActiveCfg = Release|x86 36 | {ED99AB39-27F1-49EC-800D-653737DF9AC7}.Release|x86.Build.0 = Release|x86 37 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Debug|x64.ActiveCfg = Debug|x64 40 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Debug|x64.Build.0 = Debug|x64 41 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Debug|x86.ActiveCfg = Debug|x86 42 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Debug|x86.Build.0 = Debug|x86 43 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Release|x64.ActiveCfg = Release|x64 46 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Release|x64.Build.0 = Release|x64 47 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Release|x86.ActiveCfg = Release|x86 48 | {1A35FDFE-F24D-4AA7-9835-66AEE64359E2}.Release|x86.Build.0 = Release|x86 49 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Debug|x64.ActiveCfg = Debug|x64 52 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Debug|x64.Build.0 = Debug|x64 53 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Debug|x86.ActiveCfg = Debug|x86 54 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Debug|x86.Build.0 = Debug|x86 55 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Release|x64.ActiveCfg = Release|x64 58 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Release|x64.Build.0 = Release|x64 59 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Release|x86.ActiveCfg = Release|x86 60 | {13AA1479-CE6F-48BC-AC05-F73B387C6D9E}.Release|x86.Build.0 = Release|x86 61 | EndGlobalSection 62 | EndGlobal 63 | -------------------------------------------------------------------------------- /FSharpKoans/AboutFiltering.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | 4 | module NumberFilterer = 5 | 6 | let someIfEven x = 7 | if x % 2 = 0 then Some x 8 | else None 9 | 10 | //--------------------------------------------------------------- 11 | // Getting Started with Filtering Lists 12 | // 13 | // Lists in F# can be filtered in a number of ways. 14 | // This koan looks at: 15 | // - filter 16 | // - find / tryFind 17 | // - choose 18 | // - pick 19 | //--------------------------------------------------------------- 20 | 21 | [] 22 | module ``about filtering`` = 23 | open NumberFilterer 24 | 25 | [] 26 | let FilteringAList() = 27 | let names = [ "Alice"; "Bob"; "Eve"; ] 28 | 29 | // Find all the names starting with "A" using an anonymous function 30 | let actual_names = 31 | names 32 | |> List.filter (fun name -> name.StartsWith( "A" )) 33 | 34 | AssertEquality actual_names [ __ ] 35 | 36 | //Or passing a function to filter 37 | let startsWithTheLetterB (s: string) = 38 | s.StartsWith("B") 39 | 40 | let namesBeginningWithB = 41 | names 42 | |> List.filter startsWithTheLetterB 43 | 44 | AssertEquality namesBeginningWithB [ __ ] 45 | 46 | [] 47 | let FindingJustOneItem() = 48 | let names = [ "Alice"; "Bob"; "Eve"; ] 49 | let expected_name = "Bob" 50 | 51 | // find will return just one item, or throws an exception 52 | 53 | let actual_name = 54 | names 55 | |> List.find (fun name -> name = __ ) 56 | 57 | //??? What would happen if there are 2 Bobs in the List? 58 | 59 | AssertEquality expected_name actual_name 60 | 61 | [] 62 | let FindingJustOneOrZeroItem() = 63 | let names = [ "Alice"; "Bob"; "Eve"; ] 64 | 65 | // tryFind returns an option so you can handle 0 rows returned 66 | let eve = 67 | names 68 | |> List.tryFind (fun name -> name = "Eve" ) 69 | let zelda = 70 | names 71 | |> List.tryFind (fun name -> name = "Zelda" ) 72 | 73 | AssertEquality eve.IsSome __ 74 | AssertEquality zelda.IsSome __ 75 | 76 | [] 77 | let ChoosingItemsFromAList() = 78 | let numbers = [ 1; 2; 3; ] 79 | 80 | // choose takes a function that transforms the input into an option 81 | // And filters out the results that are None. 82 | let evenNumbers = 83 | numbers 84 | |> List.choose someIfEven 85 | 86 | AssertEquality evenNumbers [ __ ] 87 | 88 | //You can also use the "id" function on types of 'a option list 89 | //"id" will return just those that are "Some" 90 | let optionNames = [ None; Some "Alice"; None; ] 91 | 92 | let namesWithValue = 93 | optionNames 94 | |> List.choose id 95 | 96 | //Notice the type of namesWithValue is 'string list', whereas optionNames is 'string option list' 97 | AssertEquality namesWithValue [ __ ] 98 | 99 | [] 100 | let PickingItemsFromAList() = 101 | let numbers = [ 5..10 ] 102 | 103 | //Pick is similar to choose, but returns the first element, or throws an exception if there are no 104 | //items that return "Some" (a bit like find does) 105 | let firstEven = 106 | numbers 107 | |> List.pick someIfEven 108 | 109 | AssertEquality firstEven __ 110 | 111 | //As with choose, you can also use the "id" function on types of 'a option list 112 | //to return just those that are "Some" 113 | let optionNames = [ None; Some "Alice"; None; Some "Bob"; ] 114 | 115 | let firstNameWithValue = 116 | optionNames 117 | |> List.pick id 118 | 119 | AssertEquality firstNameWithValue __ 120 | 121 | //There is also a tryPick which works like tryFind, returning "None" instead of throwing an exception. 122 | -------------------------------------------------------------------------------- /FSharpKoans/AboutLists.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpKoans 2 | open FSharpKoans.Core 3 | open System.Collections.Generic 4 | 5 | //--------------------------------------------------------------- 6 | // About Lists 7 | // 8 | // Lists are important building blocks that you'll use frequently 9 | // in F# programming. They are used to group arbitrarily large 10 | // sequences of values. It's very common to store values in a 11 | // list and perform operations across each value in the 12 | // list. 13 | //--------------------------------------------------------------- 14 | [] 15 | module ``about lists`` = 16 | 17 | [] 18 | let CreatingLists() = 19 | let list = ["apple"; "pear"; "grape"; "peach"] 20 | 21 | //Note: The list data type in F# is a singly linked list, 22 | // so indexing elements is O(n). 23 | 24 | AssertEquality list.Head __ 25 | AssertEquality list.Tail __ 26 | AssertEquality list.Length __ 27 | 28 | (* .NET developers coming from other languages may be surprised 29 | that F#'s list type is not the same as the base class library's 30 | List. In other words, the following assertion is true *) 31 | 32 | let dotNetList = new List() 33 | //you don't need to modify the following line 34 | AssertInequality (list.GetType()) (dotNetList.GetType()) 35 | 36 | [] 37 | let BuildingNewLists() = 38 | let first = ["grape"; "peach"] 39 | let second = "pear" :: first 40 | let third = "apple" :: second 41 | 42 | //Note: "::" is known as "cons" 43 | 44 | AssertEquality ["apple"; "pear"; "grape"; "peach"] third 45 | AssertEquality second __ 46 | AssertEquality first __ 47 | 48 | //What happens if you uncomment the following? 49 | 50 | //first.Head <- "apple" 51 | //first.Tail <- ["peach"; "pear"] 52 | 53 | //THINK ABOUT IT: Can you change the contents of a list once it has been 54 | // created? 55 | 56 | 57 | [] 58 | let ConcatenatingLists() = 59 | let first = ["apple"; "pear"; "grape"] 60 | let second = first @ ["peach"] 61 | 62 | AssertEquality first __ 63 | AssertEquality second __ 64 | 65 | (* THINK ABOUT IT: In general, what performs better for building lists, 66 | :: or @? Why? 67 | 68 | Hint: There is no way to modify "first" in the above example. It's 69 | immutable. With that in mind, what does the @ function have to do in 70 | order to append ["peach"] to "first" to create "second"? *) 71 | 72 | [] 73 | let CreatingListsWithARange() = 74 | let list = [0..4] 75 | 76 | AssertEquality list.Head __ 77 | AssertEquality list.Tail __ 78 | 79 | [] 80 | let CreatingListsWithComprehensions() = 81 | let list = [for i in 0..4 do yield i ] 82 | 83 | AssertEquality list __ 84 | 85 | [] 86 | let ComprehensionsWithConditions() = 87 | let list = [for i in 0..10 do 88 | if i % 2 = 0 then yield i ] 89 | 90 | AssertEquality list __ 91 | 92 | [] 93 | let TransformingListsWithMap() = 94 | let square x = 95 | x * x 96 | 97 | let original = [0..5] 98 | let result = List.map square original 99 | 100 | AssertEquality original __ 101 | AssertEquality result __ 102 | 103 | [] 104 | let FilteringListsWithFilter() = 105 | let isEven x = 106 | x % 2 = 0 107 | 108 | let original = [0..5] 109 | let result = List.filter isEven original 110 | 111 | AssertEquality original __ 112 | AssertEquality result __ 113 | 114 | [] 115 | let DividingListsWithPartition() = 116 | let isOdd x = 117 | x % 2 <> 0 118 | 119 | let original = [0..5] 120 | let result1, result2 = List.partition isOdd original 121 | 122 | AssertEquality result1 __ 123 | AssertEquality result2 __ 124 | 125 | (* Note: There are many other useful methods in the List module. Check them 126 | via intellisense in Visual Studio by typing '.' after List, or online at 127 | https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/lists *) 128 | --------------------------------------------------------------------------------