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
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.
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<_>
Full name: Microsoft.FSharp.Collections.List.toSeq
type UserData1 =
new : unit -> UserData1
member Email : string
member Email : string with set
Full name: FSharpBasicsFin.UserData1
--------------------
new : unit -> UserData1
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
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<_>
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 string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Full name: FSharpBasicsFin.sayHello
Full name: FSharpBasicsFin.emptyList
Full name: FSharpBasicsFin.listOfLists
Full name: FSharpBasicsFin.myList
Full name: Microsoft.FSharp.Collections.List.filter
Full name: FSharpBasicsFin.mySeq
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.filter
Full name: FSharpBasicsFin.merged
Full name: FSharpBasicsFin.head
Full name: FSharpBasicsFin.tail
Full name: FSharpBasicsFin.( A and H to Z )
Full name: FSharpBasicsFin.threeFirst
Full name: Microsoft.FSharp.Collections.Seq.toArray
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Array.map
Full name: FSharpBasicsFin.composed
Full name: Microsoft.FSharp.Collections.List.map
Full name: FSharpBasicsFin.emptyList2
Full name: Microsoft.FSharp.Collections.list<_>
Full name: Microsoft.FSharp.Core.bigint
Full name: FSharpBasicsFin.emptyList3
Full name: FSharpBasicsFin.tuple
Full name: FSharpBasicsFin.a
Full name: FSharpBasicsFin.b
Full name: FSharpBasicsFin.c
Full name: FSharpBasicsFin.myInstance1
type UserData2 =
new : unit -> UserData2
new : email:string -> UserData2
member Email : string
Full name: FSharpBasicsFin.UserData2
--------------------
new : unit -> UserData2
new : email:string -> UserData2
Full name: FSharpBasicsFin.UserData2.Email
type Base =
new : unit -> Base
abstract member Method : unit -> unit
override Method : unit -> unit
Full name: FSharpBasicsFin.Base
--------------------
new : unit -> Base
Full name: FSharpBasicsFin.Base.Method
Full name: Microsoft.FSharp.Core.unit
Full name: FSharpBasicsFin.Base.Method
Full name: FSharpBasicsFin.My inherited class with dispose.Method
Full name: FSharpBasicsFin.My inherited class with dispose.Dispose
| A
| B
Full name: FSharpBasicsFin.myEnum
| ExactTime of obj
| MonthAndYear of string * int
Full name: FSharpBasicsFin.CalendarEvent
| Leaf of int
| Node of Tree * Tree
Full name: FSharpBasicsFin.Tree
Full name: FSharpBasicsFin.aliasItem<_,_>
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
Full name: FSharpBasicsFin.OrderId
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
{Street: string;
Zip: int;
City: string;}
Full name: FSharpBasicsFin.Address
Full name: FSharpBasicsFin.myAddrr
type MeasureAttribute =
inherit Attribute
new : unit -> MeasureAttribute
Full name: Microsoft.FSharp.Core.MeasureAttribute
--------------------
new : unit -> MeasureAttribute
type EUR
Full name: FSharpBasicsFin.EUR
type LTC
Full name: FSharpBasicsFin.LTC
Full name: FSharpBasicsFin.rate
Full name: FSharpBasicsFin.myMoneyInEur
Full name: FSharpBasicsFin.converted
Full name: FSharpBasicsFin.MyFunction1
Full name: FSharpBasicsFin.MyFunction2
Full name: FSharpBasicsFin.MyFunction3
Full name: FSharpBasicsFin.MyFunction4
Full name: FSharpBasicsFin.MyFunction5
Full name: FSharpBasicsFin.MyFunction6
Full name: FSharpBasicsFin.echo
Full name: FSharpBasicsFin.( working hours )
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)
Full name: FSharpBasicsFin.echoHours1
Full name: FSharpBasicsFin.echoHours2
from Microsoft.FSharp.Core
Full name: Microsoft.FSharp.Core.Option.map
Full name: FSharpBasicsFin.echoHours3
Full name: FSharpBasicsFin.sumItems
Full name: FSharpBasicsFin.asyncOperation
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<_>
Full name: FSharpBasicsFin.waitAndReturn
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Full name: FSharpBasicsFin.waitAndReturn2
Full name: FSharpBasicsFin.result
Full name: FSharpBasicsFin.slice
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<_>