namespace System
val originalCwd : string

Full name: Index.originalCwd
type Environment =
  static member CommandLine : string
  static member CurrentDirectory : string with get, set
  static member CurrentManagedThreadId : int
  static member Exit : exitCode:int -> unit
  static member ExitCode : int with get, set
  static member ExpandEnvironmentVariables : name:string -> string
  static member FailFast : message:string -> unit + 1 overload
  static member GetCommandLineArgs : unit -> string[]
  static member GetEnvironmentVariable : variable:string -> string + 1 overload
  static member GetEnvironmentVariables : unit -> IDictionary + 1 overload
  ...
  nested type SpecialFolder
  nested type SpecialFolderOption

Full name: System.Environment
property Environment.CurrentDirectory: string
val parentDir : string

Full name: Index.parentDir
val dir : IO.DirectoryInfo
namespace System.IO
Multiple items
type DirectoryInfo =
  inherit FileSystemInfo
  new : path:string -> DirectoryInfo
  member Create : unit -> unit + 1 overload
  member CreateSubdirectory : path:string -> DirectoryInfo + 1 overload
  member Delete : unit -> unit + 1 overload
  member EnumerateDirectories : unit -> IEnumerable<DirectoryInfo> + 2 overloads
  member EnumerateFileSystemInfos : unit -> IEnumerable<FileSystemInfo> + 2 overloads
  member EnumerateFiles : unit -> IEnumerable<FileInfo> + 2 overloads
  member Exists : bool
  member GetAccessControl : unit -> DirectorySecurity + 1 overload
  member GetDirectories : unit -> DirectoryInfo[] + 2 overloads
  ...

Full name: System.IO.DirectoryInfo

--------------------
IO.DirectoryInfo(path: string) : unit
property IO.DirectoryInfo.Parent: IO.DirectoryInfo
property IO.FileSystemInfo.FullName: string
val setCwd : dir:string -> unit

Full name: Index.setCwd
val dir : string
type Path =
  static val InvalidPathChars : char[]
  static val AltDirectorySeparatorChar : char
  static val DirectorySeparatorChar : char
  static val PathSeparator : char
  static val VolumeSeparatorChar : char
  static member ChangeExtension : path:string * extension:string -> string
  static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
  static member GetDirectoryName : path:string -> string
  static member GetExtension : path:string -> string
  static member GetFileName : path:string -> string
  ...

Full name: System.IO.Path
IO.Path.Combine([<ParamArray>] paths: string []) : string
IO.Path.Combine(path1: string, path2: string) : string
IO.Path.Combine(path1: string, path2: string, path3: string) : string
IO.Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val lock : lockObject:'Lock -> action:(unit -> 'T) -> 'T (requires reference type)

Full name: Microsoft.FSharp.Core.Operators.lock
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
Multiple items
type MyClass =
  new : unit -> MyClass
  member Sum : x:int * y:int -> int
  member Name : string
  member Name : string with set

Full name: Index.MyClass

--------------------
new : unit -> MyClass
val this : MyClass
member MyClass.Sum : x:int * y:int -> int

Full name: Index.MyClass.Sum
val x : int
val y : int
val x : MyClass

Full name: Index.x
val result : int

Full name: Index.result
member MyClass.Sum : x:int * y:int -> int
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
property MyClass.Name: string
namespace System.Configuration
namespace System.Collections
namespace System.Collections.Specialized
Multiple items
type NameValueCollection =
  inherit NameObjectCollectionBase
  new : unit -> NameValueCollection + 7 overloads
  member Add : c:NameValueCollection -> unit + 1 overload
  member AllKeys : string[]
  member Clear : unit -> unit
  member CopyTo : dest:Array * index:int -> unit
  member Get : name:string -> string + 1 overload
  member GetKey : index:int -> string
  member GetValues : name:string -> string[] + 1 overload
  member HasKeys : unit -> bool
  member Item : string -> string with get, set
  ...

Full name: System.Collections.Specialized.NameValueCollection

--------------------
System.Collections.Specialized.NameValueCollection() : unit
System.Collections.Specialized.NameValueCollection(col: System.Collections.Specialized.NameValueCollection) : unit
System.Collections.Specialized.NameValueCollection(capacity: int) : unit
System.Collections.Specialized.NameValueCollection(equalityComparer: System.Collections.IEqualityComparer) : unit
System.Collections.Specialized.NameValueCollection(capacity: int, equalityComparer: System.Collections.IEqualityComparer) : unit
System.Collections.Specialized.NameValueCollection(capacity: int, col: System.Collections.Specialized.NameValueCollection) : unit
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Data
type ConfigurationManager =
  static member AppSettings : NameValueCollection
  static member ConnectionStrings : ConnectionStringSettingsCollection
  static member GetSection : sectionName:string -> obj
  static member OpenExeConfiguration : userLevel:ConfigurationUserLevel -> Configuration + 1 overload
  static member OpenMachineConfiguration : unit -> Configuration
  static member OpenMappedExeConfiguration : fileMap:ExeConfigurationFileMap * userLevel:ConfigurationUserLevel -> Configuration
  static member OpenMappedMachineConfiguration : fileMap:ConfigurationFileMap -> Configuration
  static member RefreshSection : sectionName:string -> unit

Full name: System.Configuration.ConfigurationManager
property ConfigurationManager.AppSettings: Collections.Specialized.NameValueCollection
val appSettingsTest2 : string

Full name: Index.appSettingsTest2
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.Configuration
type Settings1 = AppSettings<...>

Full name: Index.Settings1
type AppSettings

Full name: FSharp.Configuration.AppSettings
property AppSettings<...>.Test2: string


Returns the value from App.config with key test2
AppSettings<...>.SelectExecutableFile(pathOfExe: string) : Unit


Property to change the executable file that is read for configurations. This idea is that you can manage other executables also (e.g. from script).
type Settings2 = AppSettings<...>

Full name: Index.Settings2
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val AppSettingsConfig : string

Full name: Index.AppSettingsConfig
type Settings = AppSettings<...>

Full name: Index.Settings
val AppSettingsExe : string

Full name: Index.AppSettingsExe
val Config : string

Full name: Index.Config
type ScriptSettings = AppSettings<...>

Full name: Index.ScriptSettings
val Exec : string

Full name: Index.Exec
val appKey1 : Guid

Full name: Index.appKey1
property AppSettings<...>.AppKey: Guid


Returns the value from /Users/admin/Code/scripting-workshop/slides/index.fsx.config with key AppKey
namespace System.Xml
namespace System.Xml.Linq
val AppSettingsPath : string

Full name: Index.AppSettingsPath
Multiple items
type ConfigurationManager =
  new : unit -> ConfigurationManager
  static member AppSettings : IDictionary<string,string>
  static member ConnectionStrings : IDictionary<string,string>

Full name: Index.ConfigurationManager

--------------------
new : unit -> ConfigurationManager
val config : XDocument
Multiple items
type XDocument =
  inherit XContainer
  new : unit -> XDocument + 3 overloads
  member Declaration : XDeclaration with get, set
  member DocumentType : XDocumentType
  member NodeType : XmlNodeType
  member Root : XElement
  member Save : fileName:string -> unit + 6 overloads
  member WriteTo : writer:XmlWriter -> unit
  static member Load : uri:string -> XDocument + 7 overloads
  static member Parse : text:string -> XDocument + 1 overload

Full name: System.Xml.Linq.XDocument

--------------------
XDocument() : unit
XDocument([<ParamArray>] content: obj []) : unit
XDocument(other: XDocument) : unit
XDocument(declaration: XDeclaration, [<ParamArray>] content: obj []) : unit
XDocument.Load(reader: XmlReader) : XDocument
XDocument.Load(textReader: IO.TextReader) : XDocument
XDocument.Load(stream: IO.Stream) : XDocument
XDocument.Load(uri: string) : XDocument
XDocument.Load(reader: XmlReader, options: LoadOptions) : XDocument
XDocument.Load(textReader: IO.TextReader, options: LoadOptions) : XDocument
XDocument.Load(stream: IO.Stream, options: LoadOptions) : XDocument
XDocument.Load(uri: string, options: LoadOptions) : XDocument
val section : (XDocument -> string -> string -> string -> Collections.Generic.IDictionary<string,string>)
val name : string
val key : string
val value : string
val query : Linq.QueryBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.query
val els : XElement
XContainer.Descendants() : Collections.Generic.IEnumerable<XElement>
XContainer.Descendants(name: XName) : Collections.Generic.IEnumerable<XElement>
type XName =
  member Equals : obj:obj -> bool
  member GetHashCode : unit -> int
  member LocalName : string
  member Namespace : XNamespace
  member NamespaceName : string
  member ToString : unit -> string
  static member Get : expandedName:string -> XName + 1 overload

Full name: System.Xml.Linq.XName
XName.Get(expandedName: string) : XName
XName.Get(localName: string, namespaceName: string) : XName
val el : XElement
val k : string
XElement.Attribute(name: XName) : XAttribute
val v : string
custom operation: select ('Result)

Calls Linq.QueryBuilder.Select
val dict : keyValuePairs:seq<'Key * 'Value> -> Collections.Generic.IDictionary<'Key,'Value> (requires equality)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict
val appSettings : Collections.Generic.IDictionary<string,string>
val connectionStrings : Collections.Generic.IDictionary<string,string>
Multiple items
static member ConfigurationManager.AppSettings : Collections.Generic.IDictionary<string,string>

Full name: Index.ConfigurationManager.AppSettings

--------------------
type AppSettings

Full name: FSharp.Configuration.AppSettings
static member ConfigurationManager.ConnectionStrings : Collections.Generic.IDictionary<string,string>

Full name: Index.ConfigurationManager.ConnectionStrings
type Configuration =
  member AppSettings : AppSettingsSection
  member ConnectionStrings : ConnectionStringsSection
  member EvaluationContext : ContextInformation
  member FilePath : string
  member GetSection : path:string -> ConfigurationSection
  member GetSectionGroup : path:string -> ConfigurationSectionGroup
  member HasFile : bool
  member Locations : ConfigurationLocationCollection
  member NamespaceDeclared : bool with get, set
  member RootSectionGroup : ConfigurationSectionGroup
  ...

Full name: System.Configuration.Configuration
Multiple items
property ConfigurationManager.AppSettings: Collections.Specialized.NameValueCollection

--------------------
property ConfigurationManager.AppSettings: Collections.Generic.IDictionary<string,string>
val appKey : string

Full name: Index.appKey
Multiple items
property ConfigurationManager.ConnectionStrings: ConnectionStringSettingsCollection

--------------------
property ConfigurationManager.ConnectionStrings: Collections.Generic.IDictionary<string,string>
val test1 : ConnectionStringSettings

Full name: Index.test1
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
val ( geocoding.json ) : string

Full name: Index.( geocoding.json )
type Location = obj

Full name: Index.Location
type JsonProvider

Full name: FSharp.Data.JsonProvider


<summary>Typed representation of a JSON document.</summary>
       <param name='Sample'>Location of a JSON sample file or a string containing a sample JSON document.</param>
       <param name='SampleIsList'>If true, sample should be a list of individual samples for the inference.</param>
       <param name='RootName'>The name to be used to the root type. Defaults to `Root`.</param>
       <param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param>
       <param name='Encoding'>The encoding used to read the sample. You can specify either the character set name or the codepage number. Defaults to UTF8 for files, and to ISO-8859-1 the for HTTP requests, unless `charset` is specified in the `Content-Type` response header.</param>
       <param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param>
       <param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource
          (e.g. 'MyCompany.MyAssembly, resource_name.json'). This is useful when exposing types generated by the type provider.</param>
       <param name='InferTypesFromValues'>If true, turns on additional type inference from values.
          (e.g. type inference infers string values such as "123" as ints and values constrained to 0 and 1 as booleans.)</param>
val extract : city:string -> 'a

Full name: Index.extract
val city : string
val uri : string
val result : obj

Full name: Index.result
type LocationResult =
  {DisplayName: string;
   Latitude: float;
   Longitude: float;
   City: string;
   State: string;
   Country: string;
   CountryCode: string;
   EnergyUse: Indicator option;}

Full name: Index.LocationResult
LocationResult.DisplayName: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
LocationResult.Latitude: float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
LocationResult.Longitude: float
LocationResult.City: string
LocationResult.State: string
LocationResult.Country: string
LocationResult.CountryCode: string
LocationResult.EnergyUse: Runtime.WorldBank.Indicator option
Multiple items
namespace FSharp.Data.Runtime

--------------------
namespace System.Runtime
namespace FSharp.Data.Runtime.WorldBank
type Indicator =
  interface IEnumerable
  interface seq<int * float>
  private new : connection:ServiceConnection * countryOrRegionCode:string * indicatorCode:string -> Indicator
  member TryGetValueAt : year:int -> float option
  member Code : string
  member Description : string
  member IndicatorCode : string
  member Item : year:int -> float with get
  member Name : string
  member Source : string
  ...

Full name: FSharp.Data.Runtime.WorldBank.Indicator
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
val transformSimple : source:'a -> LocationResult list

Full name: Index.transformSimple
val source : 'a
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val result : obj
union case Option.None: Option<'T>
val extractEnergyUse : result:'a -> wb:WorldBankData.ServiceTypes.WorldBankDataService -> Runtime.WorldBank.Indicator option

Full name: Index.extractEnergyUse
val result : 'a
Multiple items
module Result

from Microsoft.FSharp.Core

--------------------
type Result<'T,'TError> =
  | Ok of ResultValue: 'T
  | Error of ErrorValue: 'TError

Full name: Microsoft.FSharp.Core.Result<_,_>
val wb : WorldBankData.ServiceTypes.WorldBankDataService
type WorldBankData =
  static member GetDataContext : unit -> WorldBankDataService
  nested type ServiceTypes

Full name: FSharp.Data.WorldBankData


<summary>Typed representation of WorldBank data. See http://www.worldbank.org for terms and conditions.</summary>
type ServiceTypes =
  nested type Countries
  nested type Country
  nested type Indicators
  nested type IndicatorsDescriptions
  nested type Region
  nested type Regions
  nested type Topic
  nested type Topics
  nested type WorldBankDataService

Full name: FSharp.Data.WorldBankData.ServiceTypes


<summary>Contains the types that describe the data service</summary>
type WorldBankDataService =
  inherit WorldBankData
  member Countries : Countries
  member Regions : Regions
  member Topics : Topics

Full name: FSharp.Data.WorldBankData.ServiceTypes.WorldBankDataService
property WorldBankData.ServiceTypes.WorldBankDataService.Countries: WorldBankData.ServiceTypes.Countries
module Seq

from Microsoft.FSharp.Collections
val tryFind : predicate:('T -> bool) -> source:seq<'T> -> 'T option

Full name: Microsoft.FSharp.Collections.Seq.tryFind
val x : WorldBankData.ServiceTypes.Country
property Runtime.WorldBank.Country.Name: string
module Option

from Microsoft.FSharp.Core
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
property WorldBankData.ServiceTypes.Country.Indicators: WorldBankData.ServiceTypes.Indicators


<summary>The indicators for the country</summary>
val transform : source:'a -> LocationResult list

Full name: Index.transform
WorldBankData.GetDataContext() : WorldBankData.ServiceTypes.WorldBankDataService
val energyUse : Runtime.WorldBank.Indicator option
type LocationSink = CsvProvider<...>

Full name: Index.LocationSink
type CsvProvider

Full name: FSharp.Data.CsvProvider


<summary>Typed representation of a CSV file.</summary>
       <param name='Sample'>Location of a CSV sample file or a string containing a sample CSV document.</param>
       <param name='Separators'>Column delimiter(s). Defaults to `,`.</param>
       <param name='InferRows'>Number of rows to use for inference. Defaults to `1000`. If this is zero, all rows are used.</param>
       <param name='Schema'>Optional column types, in a comma separated list. Valid types are `int`, `int64`, `bool`, `float`, `decimal`, `date`, `guid`, `string`, `int?`, `int64?`, `bool?`, `float?`, `decimal?`, `date?`, `guid?`, `int option`, `int64 option`, `bool option`, `float option`, `decimal option`, `date option`, `guid option` and `string option`.
       You can also specify a unit and the name of the column like this: `Name (type&lt;unit&gt;)`, or you can override only the name. If you don't want to specify all the columns, you can reference the columns by name like this: `ColumnName=type`.</param>
       <param name='HasHeaders'>Whether the sample contains the names of the columns as its first line.</param>
       <param name='IgnoreErrors'>Whether to ignore rows that have the wrong number of columns or which can't be parsed using the inferred or specified schema. Otherwise an exception is thrown when these rows are encountered.</param>
       <param name='SkipRows'>Skips the first n rows of the CSV file.</param>
       <param name='AssumeMissingValues'>When set to true, the type provider will assume all columns can have missing values, even if in the provided sample all values are present. Defaults to false.</param>
       <param name='PreferOptionals'>When set to true, inference will prefer to use the option type instead of nullable types, `double.NaN` or `""` for missing values. Defaults to false.</param>
       <param name='Quote'>The quotation mark (for surrounding values containing the delimiter). Defaults to `"`.</param>
       <param name='MissingValues'>The set of strings recogized as missing values. Defaults to `NaN,NA,N/A,#N/A,:,-,TBA,TBD`.</param>
       <param name='CacheRows'>Whether the rows should be caches so they can be iterated multiple times. Defaults to true. Disable for large datasets.</param>
       <param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param>
       <param name='Encoding'>The encoding used to read the sample. You can specify either the character set name or the codepage number. Defaults to UTF8 for files, and to ISO-8859-1 the for HTTP requests, unless `charset` is specified in the `Content-Type` response header.</param>
       <param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param>
       <param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource
          (e.g. 'MyCompany.MyAssembly, resource_name.csv'). This is useful when exposing types generated by the type provider.</param>
type EnergyUseSink = CsvProvider<...>

Full name: Index.EnergyUseSink
val load : data:LocationResult list -> unit

Full name: Index.load
val data : LocationResult list
val locations : CsvProvider<...>.Row list
val energyUse : LocationResult list list
val row : LocationResult
val location : CsvProvider<...>.Row
type Row =
  inherit string * float * float * string * string * string * string
  new : location: string * latitude: float * longitude: float * city: string * state: string * country: string * countryCode: string -> Row
  member City : string
  member Country : string
  member CountryCode : string
  member Latitude : float
  member Location : string
  member Longitude : float
  member State : string

Full name: FSharp.Data.CsvProvider,Sample="Location (string),Latitude (float),Longitude (float),City (string),State (string),Country (string),CountryCode (string)".Row
val usage : LocationResult list
union case Option.Some: Value: 'T -> Option<'T>
val i : Runtime.WorldBank.Indicator
type Row =
  inherit string * int * float
  new : countryCode: string * year: int * energyUse: float -> Row
  member CountryCode : string
  member EnergyUse : float
  member Year : int

Full name: FSharp.Data.CsvProvider,Sample="CountryCode (string),Year (int),EnergyUse (float)".Row
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IReadOnlyCollection<'T>
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val unzip : list:('T1 * 'T2) list -> 'T1 list * 'T2 list

Full name: Microsoft.FSharp.Collections.List.unzip
val locationSink : LocationSink
member Runtime.CsvFile.Save : path:string * ?separator:char * ?quote:char -> unit
member Runtime.CsvFile.Save : stream:IO.Stream * ?separator:char * ?quote:char -> unit
member Runtime.CsvFile.Save : writer:IO.TextWriter * ?separator:char * ?quote:char -> unit
val distinct : LocationResult list
val concat : lists:seq<'T list> -> 'T list

Full name: Microsoft.FSharp.Collections.List.concat
val distinctBy : projection:('T -> 'Key) -> list:'T list -> 'T list (requires equality)

Full name: Microsoft.FSharp.Collections.List.distinctBy
val x : LocationResult
val energyUseSink : EnergyUseSink
val run : city:string -> unit

Full name: Index.run
namespace Expecto
val addTest : Test

Full name: Index.addTest
val testCase : name:string -> test:(unit -> unit) -> Test

Full name: Expecto.Tests.testCase
val expected : int
module Expect

from Expecto
val equal : actual:'a -> expected:'a -> message:string -> unit (requires equality)

Full name: Expecto.Expect.equal
Multiple items
module Tests

from Expecto

--------------------
type TestsAttribute =
  inherit Attribute
  new : unit -> TestsAttribute

Full name: Expecto.TestsAttribute

--------------------
new : unit -> TestsAttribute
val runTests : config:Impl.ExpectoConfig -> tests:Test -> int

Full name: Expecto.Tests.runTests
val defaultConfig : Impl.ExpectoConfig

Full name: Expecto.Tests.defaultConfig
val failTest : Test

Full name: Index.failTest
val multTest : Test

Full name: Index.multTest
val intTests : Test

Full name: Index.intTests
val testList : name:string -> tests:Test list -> Test

Full name: Expecto.Tests.testList
val config : FsCheckConfig

Full name: Index.config
type FsCheckConfig =
  {maxTest: int;
   startSize: int;
   endSize: int;
   replay: (int * int) option;
   arbitrary: Type list;
   receivedArgs: FsCheckConfig -> string -> int -> obj list -> Async<unit>;
   successfulShrink: FsCheckConfig -> string -> obj list -> Async<unit>;
   finishedTest: FsCheckConfig -> string -> Async<unit>;}
  static member defaultConfig : FsCheckConfig

Full name: Expecto.FsCheckConfig
property FsCheckConfig.defaultConfig: FsCheckConfig
val properties : Test

Full name: Index.properties
val testProperty : name:string -> ('a -> Test)

Full name: Expecto.ExpectoFsCheck.testProperty
val a : int
val b : int
val xs : int list
val rev : list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.rev
val testPropertyWithConfig : config:FsCheckConfig -> name:string -> ('a -> Test)

Full name: Expecto.ExpectoFsCheck.testPropertyWithConfig
val c : int
val allTests : Test

Full name: Index.allTests
namespace XPlot
namespace XPlot.GoogleCharts
val showMap : data:LocationResult list -> unit

Full name: Index.showMap
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
type Chart =
  static member Annotation : data:seq<#seq<DateTime * 'V * string * string>> * ?Labels:seq<string> * ?Options:Options -> GoogleChart (requires 'V :> value)
  static member Annotation : data:seq<DateTime * #value * string * string> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
  static member Area : data:seq<#seq<'K * 'V>> * ?Labels:seq<string> * ?Options:Options -> GoogleChart (requires 'K :> key and 'V :> value)
  static member Area : data:seq<#key * #value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
  static member Area : data:seq<#value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
  static member Bar : data:seq<#seq<'K * 'V>> * ?Labels:seq<string> * ?Options:Options -> GoogleChart (requires 'K :> key and 'V :> value)
  static member Bar : data:seq<#key * #value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
  static member Bar : data:seq<#value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
  static member Bubble : data:seq<string * #value * #value * #value * #value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
  static member Bubble : data:seq<string * #value * #value * #value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
  ...

Full name: XPlot.GoogleCharts.Chart
static member Chart.Map : data:seq<float * float * string> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
static member Chart.Map : data:seq<string * string> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
static member Chart.WithOptions : options:Options -> chart:GoogleChart -> GoogleChart
Multiple items
type Options =
  new : unit -> Options
  member ShouldSerializeaggregationTarget : unit -> bool
  member ShouldSerializeallValuesSuffix : unit -> bool
  member ShouldSerializeallowHtml : unit -> bool
  member ShouldSerializealternatingRowStyle : unit -> bool
  member ShouldSerializeanimation : unit -> bool
  member ShouldSerializeannotations : unit -> bool
  member ShouldSerializeannotationsWidth : unit -> bool
  member ShouldSerializeareaOpacity : unit -> bool
  member ShouldSerializeavoidOverlappingGridLines : unit -> bool
  ...

Full name: XPlot.GoogleCharts.Configuration.Options

--------------------
new : unit -> Options
static member Chart.WithHeight : height:int -> chart:GoogleChart -> GoogleChart
static member Chart.Show : chart:GoogleChart -> unit
val showEnergyUse : data:LocationResult list -> unit

Full name: Index.showEnergyUse
val labels : string list
val plots : (string * float) list list
val choose : chooser:('T -> 'U option) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.choose
val energyUse : Runtime.WorldBank.Indicator
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val country : string
static member Chart.Line : data:seq<#seq<'K * 'V>> * ?Labels:seq<string> * ?Options:Options -> GoogleChart (requires 'K :> key and 'V :> value)
static member Chart.Line : data:seq<#key * #value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
static member Chart.Line : data:seq<#value> * ?Labels:seq<string> * ?Options:Options -> GoogleChart
static member Chart.WithLabels : labels:seq<string> -> chart:GoogleChart -> GoogleChart
val fsi : Compiler.Interactive.InteractiveSession

Full name: Microsoft.FSharp.Compiler.Interactive.Settings.fsi
member Compiler.Interactive.InteractiveSession.AddPrinter : ('T -> string) -> unit
val chart : GoogleChart
Multiple items
type GoogleChart =
  new : unit -> GoogleChart
  val mutable private dataTable: DataTable
  val mutable private options: Options
  val mutable private type: ChartGallery
  member GetHtml : unit -> string
  member GetInlineHtml : unit -> string
  member GetInlineJS : unit -> string
  member Show : unit -> unit
  member WithHeight : height:int -> unit
  member WithId : newId:string -> unit
  ...

Full name: XPlot.GoogleCharts.GoogleChart

--------------------
new : unit -> GoogleChart
namespace Argu
type Arguments =
  | City of string
  interface IArgParserTemplate

Full name: Index.Arguments
Multiple items
type MandatoryAttribute =
  inherit Attribute
  new : unit -> MandatoryAttribute

Full name: Argu.ArguAttributes.MandatoryAttribute

--------------------
new : unit -> MandatoryAttribute
union case Arguments.City: string -> Arguments
type IArgParserTemplate =
  interface
    abstract member Usage : string
  end

Full name: Argu.IArgParserTemplate
val this : Arguments
override Arguments.Usage : string

Full name: Index.Arguments.Usage
val runCLI : args:string [] -> unit

Full name: Index.runCLI
val args : string []
val argParser : ArgumentParser<Arguments>
Multiple items
type ArgumentParser =
  private new : argInfo:UnionArgInfo * _programName:string * helpTextMessage:string option * _usageStringCharacterWidth:int * errorHandler:IExiter -> ArgumentParser
  abstract member Accept : visitor:IArgumentParserVisitor<'R> -> 'R
  member GetArgumentCases : unit -> ArgumentCaseInfo list
  member GetSubCommandParsers : unit -> ArgumentParser list
  member PrintCommandLineSyntax : ?programName:string * ?usageStringCharacterWidth:int -> string
  member PrintUsage : ?message:string * ?programName:string * ?hideSyntax:bool * ?usageStringCharacterWidth:int -> string
  member ErrorHandler : IExiter
  member HelpDescription : string
  member HelpFlags : string list
  member HelpTextMessage : string option
  ...

Full name: Argu.ArgumentParser

--------------------
type ArgumentParser<'Template (requires 'Template :> IArgParserTemplate)> =
  inherit ArgumentParser
  new : ?programName:string * ?helpTextMessage:string * ?usageStringCharacterWidth:int * ?errorHandler:IExiter -> ArgumentParser<'Template>
  private new : argInfo:UnionArgInfo * _programName:string * helpTextMessage:string option * _usageStringCharacterWidth:int * errorHandler:IExiter -> ArgumentParser<'Template>
  override Accept : visitor:IArgumentParserVisitor<'a1> -> 'a1
  member GetArgumentCaseInfo : ctorExpr:Expr<('Fields -> 'Template)> -> ArgumentCaseInfo
  member GetArgumentCaseInfo : value:'Template -> ArgumentCaseInfo
  member GetSubCommandParser : expr:Expr<(ParseResults<'SubTemplate> -> 'Template)> -> ArgumentParser<'SubTemplate> (requires 'SubTemplate :> IArgParserTemplate)
  member GetTag : value:'Template -> int
  member Parse : ?inputs:string [] * ?configurationReader:IConfigurationReader * ?ignoreMissing:bool * ?ignoreUnrecognized:bool * ?raiseOnUsage:bool -> ParseResults<'Template>
  member ParseCommandLine : ?inputs:string [] * ?ignoreMissing:bool * ?ignoreUnrecognized:bool * ?raiseOnUsage:bool -> ParseResults<'Template>
  ...

Full name: Argu.ArgumentParser<_>

--------------------
new : ?programName:string * ?helpTextMessage:string * ?usageStringCharacterWidth:int * ?errorHandler:IExiter -> ArgumentParser<'Template>
static member ArgumentParser.Create : ?programName:string * ?helpTextMessage:string * ?usageStringCharacterWidth:int * ?errorHandler:IExiter -> ArgumentParser<#IArgParserTemplate>
Multiple items
type ProcessExiter =
  interface IExiter
  new : ?colorizer:(ErrorCode -> ConsoleColor option) -> ProcessExiter

Full name: Argu.ProcessExiter

--------------------
new : ?colorizer:(ErrorCode -> ConsoleColor option) -> ProcessExiter
val argResults : ParseResults<Arguments>
member ArgumentParser.Parse : ?inputs:string [] * ?configurationReader:IConfigurationReader * ?ignoreMissing:bool * ?ignoreUnrecognized:bool * ?raiseOnUsage:bool -> ParseResults<'Template>
member ParseResults.GetResult : expr:Quotations.Expr<('Fields -> 'Template)> * ?defaultValue:'Fields * ?source:ParseSource -> 'Fields
member ParseResults.GetResult : expr:Quotations.Expr<'Template> * ?defaultValue:'Template * ?source:ParseSource -> 'Template
property Compiler.Interactive.InteractiveSession.CommandLineArgs: string []
val args : string []

Full name: Index.args
module Paket
val download : unit -> unit

Full name: Paket.download
val restore : unit -> unit

Full name: Paket.restore
val generateLoadScripts : framework:string -> unit

Full name: Paket.generateLoadScripts

The Agony and the Ecstasy of F# Scripting

The Agony and the Ecstasy

Overview

ETL Applications

What's not covered?

Working with Databases

Fable

Notebooks

Azure Functions, AWS Lambda, etc.

What we will cover

  • Creating and running scripts
  • Iterating design
  • Loading and referencing files
  • Referencing NuGet packages
  • Accessing values from .config files
  • Working with data
  • Leveraging scripts for ETL
  • Testing
  • Visualizing data sets
  • Accessing command line parameters
  • Using scripts in a project
  • Debugging scripts
  • Build and deploy scripting with FAKE

Getting Started

https://github.com/panesofglass/scripting-workshop

Scripting Workshop

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
/scripting-workshop
  /.paket        <- Paket related
  /completed     <- in case you get stuck
  /exercises     <- working directory
  /slides        <- ignore
  - paket.dependencies
  - paket.lock
  - ... other files 

Finding FSI on Windows

C:\Program Files (x86)\Microsoft SDKs\F#\4.1\Framework\v4.0\fsiAnyCPU.exe

agony

Finding FSI on Mac and Linux

fsharpi

ecstasy

F# Interactive in the Terminal

1: 
2: 
3: 
4: 
5: 
6: 
F# Interactive for F# 4.1
Freely distributed under the Apache 2.0 Open Source License

For help type #help;;

> 

Enter 2 + 2;;

1: 
2: 
3: 
4: 
> 2 + 2;;
val it : int = 4

> 

Enter it;;

1: 
2: 
3: 
4: 
> it;;
val it : int = 4

> 

Enter let x = it;;

1: 
2: 
3: 
4: 
> let x = it;;
val x : int = 4

> 

Enter type MyClass() = member val Name = "" with get, set;;

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
> type MyClass() =                      
-     member val Name = "" with get, set;;
type MyClass =
  class
    new : unit -> MyClass
    member Name : string
    member Name : string with set
  end

> 

Enter quit;;

1: 
2: 
3: 
> #quit;;

- Exit...

Creating and Running Scripts

Open a Text Editor

1: 
2: 
$ cd ~/scripting-workshop/exercises
$ code Script.fsx

Enter the following code snippets:

1: 
2: 
3: 
type MyClass() =
    member val Name = "" with get, set
    member this.Sum(x, y) = x + y
1: 
2: 
let x = MyClass(Name = "Some Name")
let result = x.Sum(2, 2)
1: 
printfn "%s: %i" x.Name result

Run the Script

1: 
$ fsharpi Script.fsx
Some Name: 4

ecstasy

Loading and Referencing Files

References

1: 
#r "System.Configuration.dll"

CLI References

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
$ fsharpi --reference:System.Configuration.dll

F# Interactive for F# 4.1
Freely distributed under the Apache 2.0 Open Source License

For help type #help;;

> open System.Configuration;;
> ConfigurationManager.AppSettings;;    
val it : System.Collections.Specialized.NameValueCollection = seq []

> 

ecstasy

Loading Files

Create a new file, Library.fs.

Files so far

1: 
2: 
3: 
4: 
- scripting-workshop
  - exercises
    - Library.fs
    - Script.fsx

Library.fs

1: 
2: 
3: 
type MyClass() =
    member val Name = "" with get, set
    member this.Sum(x, y) = x + y

Script.fsx

1: 
#r "System.Configuration.dll"
1: 
2: 
let x = MyClass(Name = "Some Name")
let result = x.Sum(2, 2)

Script.fsx

1: 
#r "System.Configuration.dll"
1: 
#load "Library.fs"
1: 
2: 
let x = MyClass(Name = "Some Name")
let result = x.Sum(2, 2)

Run Script.fsx

1: 
2: 
$ fsharpi Script.fsx
/Script.fsx(3,9): error FS0039: The value or constructor 'MyClass' is not defined.

agony

What went wrong?

Script.fsx

1: 
#r "System.Configuration.dll"
1: 
#load "Library.fs"
1: 
open Library
1: 
2: 
let x = MyClass(Name = "Some Name")
let result = x.Sum(2, 2)

Library.fs

1: 
2: 
3: 
4: 
5: 
6: 
7: 
S:
Files in libraries or multiple-file applications must begin
with a namespace or module declaration, e.g. 'namespace SomeNamespace.SubNamespace'
or 'module SomeNamespace.SomeModule'. Only the last source file
of an application may omit such a declaration.

namespace Library

ecstasy

Iterating Design

ecstasy

Referencing NuGet Packages

nuget.exe

1: 
2: 
#r "../packages/Argu.3.7.0/lib/net40/Argu.dll" 
#r "../packages/FSharp.Data.2.3.3/lib/net40/FSharp.Data.dll" 

agony

Paket

  • No versions in paths
  • Consistent, locked versions
  • Dependency groups
  • generate-load-scripts

ecstasy

Add Packages

1: 
2: 
3: 
4: 
5: 
6: 
7: 
nuget Argu
nuget Expecto
nuget Expecto.BenchmarkDotNet
nuget Expecto.FsCheck
nuget FSharp.Configuration
nuget FSharp.Data
nuget XPlot.GoogleCharts

Download paket.exe

1: 
2: 
cd ../
./.paket/paket.bootstrapper.exe

Install dependencies

1: 
2: 
cd ../
./.paket/paket.exe install

Generate Load Scripts

1: 
./.paket/paket.exe generate-load-scripts --group main --type fsx --framework net461

Load Dependencies

1: 
#load "../.paket/load/net461/main.group.fsx"

ecstasy

Accessing Values from .config Files

Add an App.config file

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="AppKey" value="7B7EB384FEBA4409B56066FF63F1E8D0" />
    <add key="test2" value="Some Test Value 5" />
    <add key="TestInt" value="102" />
    <add key="TestBool" value="True" />
    <add key="TestDouble" value="10.01" />
    <add key="TestTimeSpan" value="2.01:02:03.444" />
    <add key="TestDateTime" value="02/01/2014 03:04:05.777" />
    <add key="TestUri" value="http://fsharp.org" />
    <add key="TestGuid" value="{7B7EB384-FEBA-4409-B560-66FF63F1E8D0}" />
  </appSettings>
  <connectionStrings>
    <add name="Test1"
      connectionString="Server=.;Database=myDataBase;Integrated Security=True;" />
    <add name="Test2"
      connectionString="Server=.;Database=myDataBase2;Integrated Security=True;" />
  </connectionStrings>
</configuration>

Create AppSettings.fsx

1: 
#r "System.Configuration.dll"
1: 
2: 
3: 
open System.Configuration

ConfigurationManager.AppSettings.["test2"]
<null>

agony

What went wrong?

Problem 1: CurrentDirectory

1: 
System.Environment.CurrentDirectory
"/Users/admin/Code/scripting-workshop/slides"

Change the Current Directory

1: 
2: 
System.Environment.CurrentDirectory <- __SOURCE_DIRECTORY__
System.Environment.CurrentDirectory
"/Users/admin/Code/scripting-workshop/slides"
<null>

agony

Problem 2: Script <> App

Solution 1: FSharp.Configuration

1: 
2: 
3: 
4: 
5: 
6: 
open FSharp.Configuration

type Settings1 = AppSettings<"App.config">

// Enter:
Settings1.Test2
1: 
error FS0039: The field, constructor or member 'Test2' is not defined.
1: 
2: 
3: 
Settings1.SelectExecutableFile "AppSettings.fsx"

Settings1.Test2
1: 
error FS0039: The field, constructor or member 'Test2' is not defined.

Rename App.config to AppSettings.fsx.config

1: 
2: 
3: 
4: 
type Settings2 = AppSettings<"AppSettings.fsx.config">
Settings2.SelectExecutableFile "AppSettings.fsx"

Settings2.Test2
1: 
error FS0039: The field, constructor or member 'Test2' is not defined.

agony

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let [<Literal>] AppSettingsConfig =
    __SOURCE_DIRECTORY__ + "/AppSettings.fsx.config"
type Settings = AppSettings<AppSettingsConfig>
let [<Literal>] AppSettingsExe =
    __SOURCE_DIRECTORY__ + "/AppSettings.fsx"
Settings.SelectExecutableFile AppSettingsExe

Settings.Test2

ecstasy

Retrieve the value for AppKey

1: 
Settings.AppKey
No value has been returned

agony

Solution 2: Override ConfigurationManager

Configuration.fsx

1: 
2: 
3: 
4: 
#r "System.Xml.dll"
#r "System.Xml.Linq.dll"
open System.Xml
open System.Xml.Linq

Configuration.fsx

1: 
let [<Literal>] AppSettingsPath = __SOURCE_DIRECTORY__ + "/App.config"

Configuration.fsx

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
type ConfigurationManager() =
    static let config = XDocument.Load AppSettingsPath
    static let section (config:XDocument) name key value =
        query {
            for els in config.Descendants(XName.Get name) do
            for el in els.Descendants(XName.Get "add") do
            let k = el.Attribute(XName.Get key).Value 
            let v = el.Attribute(XName.Get value).Value 
            select (k,v)
        } |> dict
    static let appSettings =
        section config "appSettings" "key" "value"
    static let connectionStrings =
        section config "connectionStrings" "name" "connectionString"
    static member AppSettings = appSettings
    static member ConnectionStrings = connectionStrings

Use Configuration.fsx

1: 
2: 
3: 
4: 
#load "Configuration.fsx"
open Configuration

ConfigurationManager.AppSettings.["AppKey"]
"9050511f2aa54cc1a2be31a64af63804"
1: 
ConfigurationManager.ConnectionStrings.["Test1"]
"Server=.;Database=myDataBase;Integrated Security=True;"

ecstasy

Remember: Always Use __SOURCE_DIRECTORY__

Working with Data

OpenCage GeoCoder

https://geocoder.opencagedata.com/

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
open FSharp.Data

let appKey = ConfigurationManager.AppSettings.["AppKey"]

let [<Literal>] ``geocoding.json`` =
    __SOURCE_DIRECTORY__ + "/geocoding.json"
type Location = JsonProvider<``geocoding.json``>

let extract city =
    let uri =
        "https://api.opencagedata.com/geocode/v1/json?q="
        + city
        + "&pretty=1&no_annotations=1&key="
        + appKey
    Location.Load(uri)

Call extract

1: 
let result = extract "Adelaide"

Leveraging Scripts for ETL

Defining Domain Types

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type LocationResult =
  { DisplayName : string
    Latitude : float
    Longitude : float
    City : string
    State : string
    Country : string
    CountryCode : string
    EnergyUse : Runtime.WorldBank.Indicator option }

Define transformSimple

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let transformSimple (source:Location.Root) : LocationResult list =
    [ for result in source.Results ->
        { DisplayName = result.Formatted
          Latitude = float result.Geometry.Lat
          Longitude = float result.Geometry.Lng
          City = result.Components.City
          State = result.Components.State
          Country = result.Components.Country
          CountryCode = result.Components.Iso31661Alpha2
          EnergyUse = None }
    ]

World Bank Data

Define transform

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
let extractEnergyUse (result:Location.Result)
        (wb:WorldBankData.ServiceTypes.WorldBankDataService) =
    wb.Countries
    |> Seq.tryFind (fun x ->
        result.Components.Country.StartsWith(x.Name))
    |> Option.map (fun x ->
        x.Indicators.``Energy use (kg of oil equivalent per capita)``)

let transform (source:Location.Root) : LocationResult list =
    let wb = WorldBankData.GetDataContext()
    [ for result in source.Results ->
        let energyUse = extractEnergyUse result wb
        { DisplayName = result.Formatted
          Latitude = float result.Geometry.Lat
          Longitude = float result.Geometry.Lng
          City = result.Components.City
          State = result.Components.State
          Country = result.Components.Country
          CountryCode = result.Components.Iso31661Alpha2
          EnergyUse = energyUse }
    ]

Persisting Data

CsvProvider

1: 
2: 
3: 
4: 
type LocationSink =
    CsvProvider<"Location (string),Latitude (float),Longitude (float),City (string),State (string),Country (string),CountryCode (string)">
type EnergyUseSink =
    CsvProvider<"CountryCode (string),Year (int),EnergyUse (float)">

Define load

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
let load (data:LocationResult list) =
    let locations, energyUse =
        [ for row in data ->
            let location =
                LocationSink.Row(
                    row.DisplayName, row.Latitude,
                    row.Longitude, row.City,
                    row.State, row.Country,
                    row.CountryCode
                )
            let usage =
                match row.EnergyUse with
                | Some i ->
                    [ for y in 1960..2012 ->
                        EnergyUseSink.Row(row.CountryCode, i.[y]) ]
                | None -> []
            location, usage
        ]
        |> List.unzip

Define load (cont)

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
    do
        use locationSink = new LocationSink(locations)
        locationSink.Save("locations.csv")
    do
        let distinct =
            List.concat energyUse
            |> List.distinctBy (fun x -> x.CountryCode)
        use energyUseSink = new EnergyUseSink(distinct)
        energyUseSink.Save("energyUse.csv")

Define run

1: 
2: 
3: 
let run city =
    let data = (extract >> transform) city
    load data

Iterating Design

ecstasy

Testing

#time

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
> #time;;

--> Timing now on

> run "Houston";;
Real: 00:00:00.970, CPU: 00:00:00.093, GC gen0: 1, gen1: 0, gen2: 0
val it : unit = ()

> #time;;

--> Timing now off

>

ecstasy

Unit Testing with xUnit.net

agony

Unit Testing with NUnit

agony

Unit Testing with Expecto

1: 
2: 
3: 
#load "../.paket/load/net461/main.group.fsx"

open Expecto

Write a Test

1: 
2: 
3: 
4: 
5: 
6: 
let addTest =
    testCase "An addition test" <| fun () ->
        let expected = 4
        Expect.equal expected (2+2) "2+2 = 4"

Tests.runTests defaultConfig addTest
1: 
2: 
3: 
4: 
[15:21:46 INF] EXPECTO? Running tests... <Expecto>
[15:21:46 INF] EXPECTO! 1 tests run in 00:00:00.0297663 

val it : int = 0

Write a Failing Test

1: 
2: 
3: 
4: 
5: 
let failTest =
    testCase "Failing test" <| fun () ->
        Expect.equal 1 2 "1 <> 2"

Tests.runTests defaultConfig failTest
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
[15:29:39 INF] EXPECTO? Running tests... <Expecto>
[15:29:39 ERR] Failing test failed in 00:00:00.0150000.
1 <> 2. Actual value was 1 but had expected it to be 2.
  s:
 <Expecto>
[15:29:39 INF] EXPECTO! 1 tests run in 00:00:00.0270684 

val it : int = 1
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let multTest =
    testCase "A multiplication test" <| fun () ->
        let expected = 4
        Expect.equal expected (2*2) "2*2 = 4"

let intTests =
    testList "Integer math tests" [
        addTest
        multTest
    ]

Tests.runTests defaultConfig intTests
1: 
2: 
3: 
4: 
[15:27:37 INF] EXPECTO? Running tests... <Expecto>
[15:27:37 INF] EXPECTO! 2 tests run in 00:00:00.0019015 

val it : int = 0

ecstasy

FsCheck

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let config = { FsCheckConfig.defaultConfig with maxTest = 10000 }

let properties =
    testList "FsCheck samples" [
        testProperty "Addition is commutative" <| fun a b ->
            a + b = b + a

        testProperty "Reverse of reverse of a list is the original list" <|
            fun (xs:list<int>) -> List.rev (List.rev xs) = xs

        // you can also override the FsCheck config
        testPropertyWithConfig config "Product is distributive over addition" <|
            fun a b c -> a * (b + c) = a * b + a * c
    ]

Tests.runTests defaultConfig properties
1: 
2: 
[15:33:04 INF] EXPECTO? Running tests... <Expecto>
[15:33:04 INF] EXPECTO! 3 tests run in 00:00:00.1008472 

Group testLists

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let allTests =
    testList "all tests" [
        intTests
        properties
    ]

Tests.runTests defaultConfig allTests
1: 
2: 
[15:34:09 INF] EXPECTO? Running tests... <Expecto>
[15:34:09 INF] EXPECTO! 5 tests run in 00:00:00.1008795 

ecstasy

Write some tests!

Visualizing Data Sets

XPlot

1: 
open XPlot.GoogleCharts

ecstasy

Show Locations

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let showMap (data:LocationResult list) =
    data
    |> List.map (fun x -> x.Latitude, x.Longitude, x.DisplayName)
    |> Chart.Map
    |> Chart.WithOptions (Options(showTip = true))
    |> Chart.WithHeight 420
    |> Chart.Show

Energy Use per Country

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let showEnergyUse (data:LocationResult list) =
    let labels, plots =
        data
        |> List.choose (fun x ->
            match x.EnergyUse with
            | Some energyUse -> Some(x.Country, energyUse)
            | None -> None)
        |> List.distinctBy fst
        |> List.map (fun (country, energyUse) ->
            country, [for y in 1960..2010 -> string y, energyUse.[y]])
        |> List.unzip
    plots
    |> Chart.Line
    |> Chart.WithOptions (Options(title = "Energy use per capita"))
    |> Chart.WithLabels labels
    |> Chart.Show
1: 
2: 
3: 
fsi.AddPrinter(fun (chart:XPlot.GoogleCharts.GoogleChart) ->
   chart |> Chart.Show
   "Google Chart")

Add show* to run

1: 
2: 
3: 
4: 
5: 
let run city =
    let data = (extract >> transform) city
    load data
    showMap data
    showEnergyUse data

Accessing Command Line Parameters

PowerShell Wrappers

Argu

Argu

1: 
open Argu
1: 
2: 
3: 
4: 
5: 
6: 
7: 
type Arguments =
    | [<Mandatory>] City of string
    with
    interface IArgParserTemplate with
        member this.Usage =
            match this with
            | City _ -> "Enter a location name"
1: 
2: 
3: 
4: 
5: 
6: 
let runCLI args =
    //let args = [|"Map.fsx";"--city";"Adelaide"|].[1..]
    let argParser = ArgumentParser.Create<Arguments>(errorHandler = ProcessExiter())
    let argResults = argParser.Parse(args)
    let city = argResults.GetResult <@ City @>
    run city

fsi.CommandLineArgs

1: 
runCLI fsi.CommandLineArgs
1: 
$ fsharpi exercises/App.fsx --city 'San Francisco'

agony

1: 
2: 
3: 
4: 
5: 
6: 
7: 
ERROR: unrecognized argument: '.
USAGE: fsiAnyCPU.exe [--help] --city <string>

OPTIONS:

    --city <string>       Enter a location name
    --help                display this list of options.

fsi.CommandLineArgs

1: 
2: 
let args = fsi.CommandLineArgs.[1..]
runCLI args

Run App.fsx with --city

1: 
$ fsharpi exercises/App.fsx --city Houston

ecstasy

Paket.fsx

1: 
2: 
3: 
4: 
#load "../exercises/Paket.fsx"
Paket.download()
Paket.restore()
Paket.generateLoadScripts "net461"

End-to-end

Remove:

1: 
2: 
3: 
4: 
5: 
6: 
/scripting-workshop
  /.paket
    /load
    - paket.exe
  /packages
  /paket-files

Run: fsharpi ./exercises/App.fsx --client Adelaide

ecstasy

DEMO

Using Scripts in a Project

Compiled or Interactive

Why choose?

DEMO

Debugging Scripts

DEMO

Build and Deploy Scripting with FAKE

Review

Deployment Options

Summary

ecstasy agony