689 lines
22 KiB
FSharp
689 lines
22 KiB
FSharp
// This sample will guide you through elements of the F# language.
|
|
|
|
//
|
|
// *******************************************************************************************************
|
|
// To execute the code in F# Interactive, highlight a section of code and press Alt-Enter in Windows or
|
|
// Ctrl-Enter Mac, or right-click and select "Send Selection to F# Interactive".
|
|
// You can open the F# Interactive Window from the "View" menu.
|
|
// *******************************************************************************************************
|
|
|
|
// For more about F#, see:
|
|
// http://fsharp.org
|
|
//
|
|
// For additional templates to use with F#, see the 'Online Templates' in Visual Studio,
|
|
// 'New Project' --> 'Online Templates'
|
|
//
|
|
// For specific F# topics, see:
|
|
// http://fsharp.org (F# Open Organization)
|
|
// http://tryfsharp.org (F# Learning Portal)
|
|
// http://go.microsoft.com/fwlink/?LinkID=234174 (Visual F# Development Portal)
|
|
// http://go.microsoft.com/fwlink/?LinkID=124614 (Visual F# Code Gallery)
|
|
// http://go.microsoft.com/fwlink/?LinkId=235173 (Visual F# Math/Stats Programming)
|
|
// http://go.microsoft.com/fwlink/?LinkId=235176 (Visual F# Charting)
|
|
// http://github.com/ChrisMarinos/FSharpKoans (F# Koans)
|
|
|
|
// Contents:
|
|
|
|
// - Integers and basic functions
|
|
// - Booleans
|
|
// - Strings
|
|
// - Tuples
|
|
// - Lists and list processing
|
|
// - Classes
|
|
// - Generic classes
|
|
// - Implementing interfaces
|
|
// - Arrays
|
|
// - Sequences
|
|
// - Recursive functions
|
|
// - Record types
|
|
// - Union types
|
|
// - Option types
|
|
// - Pattern matching
|
|
// - Units of measure
|
|
// - Parallel array programming
|
|
// - Using events
|
|
// - Database access using type providers
|
|
// - OData access using type providers
|
|
|
|
// ---------------------------------------------------------------
|
|
// Integers and basic functions
|
|
// ---------------------------------------------------------------
|
|
|
|
module Integers =
|
|
let sampleInteger = 176
|
|
|
|
/// Do some arithmetic starting with the first integer
|
|
let sampleInteger2 = (sampleInteger/4 + 5 - 7) * 4
|
|
|
|
/// A list of the numbers from 0 to 99
|
|
let sampleNumbers = [ 0 .. 99 ]
|
|
|
|
/// A list of all tuples containing all the numbers from 0 to 99 and their squares
|
|
let sampleTableOfSquares = [ for i in 0 .. 99 -> (i, i*i) ]
|
|
|
|
// The next line prints a list that includes tuples, using %A for generic printing
|
|
printfn "The table of squares from 0 to 99 is:\n%A" sampleTableOfSquares
|
|
|
|
|
|
module BasicFunctions =
|
|
|
|
// Use 'let' to define a function that accepts an integer argument and returns an integer.
|
|
let func1 x = x*x + 3
|
|
|
|
// Parenthesis are optional for function arguments
|
|
let func1a (x) = x*x + 3
|
|
|
|
/// Apply the function, naming the function return result using 'let'.
|
|
/// The variable type is inferred from the function return type.
|
|
let result1 = func1 4573
|
|
|
|
printfn "The result of squaring the integer 4573 and adding 3 is %d" result1
|
|
|
|
// When needed, annotate the type of a parameter name using '(argument:type)'
|
|
let func2 (x:int) = 2*x*x - x/5 + 3
|
|
|
|
let result2 = func2 (7 + 4)
|
|
|
|
printfn "The result of applying the 1st sample function to (7 + 4) is %d" result2
|
|
|
|
let func3 x =
|
|
if x < 100.0 then
|
|
2.0*x*x - x/5.0 + 3.0
|
|
else
|
|
2.0*x*x + x/5.0 - 37.0
|
|
|
|
let result3 = func3 (6.5 + 4.5)
|
|
|
|
printfn "The result of applying the 2nd sample function to (6.5 + 4.5) is %f" result3
|
|
|
|
// ---------------------------------------------------------------
|
|
// Booleans
|
|
// ---------------------------------------------------------------
|
|
|
|
module SomeBooleanValues =
|
|
|
|
let boolean1 = true
|
|
|
|
let boolean2 = false
|
|
|
|
let boolean3 = not boolean1 && (boolean2 || false)
|
|
|
|
printfn "The expression 'not boolean1 && (boolean2 || false)' is %A" boolean3
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Strings
|
|
// ---------------------------------------------------------------
|
|
|
|
module StringManipulation =
|
|
|
|
let string1 = "Hello"
|
|
|
|
let string2 = "world"
|
|
|
|
/// Use @ to create a verbatim string literal
|
|
|
|
let string3 = @"c:\Program Files\"
|
|
|
|
/// Using a triple-quote string literal
|
|
let string4 = """He said "hello world" after you did"""
|
|
|
|
let helloWorld = string1 + " " + string2 // concatenate the two strings with a space in between
|
|
|
|
printfn "%s" helloWorld
|
|
|
|
/// A string formed by taking the first 7 characters of one of the result strings
|
|
let substring = helloWorld.[0..6]
|
|
|
|
printfn "%s" substring
|
|
|
|
// ---------------------------------------------------------------
|
|
// Tuples (ordered sets of values)
|
|
// ---------------------------------------------------------------
|
|
|
|
module Tuples =
|
|
|
|
/// A simple tuple of integers
|
|
let tuple1 = (1, 2, 3)
|
|
|
|
/// A function that swaps the order of two values in a tuple.
|
|
/// QuickInfo shows that the function is inferred to have a generic type.
|
|
|
|
let swapElems (a, b) = (b, a)
|
|
|
|
printfn "The result of swapping (1, 2) is %A" (swapElems (1,2))
|
|
|
|
/// A tuple consisting of an integer, a string, and a double-precision floating point number
|
|
let tuple2 = (1, "fred", 3.1415)
|
|
|
|
printfn "tuple1: %A tuple2: %A" tuple1 tuple2
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Lists and list processing
|
|
// ---------------------------------------------------------------
|
|
|
|
module Lists =
|
|
|
|
let list1 = [ ] /// an empty list
|
|
|
|
let list2 = [ 1; 2; 3 ] /// list of 3 elements
|
|
|
|
let list3 = 42 :: list2 /// a new list with '42' added to the beginning
|
|
|
|
let numberList = [ 1 .. 1000 ] /// list of integers from 1 to 1000
|
|
|
|
/// A list containing all the days of the year
|
|
let daysList =
|
|
[ for month in 1 .. 12 do
|
|
for day in 1 .. System.DateTime.DaysInMonth(2012, month) do
|
|
yield System.DateTime(2012, month, day) ]
|
|
|
|
/// A list containing the tuples which are the coordinates of the black squares on a chess board.
|
|
let blackSquares =
|
|
|
|
[ for i in 0 .. 7 do
|
|
for j in 0 .. 7 do
|
|
if (i+j) % 2 = 1 then
|
|
yield (i, j) ]
|
|
|
|
/// Square the numbers in numberList, using the pipeline operator to pass an argument to List.map
|
|
let squares =
|
|
numberList
|
|
|> List.map (fun x -> x*x)
|
|
|
|
/// Computes the sum of the squares of the numbers divisible by 3.
|
|
let sumOfSquaresUpTo n =
|
|
numberList
|
|
|> List.filter (fun x -> x % 3 = 0)
|
|
|> List.sumBy (fun x -> x * x)
|
|
|
|
// ---------------------------------------------------------------
|
|
// Classes
|
|
// ---------------------------------------------------------------
|
|
|
|
module DefiningClasses =
|
|
/// The class's constructor takes two arguments: dx and dy, both of type 'float'.
|
|
type Vector2D(dx : float, dy : float) =
|
|
|
|
/// The length of the vector, computed when the object is constructed
|
|
let length = sqrt (dx*dx + dy*dy)
|
|
|
|
// 'this' specifies a name for the object's self identifier
|
|
// In instance methods, it must appear before the member name.
|
|
|
|
member this.DX = dx
|
|
|
|
member this.DY = dy
|
|
|
|
member this.Length = length
|
|
|
|
member this.Scale(k) = Vector2D(k * this.DX, k * this.DY)
|
|
|
|
|
|
/// An instance of the Vector2D class
|
|
let vector1 = Vector2D(3.0, 4.0)
|
|
|
|
/// Get a new scaled vector object, without modifying the original object
|
|
let vector2 = vector1.Scale(10.0)
|
|
|
|
printfn "Length of vector1: %f Length of vector2: %f" vector1.Length vector2.Length
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Generic classes
|
|
// ---------------------------------------------------------------
|
|
|
|
module DefiningGenericClasses =
|
|
|
|
type StateTracker<'T>(initialElement: 'T) = // 'T is the type parameter for the class
|
|
|
|
/// Store the states in an array
|
|
let mutable states = [ initialElement ]
|
|
|
|
/// Add a new element to the list of states
|
|
member this.UpdateState newState =
|
|
states <- newState :: states // use the '<-' operator to mutate the value
|
|
|
|
/// Get the entire list of historical states
|
|
member this.History = states
|
|
|
|
|
|
/// Get the latest state
|
|
member this.Current = states.Head
|
|
|
|
/// An 'int' instance of the state tracker class. Note that the type parameter is inferred.
|
|
let tracker = StateTracker 10
|
|
|
|
// Add a state
|
|
tracker.UpdateState 17
|
|
|
|
// ---------------------------------------------------------------
|
|
// Implementing interfaces
|
|
// ---------------------------------------------------------------
|
|
|
|
/// Type that implements IDisposable
|
|
|
|
type ReadFile() =
|
|
|
|
let file = new System.IO.StreamReader("readme.txt")
|
|
|
|
member this.ReadLine() = file.ReadLine()
|
|
|
|
// this class's implementation of IDisposable members
|
|
interface System.IDisposable with
|
|
member this.Dispose() = file.Close()
|
|
|
|
// ---------------------------------------------------------------
|
|
// Arrays
|
|
// ---------------------------------------------------------------
|
|
|
|
module Arrays =
|
|
|
|
/// The empty array
|
|
let array1 = [| |]
|
|
|
|
let array2 = [| "hello"; "world"; "and"; "hello"; "world"; "again" |]
|
|
|
|
let array3 = [| 1 .. 1000 |]
|
|
|
|
/// An array containing only the words "hello" and "world"
|
|
|
|
let array4 = [| for word in array2 do
|
|
if word.Contains("l") then
|
|
yield word |]
|
|
|
|
/// An array initialized by index and containing the even numbers from 0 to 2000
|
|
let evenNumbers = Array.init 1001 (fun n -> n * 2)
|
|
|
|
/// sub-array extracted using slicing notation
|
|
let evenNumbersSlice = evenNumbers.[0..500]
|
|
|
|
for word in array4 do
|
|
printfn "word: %s" word
|
|
|
|
// modify an array element using the left arrow assignment operator
|
|
array2.[1] <- "WORLD!"
|
|
|
|
/// Calculates the sum of the lengths of the words that start with 'h'
|
|
let sumOfLengthsOfWords =
|
|
array2
|
|
|> Array.filter (fun x -> x.StartsWith "h")
|
|
|> Array.sumBy (fun x -> x.Length)
|
|
|
|
// ---------------------------------------------------------------
|
|
// Sequences
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
module Sequences =
|
|
|
|
// Sequences are evaluated on-demand and are re-evaluated each time they are iterated.
|
|
// An F# sequence is an instance of a System.Collections.Generic.IEnumerable<'T>,
|
|
// so Seq functions can be applied to Lists and Arrays as well.
|
|
|
|
/// The empty sequence
|
|
let seq1 = Seq.empty
|
|
|
|
let seq2 = seq { yield "hello"; yield "world"; yield "and"; yield "hello"; yield "world"; yield "again" }
|
|
|
|
let numbersSeq = seq { 1 .. 1000 }
|
|
|
|
/// another array containing only the words "hello" and "world"
|
|
let seq3 =
|
|
seq { for word in seq2 do
|
|
if word.Contains("l") then
|
|
yield word }
|
|
|
|
let evenNumbers = Seq.init 1001 (fun n -> n * 2)
|
|
|
|
let rnd = System.Random()
|
|
|
|
/// An infinite sequence which is a random walk
|
|
// Use yield! to return each element of a subsequence, similar to IEnumerable.SelectMany.
|
|
|
|
let rec randomWalk x =
|
|
seq { yield x
|
|
yield! randomWalk (x + rnd.NextDouble() - 0.5) }
|
|
|
|
let first100ValuesOfRandomWalk =
|
|
randomWalk 5.0
|
|
|> Seq.truncate 100
|
|
|> Seq.toList
|
|
|
|
// ---------------------------------------------------------------
|
|
// Recursive functions
|
|
// ---------------------------------------------------------------
|
|
|
|
module RecursiveFunctions =
|
|
|
|
/// Compute the factorial of an integer. Use 'let rec' to define a recursive function
|
|
let rec factorial n =
|
|
if n = 0 then 1 else n * factorial (n-1)
|
|
|
|
/// Computes the greatest common factor of two integers.
|
|
// Since all of the recursive calls are tail calls, the compiler will turn the function into a loop,
|
|
// which improves performance and reduces memory consumption.
|
|
|
|
let rec greatestCommonFactor a b =
|
|
if a = 0 then b
|
|
elif a < b then greatestCommonFactor a (b - a)
|
|
else greatestCommonFactor (a - b) b
|
|
|
|
/// Computes the sum of a list of integers using recursion.
|
|
let rec sumList xs =
|
|
match xs with
|
|
| [] -> 0
|
|
| y::ys -> y + sumList ys
|
|
|
|
/// Make the function tail recursive, using a helper function with a result accumulator
|
|
let rec private sumListTailRecHelper accumulator xs =
|
|
match xs with
|
|
| [] -> accumulator
|
|
| y::ys -> sumListTailRecHelper (accumulator+y) ys
|
|
|
|
let sumListTailRecursive xs = sumListTailRecHelper 0 xs
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Record types
|
|
// ---------------------------------------------------------------
|
|
|
|
module RecordTypes =
|
|
// define a record type
|
|
type ContactCard =
|
|
{ Name : string
|
|
Phone : string
|
|
Verified : bool }
|
|
|
|
let contact1 = { Name = "Alf" ; Phone = "(206) 555-0157" ; Verified = false }
|
|
|
|
// Create a new record that is a copy of contact1,
|
|
// but has different values for the 'Phone' and 'Verified' fields
|
|
|
|
let contact2 = { contact1 with Phone = "(206) 555-0112"; Verified = true }
|
|
|
|
/// Converts a 'ContactCard' object to a string
|
|
let showCard c =
|
|
c.Name + " Phone: " + c.Phone + (if not c.Verified then " (unverified)" else "")
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Union types
|
|
// ---------------------------------------------------------------
|
|
|
|
module UnionTypes =
|
|
|
|
/// Represents the suit of a playing card
|
|
type Suit =
|
|
| Hearts
|
|
| Clubs
|
|
| Diamonds
|
|
| Spades
|
|
|
|
/// Represents the rank of a playing card
|
|
type Rank =
|
|
/// Represents the rank of cards 2 .. 10
|
|
| Value of int
|
|
| Ace
|
|
| King
|
|
| Queen
|
|
| Jack
|
|
|
|
static member GetAllRanks() =
|
|
[ yield Ace
|
|
for i in 2 .. 10 do yield Value i
|
|
yield Jack
|
|
yield Queen
|
|
yield King ]
|
|
|
|
type Card = { Suit: Suit; Rank: Rank }
|
|
|
|
/// Returns a list representing all the cards in the deck
|
|
|
|
let fullDeck =
|
|
[ for suit in [ Hearts; Diamonds; Clubs; Spades] do
|
|
for rank in Rank.GetAllRanks() do
|
|
yield { Suit=suit; Rank=rank } ]
|
|
|
|
/// Converts a 'Card' object to a string
|
|
let showCard c =
|
|
let rankString =
|
|
match c.Rank with
|
|
| Ace -> "Ace"
|
|
| King -> "King"
|
|
| Queen -> "Queen"
|
|
| Jack -> "Jack"
|
|
| Value n -> string n
|
|
|
|
let suitString =
|
|
match c.Suit with
|
|
| Clubs -> "clubs"
|
|
| Diamonds -> "diamonds"
|
|
| Spades -> "spades"
|
|
| Hearts -> "hearts"
|
|
|
|
rankString + " of " + suitString
|
|
|
|
let printAllCards() =
|
|
for card in fullDeck do
|
|
printfn "%s" (showCard card)
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Option types
|
|
// ---------------------------------------------------------------
|
|
|
|
module OptionTypes =
|
|
/// Option values are any kind of value tagged with either 'Some' or 'None'.
|
|
/// They are used extensively in F# code to represent the cases where many other
|
|
/// languages would use null references.
|
|
|
|
type Customer = { zipCode : decimal option }
|
|
|
|
/// Abstract class that computes the shipping zone for the customer's zip code,
|
|
/// given implementations for the 'GetState' and 'GetShippingZone' abstract methods.
|
|
|
|
[<AbstractClass>]
|
|
|
|
type ShippingCalculator =
|
|
|
|
abstract GetState : decimal -> string option
|
|
|
|
abstract GetShippingZone : string -> int
|
|
|
|
/// Return the shipping zone corresponding to the customer's ZIP code
|
|
/// Customer may not yet have a ZIP code or the ZIP code may be invalid
|
|
member this.CustomerShippingZone(customer : Customer) =
|
|
|
|
customer.zipCode
|
|
|> Option.bind this.GetState
|
|
|> Option.map this.GetShippingZone
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Pattern matching
|
|
// ---------------------------------------------------------------
|
|
|
|
module PatternMatching =
|
|
|
|
/// A record for a person's first and last name
|
|
|
|
type Person =
|
|
{ First : string
|
|
Last : string }
|
|
|
|
/// Define a discriminated union of 3 different kinds of employees
|
|
|
|
type Employee =
|
|
/// Engineer is just herself
|
|
| Engineer of Person
|
|
/// Manager has list of reports
|
|
| Manager of Person * list<Employee>
|
|
/// Executive also has an assistant
|
|
| Executive of Person * list<Employee> * Employee
|
|
|
|
/// Count everyone underneath the employee in the management hierarchy, including the employee
|
|
let rec countReports(emp : Employee) =
|
|
1 + match emp with
|
|
| Engineer(id) ->
|
|
0
|
|
| Manager(id, reports) ->
|
|
reports |> List.sumBy countReports
|
|
| Executive(id, reports, assistant) ->
|
|
(reports |> List.sumBy countReports) + countReports assistant
|
|
|
|
/// Find all managers/executives named "Dave" who do not have any reports
|
|
let rec findDaveWithOpenPosition(emps : Employee list) =
|
|
emps
|
|
|> List.filter(function
|
|
| Manager({First = "Dave"}, []) -> true // [] matches the empty list
|
|
| Executive({First = "Dave"}, [], _) -> true
|
|
| _ -> false) // '_' is a wildcard pattern that matches anything
|
|
// this handles the "or else" case
|
|
|
|
// ---------------------------------------------------------------
|
|
// Units of measure
|
|
// ---------------------------------------------------------------
|
|
|
|
module UnitsOfMeasure =
|
|
// Code can be annotated with units of measure when using F# arithmetic over numeric types
|
|
|
|
open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames
|
|
|
|
[<Measure>]
|
|
type mile =
|
|
|
|
/// Conversion factor mile to meter: meter is defined in SI.UnitNames
|
|
|
|
static member asMeter = 1600.<meter/mile>
|
|
|
|
|
|
let d = 50.<mile> // Distance expressed using imperial units
|
|
let d' = d * mile.asMeter // Same distance expressed using metric system
|
|
|
|
printfn "%A = %A" d d'
|
|
// let error = d + d' // Compile error: units of measure do not match
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Parallel array programming
|
|
// ---------------------------------------------------------------
|
|
|
|
module ParallelArrayProgramming =
|
|
|
|
let oneBigArray = [| 0 .. 100000 |]
|
|
|
|
// do some CPU intensive computation
|
|
let rec computeSomeFunction x =
|
|
if x <= 2 then 1
|
|
else computeSomeFunction (x - 1) + computeSomeFunction (x - 2)
|
|
|
|
// Do a parallel map over a large input array
|
|
let computeResults() = oneBigArray |> Array.Parallel.map (fun x -> computeSomeFunction (x % 20))
|
|
|
|
printfn "Parallel computation results: %A" (computeResults())
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// Using events
|
|
// ---------------------------------------------------------------
|
|
|
|
module Events =
|
|
|
|
open System
|
|
|
|
// Create instance of Event object that consists of subscription point (event.Publish) and event trigger (event.Trigger)
|
|
let simpleEvent = new Event<int>()
|
|
|
|
// Add handler
|
|
simpleEvent.Publish.Add(fun x -> printfn "this is handler was added with Publish.Add: %d" x)
|
|
|
|
// Trigger event
|
|
simpleEvent.Trigger(5)
|
|
|
|
// Create instance of Event that follows standard .NET convention: (sender, EventArgs)
|
|
let eventForDelegateType = new Event<EventHandler, EventArgs>()
|
|
|
|
|
|
// Add handler
|
|
eventForDelegateType.Publish.AddHandler(
|
|
EventHandler(fun _ _ -> printfn "this is handler was added with Publish.AddHandler"))
|
|
|
|
|
|
// Trigger event (note that sender argument should be set)
|
|
eventForDelegateType.Trigger(null, EventArgs.Empty)
|
|
|
|
// ---------------------------------------------------------------
|
|
// Database access using type providers
|
|
// ---------------------------------------------------------------
|
|
|
|
module DatabaseAccess =
|
|
|
|
// The easiest way to access a SQL database from F# is to use F# type providers.
|
|
// Add references to System.Data, System.Data.Linq, and FSharp.Data.TypeProviders.dll.
|
|
// You can use Server Explorer to build your ConnectionString.
|
|
|
|
(*
|
|
|
|
#r "System.Data"
|
|
#r "System.Data.Linq"
|
|
#r "FSharp.Data.TypeProviders"
|
|
|
|
open Microsoft.FSharp.Data.TypeProviders
|
|
|
|
type SqlConnection = SqlDataConnection<ConnectionString = @"Data Source=.\sqlexpress;Initial Catalog=tempdb;Integrated Security=True">
|
|
let db = SqlConnection.GetDataContext()
|
|
|
|
let table =
|
|
query { for r in db.Table do
|
|
select r }
|
|
|
|
*)
|
|
|
|
// You can also use SqlEntityConnection instead of SqlDataConnection, which accesses the database using Entity Framework.
|
|
|
|
()
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
// OData access using type providers
|
|
// ---------------------------------------------------------------
|
|
|
|
module OData =
|
|
|
|
(*
|
|
|
|
open System.Data.Services.Client
|
|
open Microsoft.FSharp.Data.TypeProviders
|
|
|
|
// Consume demographics population and income OData service from Azure Marketplace.
|
|
// For more information, see http://go.microsoft.com/fwlink/?LinkId=239712
|
|
|
|
type Demographics = Microsoft.FSharp.Data.TypeProviders.ODataService<ServiceUri = "https://api.datamarket.azure.com/Esri/KeyUSDemographicsTrial/">
|
|
let ctx = Demographics.GetDataContext()
|
|
|
|
// Sign up for a Azure Marketplace account at https://datamarket.azure.com/account/info
|
|
ctx.Credentials <- System.Net.NetworkCredential ("<your liveID>", "<your Azure Marketplace Key>")
|
|
|
|
let cities =
|
|
query { for c in ctx.demog1 do
|
|
where (c.StateName = "Washington") }
|
|
|
|
for c in cities do
|
|
printfn "%A - %A" c.GeographyId c.PerCapitaIncome2010.Value
|
|
|
|
*)
|
|
|
|
()
|
|
|
|
|
|
#if COMPILED
|
|
|
|
module BoilerPlateForForm =
|
|
[<System.STAThread>]
|
|
do ()
|
|
// do System.Windows.Forms.Application.Run()
|
|
|
|
#endif
|