The Greater Helsinki Area F# User Group - F# & Azure - FI EN

F# lyhyt oppimäärä

F# on selkeä ohjelmointikieli, joka ohjaa lähtökohtaisesti käyttäjäänsä funktionaalisiin ratkaisuihin. Tätä kautta ohjelman tila mallintuu oikein, ja ohjelmat säikeistyvät hyvin ja niillä on totuttua parempi ylläpidettävyys.

"Functional programming combines the flexibility and power of abstract mathematics with the intuitive clarity of abstract mathematics." - XKCD

Vaikka lyhyt on kaunista, ja F# on tarvittaessa hyvinkin abstrakti kieli, niin tärkeintä ei ole saada aikaan lyhyin syntaksi, vaan tilanteeseen sopivin/selkein. On kielen voimakkuutta, että asiat voidaan tehdä monella tavalla, mutta hyvyyttä, että kieli ohjaa intuitiivisesti parhaaseen ratkaisuun.

Ehkä pieni harppaus kokeellisesta yritys-erehdys-ohjelmistoylläpidosta vähän matemaattisempaan suuntaan ei olisi pahasta?

Koodia oppii lukemaan nopeasti, mutta kivuton kirjoittaminen vaatii aikansa: uuden opettelu vaatii malttia. F#-kääntäjä on tiukempi kuin C#, sen kanssa saa alussa vähän hakata päätä seinään, mutta kun koodi menee kääntäjästä läpi, niin todennäköisemmin ohjelma ei kaadu ajonaikaisesti. Toisaalta se tuo onnistumisentunteita, ja saa C#-veteraaninkin huomaamaan, että koodaaminen voi olla kivaa.

F-Sharp-projektin luonti Visual Studiossa

Voit käyttää Azure-Worker-rolea (josta erilliset ohjeet), tai tehdä kokonaan uuden projektin. Oletuksena projektista kääntyy dll, samoin kuin C#-projektista, ja dll:t ovat yhteensopivia.

Jos haluat tehdä uuden projektin Visual Studiossa, niin se menee näin: File -> New -> Project... -> Other Languages -> Visual F# ->F# Library

C# ja F# yhteispeli (ja miksei myös VB.NET)

C# on parhaimmillaan kun tarvitaan valmiit wizardit riittävät, esim. XAML-kehityksessä. F#:sta voi referoida ja käyttää suoraa C#-dll:iä. Samassa Visual Studio "solutionissa" voi olla sekä F# että C# projekteja, mutta muutosten näkyminen toisesta toiseen vaatii referoitavan projektin kääntämistä.

Lisää kokeeksi C#-projekti (kirjasto, console-app, testiprojekti, tms) samaan "solutioniin" F#-projektin rinnalle, ja lisää viittaus: Solution Explorerista C#-projektin References-kansion päällä hiiren oikeaa nappia ja: Add reference... -> Solution -> rasti F#-projektin ruutuun ja ok.

Tässä malliluokka F#-sisällöksi (näitä käydään tarkemmin läpi alempana):

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
namespace MyLibrary

type MyClass1() = 
    member this.X = "F#"

module MyModule =

    let StaticItems = 42

    let MyList = ["a";"b";"c"] |> List.toSeq

    let MyAddFive x = x+5;

    let MyGenericFunction x = "Hello " + x.ToString();

    type UserData1() = 
        member val Email = "" with get, set

...ja C#-projektin luokasta niitä voi (projektin käännön jälkeen) käyttää ihan suoraa:

var class1 = new MyLibrary.MyClass1();
var fromFs = class1.X;

int Item = MyLibrary.MyModule.StaticItems;

IEnumerable<string> response = MyLibrary.MyModule.MyList;

int seven = MyLibrary.MyModule.MyAddFive(2);

var res = MyLibrary.MyModule.MyGenericFunction<int>(2);

var class2 = new MyLibrary.MyModule.UserData1();
class2.Email = "no@spam.com";

Näin vanhalla C#-koodaajalla on turvaverkko, johon voi luottaa ennenkuin F# on täysin hallinnassa. Samaan tapaan toimisi myös referointi VB.NET-komponenttiin.

Huomattavat rajat:

  • F#:ssa yleensä kirjoitetaan vakioiden/muuttujien/funktioiden nimet pienellä.
    • Tosin se on vain käytäntö, jotta erottaa nämä tyypeistä/luokista.
  • F#-perus-lista on oma linkitetty listansa.
    • Seq on tyypitetty IEnumerable.
  • F#-perus-funktio on myös omansa
    • F#:sta voi kutsua C#-metodia joka ottaa C# Func-parametrin, F#-funktio kelpaa tähän suuntaan.
    • Mutta jos F#:sta palauttaa oletus-funktion metodista ulos, niin C#-kehittäjä vähän hämmästyy.
    • F#:sta voi erikseen luoda ja palauttaa ulos C#:pin Func-tietotyyppin, mutta sen käyttö F#:pin sisällä ei ole luonnollista.
      • C#:ssa Func ja Action ovat eri luokat ja void ei ole tietotyyppi.
      • F# void on nimeltään unit ja se on tietotyyppi.

Näillä rajoilla on merkitystä lähinnä, jos ennalta tiedetään, että kehitetään F#:sta komponenttia C#-kehityksen käyttöön.

Sitten itse kieleen:

Hello world

Käyttäen .NET-luokkia, "Hello World"-ohjelman voi tehdä esim. näin:

1: 
2: 
3: 
open System
let x = "Hello World"
Console.WriteLine x

Lähtökohtaisesti F# ohjaa siihen, että et voi käyttää "muuttujia" (/vakioita) uudestaan, eli kun x on kerran määritelty, niin käytä mieluummin seuraavalla kerralla muuttujaa x2 kuin x. (Tosin ehkä paremmalla nimeämiskäytännöllä.) Oikeastihan tarvitset muuttujia vain ylläpitämään luuppien tilaa. Lähtökohtaisesti F# ohjaa käyttämään rekursiota luuppien sijaan, mutta tästä lisää myöhemmin.

Vertailuoperaatio on vain yksi yhtäkuinmerkki. Kommentit ovat // tai ( ja ). Private ja public näkyvyydet ovat käytettävissä.

Voit myös antaa kohteille vielä selkeämpiä välilyönnillisiä nimiä (tosin huonona puolena suomalaisen näppäimistö-layout:in hankaluus):

1: 
let ``Hello World Container`` = "Hello World"

Interactive

Olettaen, että Resharper ei ole ihan sotkenut pikanäppäimiäsi:

Painamalla Ctrl+Alt+F saat esille interactive-ikkunan ("REPL-loop"), jossa voit suorittaa koodia lennosta. Voit kirjoittaa koodia ikkunaan suoraa, ja suorittaa sen kirjoittamalla ennen enterin painallusta kaksi puolipistettä, esim:

1: 
let z = 4;;

Tai sitten voit suoraa projektitiedostosta maalata kasan koodia, ja lähettää sen interactive-ikkunaan (joko hiiren oikealla napilla ja "Execute in interactive" tai) näppäinyhdistelmällä Alt+Enter.

Yleensä kannattaa ensin antaa haluamansa alkuarvot parametreille ja sitten lähettää haluamansa koodinpala interactiveen.

F#:lle on myös hyvät yksikkötesti- ja BDD-frameworkit, mutta interactiven ansiosta niitä ei tarvita kokeelliseen kehitykseen, vaan lähinnä ylläpidon varmistamiseen.

Tyypitys

Tyyppijärjestelmähän on tarkoitettu siihen, että kääntäjä palvelee koodaajaa, eikä toisinpäin. Oletuksena F# löytää tyypit itsekin, mutta joskus käyttäjä haluaa tyypittää käsin:

1: 
2: 
3: 
let f1 x = x+4.5m 
let f2 (x:decimal) = x+4.5m //argument type
let f3 x :decimal = x+4.5m  //function type

Geneeriset tyypit (klassinen T, T1, T2, jne) merkitään tyyppijärjestelmässä heittomerkillä: 'a, 'b, 'c, jne

Putkitus

F#:ssa on operaatio "|>", joka siirtää metodin viimeisen parametrin ensimmäiseksi:

1: 
2: 
open System
"Hello World" |> Console.WriteLine

Tätä voit ajatella vähän kuin C#-extension-metodina: jos metodit ovat verbejä, on usein luonnollista aloittaa lause substantiivilla, kielestä tulee luonnollisempaa:

"koira haukkuu ihmiselle" vs. "haukkuu(koira, ihmiselle)"

Funktiot

F#:ssa ei tarvita sulkuja funktioparametreissa ja pilkku on varattu Tuple-luokkien käyttöön, joten parametrillisen funktion voit tehdä näin:

1: 
let f x y = x+y

Funktion tyyppisyntaksi on sama kuin C#-kielen Func<...>-luokan parametrit, esim: Func on funktio joka ottaa kaksi numeroa ja palauttaa merkkijonon. F#:ssa parametrien välissä on nuoli, eli vastaavan funktion tyyppisyntaksi on

1: 
int -> int -> string

Funktion tyyppisyntaksista voi hyvin pitkälle päätellä mitä funktio tekee.

Parametriton funktio määriteltäisiin suluilla, tätä tarvitset, jos et halua suorittaa evaluointia heti. return-komentoa ei yleensä tarvi, koska funktio palauttaa viimeisen käskyn arvon ulos joka tapauksessa. Funktiot voivat olla sisäkkäin.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let sayHello () = 
    let hello = 
        let ello = "ello"
        "h"+ello
    let world = 
        "world"
    hello + " " + world

Lambda-funktion voi tehdään fun-sanalla ja nuolella.

Listat

F#:ssa on LINQ:a monipuolisemmat listojenkäsittelykirjastot, mutta voit halutessasi käyttää myös LINQ-kirjastoa, jos sen jo osaat.

F#:ssa näkyy usein käytettävän kolmea eri tyyppiä listoja, joilla on kaikilla suurin piirtein samat käsittelykirjastot. Karrikoidusti tyypit menevät näin:

  • List
    • Ylläpidettävin koodi.
    • Immutable: alkiot eivät muutu
    • Kaksi kaksoispistettä voidaan käyttää tunnistamaan ensimmäinen ja loput alkiot
    • At-merkkiä "@" voi käyttää yhdistämään kaksi listaa.
  • Array
    • Tehokkain suoritus. Vastaa C#-arrayta.
    • Lähtökohtaisesti ei ole hyvä tapa viitata suoraa indeksiin. Se tapahtuu laittamalla piste ennen hakasulkuja.
  • Seq
    • Yhteensopivin. Tämä vastaa C# IEnumerablea.

Listoja voi muuttaa muodosta toiseen: List.toArray, List.toSeq, Seq.toList, Array.toSeq, jne. Pari koodiesimerkkiä:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
let emptyList = []

let listOfLists = [[1;2;3];[4;5;6]]

let myList = 
    [1..10] |> List.filter(fun i -> i%2=0)
let mySeq =
    [|1..10|] |> Seq.filter(fun i -> i%2=0)

let merged = [1;2;3] @ [4;5;6]

let head::tail = ["h";"t1";"t2";"t3"]

let ``A and H to Z`` = 'A'::['H'..'Z']

open System.Linq
let threeFirst = 
    mySeq.Take(3)
    |> Seq.toArray
    |> Array.map(fun i -> i*3)

Funktioita voi myös yhdistellä (function composition) ">>" -merkillä, tosin tätä näkee harvemmin. "Tee ensin tämä, sitten tämä."

1: 
2: 
3: 
let composed = 
    List.filter(fun i -> i%2=0) >> List.map(fun i -> i+5)
[6; 5; 3; 2; 4] |> composed

Listan tyypin voi halutessaan merkitä joko C#-tyylisesti tai OCaml-tyylisesti:

1: 
2: 
let emptyList2:list<bigint> = []
let emptyList3:bigint list = []

Tuple

Listojen lisäksi toinen yleinen rakenne on Tuple: lista on n-kappaletta yhtä samaa tyyppiä, tuple on yksi kappale n:ää eri tyyppiä. C#:ssa on myös Tuple, ja tämä luokka on sama.

Voit toki tehdä Tuplen C#-tapaan System.Tuple.Create(1, "a", 1.0), mutta tähän on myös helpompi tapa, pelkkä pilkku. Tuplen voi myös takaisin purkaa osiinsa, joko fst- ja snd-käskyillä, tai sitten määrittelemällä "temp-muuttujat" yhtäkuin-merkin vasemmalla puolella:

1: 
2: 
let tuple = 1, "a", 1.0
let a, b, c = tuple

Tyyppisyntaksissa tuple merkitään tähdellä, esim: int*string.

Luokat

F# on myös erinomainen oliokieli.

Olio, jolla on yksi property, email, jossa on getteri ja setteri, ja oletusarvo sille on tyhjä merkkijono, tehtäisiin näin:

1: 
2: 
3: 
4: 
type UserData1() = 
   member val Email = "" with get, set

let myInstance1 = UserData1()

Kun luot uuden instanssin oliosta, niin new-sana on vapaaehtoinen.

Olio, jolla on yksi konstruktorissa asetettava readonly-property, email, ja lisäksi tyhjä konstruktori, joka asettaa tyhjän arvon tehtäisiin näin:

1: 
2: 
3: 
type UserData2(email) =
   new() = UserData2("")
   member x.Email = email

Perintä-syntaksi ei ole yhtä näppärä kuin C#:ssa, koska vahvaan tyyppitarkistukseen rajapinnat pitää aina periyttää eksplisiittisesti:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type Base() =
    let DoSomething() = ()
    abstract member Method : unit -> unit
    default x.Method() = DoSomething()

type ``My inherited class with dispose``() =
    inherit Base()
    override x.Method() =
        base.Method()

    interface IDisposable with
        override x.Dispose() = 
            //Dispose resources...
            GC.SuppressFinalize(x)

Discriminated union

Voit tehdä "joko-tai"-tyyppejä:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type myEnum =
| A
| B

type CalendarEvent =
| ExactTime of DateTime
| MonthAndYear of string*int //month*year

type Tree =
| Leaf of int
| Node of Tree*Tree

Käyttötarkoituksena esimerkiksi ohjelman tilanhallinta, jo kääntäjätasolla, ilman sotkuista ajonaikaista if-logiikkaviidakkoa. Nämä ovat erittäin käteviä yhdessä pattern matchingin kanssa.

Astetta erikoisemmat tyypit

Tyyppi voi olla myös vain alias, tai "joko-tai" voi esiintyä myös yksinään:

1: 
2: 
3: 
type aliasItem<'a,'b> = System.Collections.Generic.KeyValuePair<'a,'b>

type OrderId = | Id of System.Guid

Tyyppi voi olla tietue, record:

1: 
2: 
3: 
4: 
5: 
6: 
type Address = { 
    Street: string; 
    Zip: int; 
    City: string; 
}
let myAddrr = { Street = "Juvank."; Zip = 33710; City = "Tre"; }

...tai sillä voidaan Measure-attribuutin kanssa määritellä vahva vain-käännösaikainen tyypitys. Vähän kuin oma luokka kapseloimaan vain yksi laskennallinen arvo (mutta näyttämään se aina ulos), jotta eri asiat tai mittayksiköt eivät varmasti mene sekaisin vahingossa:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
[<Measure>]
type EUR

[<Measure>]
type LTC

let rate =
    let ratePlain = 9.45m //fetch from internet...
    ratePlain * 1m<EUR/LTC>

let myMoneyInEur (x:decimal<LTC>) = x*rate 

let converted = myMoneyInEur 7m<LTC>

Helpoin tapa muuttaa perus-.NET-tyyppi mitaksi on kertoa se yhdellä mitalla. Esteettinen haittapuoli on, että tämä tuo "pienempi kuin" ja "suurempi kuin" merkkejä koodiin.

Pattern matching

Pattern-matching on tavallaan selkeä switch/case tai if-elseif-kuvio, jossa ehto ei voi vaihtua kesken kaiken, mutta eri case:illa ei ole constant-vaatimusta. Eri patterneita on useita.

Lisäksi funktio voi suoraa olla pattern-funktio tai voidaan käyttää ns. active-patterneita selkeyttämään koodia. Tässä sama toiminnallisuus usealla eri kooditavalla:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
let MyFunction1 a =
   if a = "A" then 1
   elif a = "B" then 2
   else 3

let MyFunction2 a = 
   match a with
   | "A" -> 1
   | "B" -> 2
   | _ -> 3

let MyFunction3 = fun a -> 
   match a with
   | "A" -> 1
   | "B" -> 2
   | _ -> 3

let MyFunction4 = function
   | "A" -> 1
   | "B" -> 2
   | _ -> 3

let MyFunction5 a = 
   match a with
   | x when x = "A" -> 1
   | x when x = "B" -> 2
   | x -> 3


let MyFunction6 a = 

    let (|FirstVowel|FirstConsonant|Other|) p =
        match p with
        | "A" -> FirstVowel
        | "B" -> FirstConsonant
        | _ -> Other

    match a with
    | FirstVowel -> 1
    | FirstConsonant -> 2
    | Other -> 3

Usein, jos mahdollista, on parempi jättää oletus-tapaus kokonaan pois, koska silloin kääntäjä saa kiinni virheet, jos tuntemattomia tapauksia luodaan jälkikäteen koodiin lisää.

Nullittomuus ja option-type

C#:ssa NULL on sekä "special case" (Fowler: PoEAA) että un-assigned-muuttuja. Kaksi roolia yhdellä asialla tekee sen käytöstä sotkuista.

Lähtökohtaisesti F#:ssa NULL ei ole käytössä. Sen sijaan on käytössä option-type, asialle jota ei ole tiedossa:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
let echo x = "You have to work " + x.ToString() + " hours."

let ``working hours`` =
    let day = (int System.DateTime.Now.DayOfWeek)
    match day with
    | 0 | 6 -> None
    | _ -> Some(7.5)

let echoHours1 = 
    match ``working hours`` with
    | None -> ""
    | Some h -> echo h

let echoHours2 = 
    ``working hours`` 
    |> Option.map(fun h -> echo h)

let echoHours3 = 
    ``working hours`` 
    |> Option.map(echo)

Rekursiivisuus

Rekursiiviseen funktioon kirjoitetaan rec. Usein itse rekursio kapseloidaan toisen funktion sisään, jotta voidaan peittää "turhat" alkuarvot. Usein myös käytetään muotoa, jossa viimeisenä parametrina menee "akkumulaattori", johon kerätään tulos. Tämä siksi, että kääntäjä optimoi häntärekursiolla stäkin pois, ts. ei tule StackOverflowException:ia, vaikka läpikäytävä rekursio olisi iso.

1: 
2: 
3: 
4: 
5: 
6: 
let sumItems myList =
    let rec calculate li acc =
        match li with
        |[] -> acc
        |h::t -> calculate t (h+acc)
    calculate myList 0

Tämä tyypillinen funktio löytyy tietysti myös suoraa List-kirjastosta, mutta jos tarvitaan enemmän custom-toiminnallisuutta, niin silloin oma rekursio on joskus paikallaan.

Computation Expressions

Nämä ovat eräänlaisia kapseleita kapseloimaan toiminnallisuuden sivuvaikutuksia. Valmiita ovat mm. seq (listoille) ja async (asynkroniselle koodille), mutta näitä voi tehdä itse lisää.

Asynkronisuuden sivuvaikutus on odottaminen. Async.RunSynchronously nyt ei itsessään ole kovin hyödyllinen, mutta Async:illa on paljon muitakin vaihtoehtoja.

Asian ydin on se, että kun katsotaan koodia kohdassa sum = x + y, niin se näyttää hyvin samalta kuin perus F#-koodi muutenkin, vaikka ollaan sivuvaikutuksen piirissä. Ja itse sivuvaikutus, async, ilmenee koodista eksplisiittisesti. Huutomerkki-syntaksi tarkoittaa kutsua "computation expression"-rajapintaan.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let asyncOperation = Async.Sleep 3000

let waitAndReturn = 
    async {
        do! asyncOperation
        return 3.0
    }

let waitAndReturn2 = 
    async {
        let! x = waitAndReturn
        let y = 7.0
        let sum = x + y
        return sum
    }

let result = Async.RunSynchronously waitAndReturn2

Jos verrataan tätä C#:piin, niin seq { ... } vastaava on LINQ. Mutta LINQ on tavallaan oma kielensä, joka koodaajan on opeteltava erikseen. Ja koodaaja ei tiedä tekeekö LINQ:a IEnumerablea, IQueryablea, IObservablea, vai mitä vasten... Vähän naiivi ja C#-henkinen, mutta toimiva esimerkki seq-expressionista:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
open System.Linq
let slice mySeq = 
    let chunkSize = 5
    let rec seqSlice (s:int seq) =
        seq{
            yield s.Take chunkSize
            yield! seqSlice (s.Skip chunkSize)
        }
    seqSlice mySeq

"Computation Expressions" on kauniimpi nimi asialle, josta käytetään myös ilmaisua "Monad".

Harjoitustehtäviä

  • Luo rekursiivinen ohjelma joka listaa 1000 ensimmäistä Fibonacci-lukujonon lukua (1, 1, 2, 3, 5, 8, 13, ...).
  • Luo C#-yksikkötesti tai konsoliaplikaatio kutsumaan edellistä.

Linkit

Jos tämä oli liian hapokasta, niin kannattaa katsoa läpi tämä suomenkielinen materiaali ja [tämä englanninkielinen tutorial] (http://www.tryfsharp.org/Learn). Jos haluat opetella listakirjastojen toiminnallisuuksia, niin koita tehdä Project Euler -tehtäviä. Jos haluat itsenäisen pienen koodiesimerkin jostain aiheesta, niin fssnip.net on hyvä saitti lähteä etsimään.

Takaisin valikkoon

Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  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 toSeq : list:'T list -> seq<'T>

Full name: Microsoft.FSharp.Collections.List.toSeq
Multiple items
type UserData1 =
  new : unit -> UserData1
  member Email : string
  member Email : string with set

Full name: FSharpBasicsFin.UserData1

--------------------
new : unit -> UserData1
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
namespace System
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

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

--------------------
type decimal = System.Decimal

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
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<_>
Multiple items
val string : value:'T -> string

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

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

Full name: Microsoft.FSharp.Core.string
val sayHello : unit -> string

Full name: FSharpBasicsFin.sayHello
val hello : string
val ello : string
val world : string
val emptyList : 'a list

Full name: FSharpBasicsFin.emptyList
val listOfLists : int list list

Full name: FSharpBasicsFin.listOfLists
val myList : int list

Full name: FSharpBasicsFin.myList
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val i : int
val mySeq : seq<int>

Full name: FSharpBasicsFin.mySeq
module Seq

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

Full name: Microsoft.FSharp.Collections.Seq.filter
val merged : int list

Full name: FSharpBasicsFin.merged
val head : string

Full name: FSharpBasicsFin.head
val tail : string list

Full name: FSharpBasicsFin.tail
val ( A and H to Z ) : char list

Full name: FSharpBasicsFin.( A and H to Z )
namespace System.Linq
val threeFirst : int []

Full name: FSharpBasicsFin.threeFirst
(extension) System.Collections.Generic.IEnumerable.Take<'TSource>(count: int) : System.Collections.Generic.IEnumerable<'TSource>
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
module Array

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val composed : (int list -> int list)

Full name: FSharpBasicsFin.composed
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val emptyList2 : bigint list

Full name: FSharpBasicsFin.emptyList2
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
type bigint = System.Numerics.BigInteger

Full name: Microsoft.FSharp.Core.bigint
val emptyList3 : bigint list

Full name: FSharpBasicsFin.emptyList3
val tuple : int * string * float

Full name: FSharpBasicsFin.tuple
val a : int

Full name: FSharpBasicsFin.a
val b : string

Full name: FSharpBasicsFin.b
val c : float

Full name: FSharpBasicsFin.c
val myInstance1 : UserData1

Full name: FSharpBasicsFin.myInstance1
Multiple items
type UserData2 =
  new : unit -> UserData2
  new : email:string -> UserData2
  member Email : string

Full name: FSharpBasicsFin.UserData2

--------------------
new : unit -> UserData2
new : email:string -> UserData2
val email : string
val x : UserData2
member UserData2.Email : string

Full name: FSharpBasicsFin.UserData2.Email
Multiple items
type Base =
  new : unit -> Base
  abstract member Method : unit -> unit
  override Method : unit -> unit

Full name: FSharpBasicsFin.Base

--------------------
new : unit -> Base
val DoSomething : (unit -> unit)
abstract member Base.Method : unit -> unit

Full name: FSharpBasicsFin.Base.Method
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
val x : Base
override Base.Method : unit -> unit

Full name: FSharpBasicsFin.Base.Method
val x : My inherited class with dispose
override My inherited class with dispose.Method : unit -> unit

Full name: FSharpBasicsFin.My inherited class with dispose.Method
override My inherited class with dispose.Dispose : unit -> 'a

Full name: FSharpBasicsFin.My inherited class with dispose.Dispose
type myEnum =
  | A
  | B

Full name: FSharpBasicsFin.myEnum
union case myEnum.A: myEnum
union case myEnum.B: myEnum
type CalendarEvent =
  | ExactTime of obj
  | MonthAndYear of string * int

Full name: FSharpBasicsFin.CalendarEvent
union case CalendarEvent.ExactTime: obj -> CalendarEvent
union case CalendarEvent.MonthAndYear: string * int -> CalendarEvent
type Tree =
  | Leaf of int
  | Node of Tree * Tree

Full name: FSharpBasicsFin.Tree
union case Tree.Leaf: int -> Tree
union case Tree.Node: Tree * Tree -> Tree
type aliasItem<'a,'b> = System.Collections.Generic.KeyValuePair<'a,'b>

Full name: FSharpBasicsFin.aliasItem<_,_>
namespace System.Collections
namespace System.Collections.Generic
Multiple items
type KeyValuePair<'TKey,'TValue> =
  struct
    new : key:'TKey * value:'TValue -> KeyValuePair<'TKey, 'TValue>
    member Key : 'TKey
    member ToString : unit -> string
    member Value : 'TValue
  end

Full name: System.Collections.Generic.KeyValuePair<_,_>

--------------------
System.Collections.Generic.KeyValuePair()
System.Collections.Generic.KeyValuePair(key: 'TKey, value: 'TValue) : unit
type OrderId = | Id of Guid

Full name: FSharpBasicsFin.OrderId
union case OrderId.Id: System.Guid -> OrderId
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 4 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    static member Parse : input:string -> Guid
    static member ParseExact : input:string * format:string -> Guid
    ...
  end

Full name: System.Guid

--------------------
System.Guid()
System.Guid(b: byte []) : unit
System.Guid(g: string) : unit
System.Guid(a: int, b: int16, c: int16, d: byte []) : unit
System.Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
System.Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
type Address =
  {Street: string;
   Zip: int;
   City: string;}

Full name: FSharpBasicsFin.Address
Address.Street: string
Address.Zip: int
Address.City: string
val myAddrr : Address

Full name: FSharpBasicsFin.myAddrr
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

--------------------
new : unit -> MeasureAttribute
[<Measure>]
type EUR

Full name: FSharpBasicsFin.EUR
[<Measure>]
type LTC

Full name: FSharpBasicsFin.LTC
val rate : decimal<EUR/LTC>

Full name: FSharpBasicsFin.rate
val ratePlain : decimal
val myMoneyInEur : x:decimal<LTC> -> decimal<EUR>

Full name: FSharpBasicsFin.myMoneyInEur
val x : decimal<LTC>
val converted : decimal<EUR>

Full name: FSharpBasicsFin.converted
val MyFunction1 : a:string -> int

Full name: FSharpBasicsFin.MyFunction1
val a : string
val MyFunction2 : a:string -> int

Full name: FSharpBasicsFin.MyFunction2
val MyFunction3 : a:string -> int

Full name: FSharpBasicsFin.MyFunction3
val MyFunction4 : _arg1:string -> int

Full name: FSharpBasicsFin.MyFunction4
val MyFunction5 : a:string -> int

Full name: FSharpBasicsFin.MyFunction5
val x : string
val MyFunction6 : a:string -> int

Full name: FSharpBasicsFin.MyFunction6
val p : string
active recognizer FirstVowel: string -> Choice<unit,unit,unit>
active recognizer FirstConsonant: string -> Choice<unit,unit,unit>
active recognizer Other: string -> Choice<unit,unit,unit>
val echo : x:'a -> string

Full name: FSharpBasicsFin.echo
val x : 'a
System.Object.ToString() : string
val ( working hours ) : float option

Full name: FSharpBasicsFin.( working hours )
val day : int
Multiple items
type DateTime =
  struct
    new : ticks:int64 -> DateTime + 10 overloads
    member Add : value:TimeSpan -> DateTime
    member AddDays : value:float -> DateTime
    member AddHours : value:float -> DateTime
    member AddMilliseconds : value:float -> DateTime
    member AddMinutes : value:float -> DateTime
    member AddMonths : months:int -> DateTime
    member AddSeconds : value:float -> DateTime
    member AddTicks : value:int64 -> DateTime
    member AddYears : value:int -> DateTime
    ...
  end

Full name: System.DateTime

--------------------
System.DateTime()
   (+0 other overloads)
System.DateTime(ticks: int64) : unit
   (+0 other overloads)
System.DateTime(ticks: int64, kind: System.DateTimeKind) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, calendar: System.Globalization.Calendar) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: System.DateTimeKind) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: System.Globalization.Calendar) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: System.DateTimeKind) : unit
   (+0 other overloads)
property System.DateTime.Now: System.DateTime
property System.DateTime.DayOfWeek: System.DayOfWeek
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
val echoHours1 : string

Full name: FSharpBasicsFin.echoHours1
val h : float
val echoHours2 : string option

Full name: FSharpBasicsFin.echoHours2
module Option

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

Full name: Microsoft.FSharp.Core.Option.map
val echoHours3 : string option

Full name: FSharpBasicsFin.echoHours3
val sumItems : myList:int list -> int

Full name: FSharpBasicsFin.sumItems
val myList : int list
val calculate : (int list -> int -> int)
val li : int list
val acc : int
val h : int
val t : int list
val asyncOperation : Async<unit>

Full name: FSharpBasicsFin.asyncOperation
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.Sleep : millisecondsDueTime:int -> Async<unit>
val waitAndReturn : Async<float>

Full name: FSharpBasicsFin.waitAndReturn
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val waitAndReturn2 : Async<float>

Full name: FSharpBasicsFin.waitAndReturn2
val x : float
val y : float
val sum : float
val result : float

Full name: FSharpBasicsFin.result
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:System.Threading.CancellationToken -> 'T
val slice : mySeq:seq<int> -> seq<System.Collections.Generic.IEnumerable<int>>

Full name: FSharpBasicsFin.slice
val mySeq : seq<int>
val chunkSize : int
val seqSlice : (seq<int> -> seq<System.Collections.Generic.IEnumerable<int>>)
val s : seq<int>
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<_>
(extension) System.Collections.Generic.IEnumerable.Skip<'TSource>(count: int) : System.Collections.Generic.IEnumerable<'TSource>

Creative Commons -copyright Tuomas Hietanen, 2014, thorium(at)iki.fi, Creative Commons