ASP.NET with F# and Mono

ASP.NET is the implementation of server-side code based on .NET to create dynamic web pages. Mono does support ASP.NET with some additional configuration F# can also be used to create dynamic web content.

As we need multiple elements to work correctly to program ASP.NET in F’ we will go step-by-step to ensure that each element works on your installation as well.

First lets test your mono installation with a simple C# ASP.NET program. Do the following steps

1. Create a directory ‘csharp-hello’ in your favorite place and change into it

~> md csharp-hello
~> cd csharp-hello
~/csharp-hello>

2. Create a file index.aspx

<%@ Page Language="C#" AutoEventWireup="true" Debug=true  %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<% Response.Write("Hello World says C#");  %>
</body>
</html>

3. Start the web server xsp2 integrated together with mono to test

~/csharp-hello> xsp2
xsp2
Listening on address: 0.0.0.0
Root directory: ~/Projekte/Fsharp/asp.net/csharp-hello
Listening on port: 8080 (non-secure)
Hit Return to stop the server.

4. Start your web browser and point it to ‘http://localhost:8080&#8217;.

Now lets do the same in F#, we do need some additional configuration to write ASP.NET applications in F’.

1. Create a directory ‘hello’ in your favorite place, change into it and create a directory ‘bin’

~> md hello
~> cd hello
~/hello>

2. We need a web.config file to tell mono how to compile F# code. Create the file ‘web.config’ in the directory ‘hello’.

<?xml version="1.0"?>
<configuration>
 <system.web>
 <compilation debug="true">
 <assemblies>
 <add
 assembly="FSharp.Compiler.CodeDom, Version=2.0.0.0,
Culture=neutral,PublicKeyToken=a19089b1c74d0809"/>
 </assemblies>
 <compilers>
 <compiler
 language="F#;f#;fs;fsharp" extension=".fs"
 type="Microsoft.FSharp.Compiler.CodeDom.FSharpAspNetCodeProvider,
FSharp.Compiler.CodeDom, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=a19089b1c74d0809"/>
 </compilers>
 </compilation>
 </system.web>
</configuration>

3. Additionally, we need a directory bin which contains the F# compiler. A symbolic link is also sufficient.

~/hello> md bin
~/hello> cd bin
~/hello/bin> ln -s /usr/local/lib/FSharp-2.0.0.0/bin/FSharp.Compiler.CodeDom.dll .
~/hello/bin> cd ..
~/hello>

4. Now you may start the web server

~/hello> xsp2
xsp2
Listening on address: 0.0.0.0
Root directory: ~/Projekte/Fsharp/asp.net/hello
Listening on port: 8080 (non-secure)
Hit Return to stop the server.

5. Open your web browser and point it to ‘http://localhost:8080&#8217;

Further reading:

Advertisements
Posted in Getting Started, Web | 2 Comments

Mono 2.8 has been released

Mono 2.8 has been released (Release Notes). The main improvement is the offical support of C# 4.0 and .NET profile 4.0 plus a lot of additional libraries such as MVC.

Installation packages for Mono 2.8 can be found here.

Posted in General, Installation | Leave a comment

Embedded WebBrowser

Parsing and displaying HTML and Web content is an overwhelmingly complex issue. If you want to just display rich text inside your application (e.g. documentation) you usually need to rely on an external parser library. Luckily, Winforms provides an embedded web browser through System.Windows.Forms.WebBrowser. The Mono implementation relies on the Mozilla Gecko engine.

> open System.Windows.Forms;;
> let f = new Form();;

val f : Form = System.Windows.Forms.Form, Text: 

> f.Visible <- true;;
val it : unit = ()
> let b = new WebBrowser();;

val b : WebBrowser = System.Windows.Forms.WebBrowser

> f.Controls.Add(b);;
val it : unit = ()
> b.Dock <- DockStyle.Fill;;
val it : unit = ()
> f.Width <- 1024;;
val it : unit = ()
> f.Height <- 768;;
val it : unit = ()
> b.Url <- System.Uri("http://www.2sharp4u.wordpress.com");;
val it : unit = ()

This should result in the following screen in your application.

Next we create a full scale web browser for our own purpose.

open System
open System.Drawing
open System.Windows.Forms

let form = new Form(Visible=true, Text="Web Browser")
let container = new TableLayoutPanel(ColumnCount=2, RowCount=3)
let label = new Label(Text="Address:")
let address = new TextBox()
let toolbar = new ToolStrip()
let content = new WebBrowser()
let back = new ToolStripButton("BACK")
let forward = new ToolStripButton("FORWARD")

label.TextAlign <- ContentAlignment.MiddleRight

form.Width <- 1024
form.Height <- 768

container.Dock <- DockStyle.Fill
address.Dock <- DockStyle.Fill
content.Dock <- DockStyle.Fill

toolbar.Items.Add(back) |> ignore
toolbar.Items.Add(forward) |> ignore

form.Controls.Add(container)
container.Controls.Add(label, 0, 0)
container.Controls.Add(address, 1, 0)
container.Controls.Add(toolbar, 0, 1)
container.Controls.Add(content, 0, 2)

container.SetColumnSpan(toolbar, 2)
container.SetColumnSpan(content, 2)

content.Refresh()

back.Click.Add(fun _ -> content.GoBack() |> ignore)
forward.Click.Add(fun _ -> content.GoForward() |> ignore)

address.KeyDown.Add(fun e -> if e.KeyCode = Keys.Enter then
 try content.Url <- System.Uri(address.Text)
 with _ -> ())

form.Show()
#if COMPILED
[<STAThread()>]
Application.Run(form)
#endif

After compiling and launching the application you should get the following screen:

Posted in GUI, Web | 1 Comment

Interactive Session #11: Units of Measure

F# includes a language feature to annotate your code with units of measure like meter, second or kilogram, preferably from the SI system. These units are statically checked during compile time and will not be part of the compiled code.

> [<Measure>] type m;;

[<Measure>]
type m

> [<Measure>] type s;;

[<Measure>]
type s

> let v (s:float<m>) (t:float<s>) = s/t;;

val v : float<m> -> float<s> -> float<m/s>

> v 100.0<m> 4.5<s>;;
val it : float<m/s> = 22.22222222

You may recognize that the type of the speed function ‘v’ includes a type float<m/s> automatically derived from the basic types and units of measure float<m> and float<s>.

The F# PowerPack includes the SI units of measure (to install F# PowerPack on Linux / Mono look here).

> #r "FSharp.PowerPack.dll";;

--> Referenced '/usr/local/lib/FSharp-2.0.0.0/bin/FSharp.PowerPack.dll'

> open SI;;
> 1.0<kg>;;
val it : float<kg> = 1.0

The complete list of SI units available in module SI can be found here.

> let w = 1.0<kg>;;

val w : float<kg> = 1.0

> let g = 9.81<m/s^2>;;

val g : float<m/s ^ 2> = 9.81

> let f = w * g;;

val f : float<kg m/s ^ 2> = 9.81

Further reading:

Posted in Algorithms, Getting Started, Language | 1 Comment

Installing the F# PowerPack on Linux / Mono

The F# PowerPack is an open source collection of libraries provided by the Microsoft F# team on top of the standard distribution. It can be found here. It is recommended to always install the F# PowerPack when using F#.

1. Download the F# PowerPack ZIP-file from CodePlex

2. Go to your F# installation location (and switch to root if necessary)

~> cd /usr/local/lib/FSharp-2.0.0.0/bin/
host:/usr/local/lib/FSharp-2.0.0.0/bin> su
Password:
host:/usr/local/lib/FSharp-2.0.0.0/bin #

3. Unzip the file

host:/usr/local/lib/FSharp-2.0.0.0/bin # unzip ~/FSharpPowerPack.zip
Archive:  ~/FSharpPowerPack.zip
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.Compiler.CodeDom.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\gac\FSharp.Compiler.CodeDom.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.Compiler.CodeDom.pdb   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.Compiler.CodeDom.xml   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Build.Tasks.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Build.Tasks.pdb   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Build.Tasks.xml   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.pdb   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.xml   
 inflating: FSharpPowerPack-2.0.0.0\bin\gac\FSharp.PowerPack.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Compatibility.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Compatibility.pdb   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Compatibility.xml   
 inflating: FSharpPowerPack-2.0.0.0\bin\gac\FSharp.PowerPack.Compatibility.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Linq.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\gac\FSharp.PowerPack.Linq.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Linq.pdb   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Linq.xml   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Metadata.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\gac\FSharp.PowerPack.Metadata.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Metadata.pdb   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Metadata.xml   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Parallel.Seq.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\gac\FSharp.PowerPack.Parallel.Seq.dll   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Parallel.Seq.pdb   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.Parallel.Seq.xml   
 inflating: FSharpPowerPack-2.0.0.0\bin\fshtmldoc.exe   
 inflating: FSharpPowerPack-2.0.0.0\bin\fslex.exe   
 inflating: FSharpPowerPack-2.0.0.0\bin\fsyacc.exe   
 inflating: FSharpPowerPack-2.0.0.0\bin\FSharp.PowerPack.targets   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v3.0\FSharp.PowerPack.dll   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v3.0\FSharp.PowerPack.pdb   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v3.0\FSharp.PowerPack.Compatibility.dll   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v3.0\FSharp.PowerPack.Compatibility.pdb   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v4.0\FSharp.PowerPack.dll   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v4.0\FSharp.PowerPack.pdb   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v4.0\FSharp.PowerPack.Compatibility.dll   
 inflating: FSharpPowerPack-2.0.0.0\Silverlight\v4.0\FSharp.PowerPack.Compatibility.pdb   
 inflating: FSharpPowerPack-2.0.0.0\.\License.rtf

3. Unfortunately the file names in the ZIP file do not unzip corectly, they include the Windows path with backslashes as part of the file name. Therefore we need to correct this with the following script.

mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.Compiler.CodeDom.dll\  FSharp.Compiler.CodeDom.dll
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.Compiler.CodeDom.pdb\  FSharp.Compiler.CodeDom.pdb
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.Compiler.CodeDom.xml\  FSharp.Compiler.CodeDom.xml
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Build.Tasks.dll\  FSharp.PowerPack.Build.Tasks.dll
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Build.Tasks.pdb\  FSharp.PowerPack.Build.Tasks.pdb
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Build.Tasks.xml\  FSharp.PowerPack.Build.Tasks.xml
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Compatibility.dll\  FSharp.PowerPack.Compatibility.dll
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Compatibility.pdb\  FSharp.PowerPack.Compatibility.pdb
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Compatibility.xml\  FSharp.PowerPack.Compatibility.xml
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.dll\  FSharp.PowerPack.dll
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Linq.dll\  FSharp.PowerPack.Linq.dll
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Linq.pdb\  FSharp.PowerPack.Linq.pdb
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Linq.xml\  FSharp.PowerPack.Linq.xml
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Metadata.dll\  FSharp.PowerPack.Metadata.dll
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Metadata.pdb\  FSharp.PowerPack.Metadata.pdb
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Metadata.xml\  FSharp.PowerPack.Metadata.xml
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Parallel.Seq.dll\  FSharp.PowerPack.Parallel.Seq.dll
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Parallel.Seq.pdb\  FSharp.PowerPack.Parallel.Seq.pdb
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.Parallel.Seq.xml\  FSharp.PowerPack.Parallel.Seq.xml
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.pdb\  FSharp.PowerPack.pdb
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.targets\  FSharp.PowerPack.targets
mv FSharpPowerPack-2.0.0.0\\bin\\FSharp.PowerPack.xml\  FSharp.PowerPack.xml
mv FSharpPowerPack-2.0.0.0\\bin\\fshtmldoc.exe\  fshtmldoc.exe
mv FSharpPowerPack-2.0.0.0\\bin\\fslex.exe\  fslex.exe
mv FSharpPowerPack-2.0.0.0\\bin\\fsyacc.exe\  fsyacc.exe
mv FSharpPowerPack-2.0.0.0\\bin\\gac\\FSharp.Compiler.CodeDom.dll\  FSharp.Compiler.CodeDom.dll
mv FSharpPowerPack-2.0.0.0\\bin\\gac\\FSharp.PowerPack.Compatibility.dll\  FSharp.PowerPack.Compatibility.dll
mv FSharpPowerPack-2.0.0.0\\bin\\gac\\FSharp.PowerPack.dll\  FSharp.PowerPack.dll
mv FSharpPowerPack-2.0.0.0\\bin\\gac\\FSharp.PowerPack.Linq.dll\  FSharp.PowerPack.Linq.dll
mv FSharpPowerPack-2.0.0.0\\bin\\gac\\FSharp.PowerPack.Metadata.dll\  FSharp.PowerPack.Metadata.dll
mv FSharpPowerPack-2.0.0.0\\bin\\gac\\FSharp.PowerPack.Parallel.Seq.dll\  FSharp.PowerPack.Parallel.Seq.dll
mv FSharpPowerPack-2.0.0.0\\.\\License.rtf\  License.rtf
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v3.0\\FSharp.PowerPack.Compatibility.dll\  FSharp.PowerPack.Compatibility.dll
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v3.0\\FSharp.PowerPack.Compatibility.pdb\  FSharp.PowerPack.Compatibility.pdb
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v3.0\\FSharp.PowerPack.dll\  FSharp.PowerPack.dll
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v3.0\\FSharp.PowerPack.pdb\  FSharp.PowerPack.pdb
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v4.0\\FSharp.PowerPack.Compatibility.dll\  FSharp.PowerPack.Compatibility.dll
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v4.0\\FSharp.PowerPack.Compatibility.pdb\  FSharp.PowerPack.Compatibility.pdb
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v4.0\\FSharp.PowerPack.dll\  FSharp.PowerPack.dll
mv FSharpPowerPack-2.0.0.0\\Silverlight\\v4.0\\FSharp.PowerPack.pdb\  FSharp.PowerPack.pdb

5. Apply the script in the directory where you unziped the files

host:/usr/local/lib/FSharp-2.0.0.0/bin # bash dd

6. Change the file permissions

host:/usr/local/lib/FSharp-2.0.0.0/bin # chmod a+rx *

7. Test your installation as a normal user (not root)

~> fsi
Microsoft (R) F# 2.0 Interactive build 2.0.0.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;
> #r "FSharp.PowerPack.dll";;

--> Referenced '/usr/local/lib/FSharp-2.0.0.0/bin/FSharp.PowerPack.dll'
>
Posted in Installation | 4 Comments

Interactive Session #10: List, Array, Sequence

F# supports three different list-like data structures to store values of the same type: List, Array and Sequence. In this session we want to explore the similarities and differences among these three types. First lets instantiate values of each of these types.

> [0; 1; 2];;
val it : int list = [0; 1; 2]
> [0..10];;
val it : int list = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
> [|3; 4; 5|];;
val it : int [] = [|3; 4; 5|]
> [|0..10|];;
val it : int [] = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]
> seq { 6..8 };;   
val it : seq<int> = seq [6; 7; 8]

Now lets create large values to see how each type performs.

> [0..10000000];;
val it : int list =
 [0; 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; 42; 43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56; 57; 58;
 59; 60; 61; 62; 63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74; 75; 76; 77;
 78; 79; 80; 81; 82; 83; 84; 85; 86; 87; 88; 89; 90; 91; 92; 93; 94; 95; 96;
 97; 98; 99; ...]
> [|0..10000000|];;
val it : int [] =
 [|0; 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; 42; 43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56; 57; 58;
 59; 60; 61; 62; 63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74; 75; 76; 77;
 78; 79; 80; 81; 82; 83; 84; 85; 86; 87; 88; 89; 90; 91; 92; 93; 94; 95; 96;
 97; 98; 99; ...|]
> seq { 0..10000000 };;
val it : seq<int> = seq [0; 1; 2; 3; ...]

On my machine the creation of the list and array values needed a couple of seconds, however, the creation time of the sequence value was unnoticeable. This is due to the fact, that the sequence type does not compute its values immediately, but only on demand, this strategy is called lazy evaluation and is one of the primary computation strategies in functional programming languages.

Accessing and modifying individual elements is also different between lists, arrays and sequences. List elements may only be read, but not modified. Array elements can be read and modified. Sequences are not intended for reading and modifying individual elements (you could but it is rather tedious).

> let l = [0..10];;

val l : int list = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

> let a = [|1..10|];;

val a : int [] = [|1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]

> let s = seq { 0..10 };;

val s : seq<int>

> l.Item(5);;
val it : int = 5
> a.[0];;
val it : int = 5
> l.[0] <- 5;;

 l.[0] <- 5;;
 ^^^^^^^^^^

~/stdin(29,1): error FS0810: Property 'Item' cannot be set
> a.[0] <- 5;;
val it : unit = ()
> a;;
val it : int [] = [|5; 2; 3; 4; 5; 6; 7; 8; 9; 10|]

Iter, map, fold and filter functions are available for all three types.

> List.map (fun i -> i*i) l;;
val it : int list = [0; 1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
> Array.map (fun i -> i*i) a;;
val it : int [] = [|25; 4; 9; 16; 25; 36; 49; 64; 81; 100|]
> Seq.map (fun i -> i*i) s;;
val it : seq<int> = seq [0; 1; 4; 9; ...]

Out of the above simple exercises we can derive the following guidelines for the usage of list, array and sequence types:

  • use arrays when your algorithm needs in-place replacement
  • use sequences for processing and transforming large amounts of data (up to infinite sequences), also when each element is computed using a function
  • use lists for all other cases

Further reading:

Posted in Getting Started, Language | 1 Comment

Interactive Session #9: Processing XML

Reading and interpreting XML data is performed through the System.Xml package. You need to create a new XmlDocument value and load the XML text into the XmlDocument.

> open System.Xml;;
> let txt = "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx>
  <dy>50.0</dy></line><circle><x>50.0</x><y>50.0</y><r>20.0</r>
  </circle></drawing>";;

val txt : string =
 "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy>
  </line><circle><x>50.0</x><y>50.0</y><r>20.0</r></circle>
  </drawing>"

> let doc = new XmlDocument();;

val doc : XmlDocument

> doc.LoadXml(txt);;
val it : unit = ()

Now you may explore the XmlDocument structure with the Item method. There are two different ways of accessing the elements

  1. identifiying the elements by their name, e.g. doc.Item(“drawing”)
  2. identifiying the elements by their position in the document, e.g. doc.ChildNodes.Item(0)

In advance we need to register a printer function in the interactive for XmlNode values.

> fsi.AddPrinter(fun (x:XmlNode) -> x.OuterXml);;
val it : unit = ()
> doc.Item("drawing").Value;;
val it : string = null
> doc.Item("drawing").OuterXml;;
val it : string =
 "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
  <circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>"
> doc.Item("drawing");;
val it : XmlElement =
 <drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
<circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>
> doc.Item("drawing").Item("line");;
val it : XmlElement =
 <line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
> doc.Item("drawing").Item("line").Item("x");;
val it : XmlElement = 10.0
> doc.Item("drawing").Item("line").Item("x").ChildNodes.Item(0);;
val it : XmlNode = 10.0
> doc.ChildNodes.Item(0);;
val it : XmlNode =
 <drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
  <circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>

Now let’s parse this xml document into a data structure for drawing elements. First we need a data type for our data structure:

> type element =
-   | Drawing of element list
-   | Line of (float * float * float * float)
-   | Circle of (float * float * float);;

type element =
 | Drawing of element list
 | Line of (float * float * float * float)
 | Circle of (float * float * float)

Now we need a parser function which translates a XmlDocument into our drawing data structure. This parser function traverses recursively the XmlDocument structure and returns a structure of type element.

> exception UnknownDrawingElement of string;;

exception UnknownDrawingElement of string

> let rec parse_element (item:XmlNode) =
-   match item.Name with
-     | "drawing" -> Drawing [for i in item.ChildNodes do yield parse_element i]
-     | "line" -> Line (float(item.Item("x").ChildNodes.Item(0).Value),
-                       float(item.Item("y").ChildNodes.Item(0).Value),
-                       float(item.Item("dx").ChildNodes.Item(0).Value),
-                       float(item.Item("dy").ChildNodes.Item(0).Value))
-     | "circle" -> Circle (float(item.Item("x").ChildNodes.Item(0).Value),
-                           float(item.Item("y").ChildNodes.Item(0).Value),
-                           float(item.Item("r").ChildNodes.Item(0).Value))
-     | s -> raise (UnknownDrawingElement ("unkown element " + s));;

val parse_element : XmlNode -> element
> let drg = parse_element(doc.ChildNodes.Item(0));;

val drg : element =
 Drawing [Line (10.0, 20.0, 50.0, 50.0); Circle (50.0, 50.0, 20.0)]

draw_element takes an element data structure and paints it onto a Graphics canvas with a pen.

> open System.Drawing
- open System.Windows.Forms
-
- let rec draw_element (gfx:Graphics) (pen:Pen) (e:element) =
-   match e with
-     | Drawing d -> List.iter (fun i -> (draw_element gfx pen i)) d
-     | Line (x, y, dx, dy) -> gfx.DrawLine(pen, int(x), int(y),
-                                                int(x+dx), int(y+dy))
-     | Circle (x, y, r) -> gfx.DrawEllipse(pen, int(x), int(y),
-                                                int(r), int(r));;

val draw_element : Graphics -> Pen -> element -> unit

Now we need a form with a canvas to draw our elements onto.

> let form = new Form(Visible=true, Width=300, Height=300)
- let gfx = form.CreateGraphics()
- let pen = new Pen(Color.Black, 2.0f);;

val form : Form = System.Windows.Forms.Form, Text:
val gfx : Graphics
val pen : Pen

Finally lets draw the drawing onto the form.

> draw_element gfx pen drg;;
val it : unit = ()

Alternatively you may load the complete source as a script into the interactive

open System.Xml
let txt = "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line><circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>"

let doc = new XmlDocument()
doc.LoadXml(txt);;

type element =
 | Drawing of element list
 | Line of (float * float * float * float)
 | Circle of (float * float * float)

exception UnknownDrawingElement of string

let rec parse_element (item:XmlNode) =
 match item.Name with
 | "drawing" -> Drawing [for i in item.ChildNodes do yield parse_element i]
 | "line" -> Line (float(item.Item("x").ChildNodes.Item(0).Value),
 float(item.Item("y").ChildNodes.Item(0).Value),
 float(item.Item("dx").ChildNodes.Item(0).Value),
 float(item.Item("dy").ChildNodes.Item(0).Value))
 | "circle" -> Circle (float(item.Item("x").ChildNodes.Item(0).Value),
 float(item.Item("y").ChildNodes.Item(0).Value),
 float(item.Item("r").ChildNodes.Item(0).Value))
 | s -> raise (UnknownDrawingElement ("unkown element " + s))

open System.Drawing
open System.Windows.Forms

let rec draw_element (gfx:Graphics) (pen:Pen) (e:element) =
 match e with
 | Drawing d -> List.iter (fun i -> (draw_element gfx pen i)) d
 | Line (x, y, dx, dy) -> gfx.DrawLine(pen, int(x), int(y),
 int(x+dx), int(y+dy))
 | Circle (x, y, r) -> gfx.DrawEllipse(pen, int(x), int(y),
 int(r), int(r))

let form = new Form(Visible=true, Width=300, Height=300)
let gfx = form.CreateGraphics()
let pen = new Pen(Color.Black, 2.0f)

let drg = parse_element(doc.ChildNodes.Item(0))
draw_element gfx pen drg

Now load this script into the interactive and draw the result into the canvas.

> #load "xml1.fsx";;
[Loading ~/Projekte/Fsharp/xml1.fsx]
...
> open Xml1;;
> draw_element gfx pen drg;;
val it : unit = ()

Further reading:

Posted in Getting Started, GUI, IO, Text | 1 Comment

Interactive Session #8: Pipeline Operator

The pipeline operator |> is a quite unique feature of F#, it allows to connect functions which take one argument together by passing the result of one function to the next.

> let add1 x = x+1.0;;

val add1 : float -> float

> 1.0 |> add1;;
val it : float = 2.0
> 1.0 |> add1 |> add1;;
val it : float = 3.0

This may also be applied to a complete lists of values.

> let l = [1.0; 2.0; 3.0; 4.0];;

val l : float list = [1.0; 2.0; 3.0; 4.0]

> l |> (List.map add1);;
val it : float list = [2.0; 3.0; 4.0; 5.0]

The type of the operator may be explored as follows by putting the operator into parentheses.

> (|>);;
val it : ('a -> ('a -> 'b) -> 'b) = <fun:it@82-1>

The type signature has the meaning that the first operand (left side) is of generic type ‘a and the right side is a function of generic type ‘a -> ‘b. The resulting type is ‘b, which means that the function on the right side will be applied to the value on the left side. Therefore the pipeline operator is equivalent to the following generic function:

> let pipeline a b = b(a);;

val pipeline : 'a -> ('a -> 'b) -> 'b

Alternatively the pipeline operator may also be applied to an array or a sequence of values.

> let x_n = [|1..100|] |> Array.map (fun x -> float(x)/10.0);;
val x_n : float [] =
 [|0.1; 0.2; 0.3; 0.4; 0.5; 0.6; 0.7; 0.8; 0.9; 1.0; 1.1; 1.2; 1.3; 1.4; 1.5;
 1.6; 1.7; 1.8; 1.9; 2.0; 2.1; 2.2; 2.3; 2.4; 2.5; 2.6; 2.7; 2.8; 2.9; 3.0;
...

> let y_n = x_n |> Array.map (fun x -> sin(x));;

val y_n : float [] =
 [|0.09983341665; 0.1986693308; 0.2955202067; 0.3894183423; 0.4794255386;
 0.5646424734; 0.6442176872; 0.7173560909; 0.7833269096; 0.8414709848;

To draw a list of values we need an array of tuples to use the function DrawLines. Two arrays of values with same length may be converted into an array of tuples with Array.zip. Additionally, the points need to be translated to be in the middle of the form.

> open System.Drawing;;
> let points = Array.zip x_n y_n |> Array.map (fun (x, y) ->
-   PointF(float32(10.0*x), float32(100.0-10.0*y)));;

val points : PointF [] =
 [|{X=1, Y=99.00166}; {X=2, Y=98.01331}; {X=3, Y=97.0448}; {X=4, Y=96.10582};
 {X=5, Y=95.20574}; {X=6, Y=94.35358}; {X=7, Y=93.55782}; {X=8, Y=92.82644};
...

Now we may draw this list of points into a form.

> open System.Windows.Forms;;
> let f = new Form(Visible=true, Width=300, Height=300)
- let gfx = f.CreateGraphics()
- let pen = new Pen(Color.Black, 2.0f);;

val f : Form = System.Windows.Forms.Form, Text:
val gfx : Graphics
val pen : Pen
> gfx.DrawLines(pen, points);;
val it : unit = ()

Further reading:

Posted in Algorithms, General, Language | Leave a comment

Interactive Session #7: Text file I/O

Binary and text file access is provided through the module System.IO. The type File provides static methods to create, open, read and write files.

File.CreateText creates a new file (if it exists already content is overwritten) in text mode.

> open System.IO;;
> let f = File.CreateText("readme.txt");;          

val f : StreamWriter

> f.Write('R');;
val it : unit = ()
> f.Write('E');;
val it : unit = ()
> f.Write('A');;
val it : unit = ()
> f.Write("ME");;
> f.Close();;
val it : unit = ()

An existing text file may be read completely into a string with File.ReadAllText

> File.Exists("readme.txt");;
val it : bool = true
> let txt = File.ReadAllText("readme.txt");;

val txt : string = "README"

Alternatively you may open and read with File.Open, File.Read and File.ReadBlock

> let f = File.OpenText("readme.txt");;

val f : StreamReader

> char(f.Read());;
val it : char = 'R'
> let buffer = Array.create 100 '\x00';;

val buffer : char [] =
 [|'00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'|]

> f.ReadBlock(buffer, 0, 10);;
val it : int = 5
> buffer;;
val it : char [] =
 [|'E'; 'A'; 'M'; 'E'; '10'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00';
 '00'; '00'; '00'; '00'; '00'; '00'; '00'; '00'|]
> f.ReadBlock(buffer, 0, 10);;
val it : int = 0
> f.Close();;
val it : unit = ()
Posted in Getting Started, IO, Text | Leave a comment

Interactive Session #6: Winforms SplitContainer

The SplitContainer allows to add two forms separated by a bar to another form. First we need a form again.

> open System.Windows.Forms;;
> let f = new Form();;

val f : Form = System.Windows.Forms.Form, Text: 

> f.Visible <- true;;
val it : unit = ()

Then we are adding the SplitContainer as its only control element. The left and right element of the SplitContainer are added to SplitContainer.Panel1 and .Panel2 respectively.

> let s = new SplitContainer();;

val s : SplitContainer = System.Windows.Forms.SplitContainer

> f.Controls.Add(s);;
val it : unit = ()
> s.Dock <- DockStyle.Fill;;
val it : unit = ()
> let t = new TreeView();;

val t : TreeView = System.Windows.Forms.TreeView, Nodes.Count: 0
> let t = new TreeView();;

val t : TreeView = System.Windows.Forms.TreeView, Nodes.Count: 0

> s.Panel1.Controls.Add(t);;
val it : unit = ()

> let txt = new TextBox();;

val txt : TextBox = System.Windows.Forms.TextBox, Text: 

> s.Panel2.Controls.Add(txt);;
val it : unit = ()
> let f = new Form();;        

val f : Form = System.Windows.Forms.Form, Text:

As you can see the TreeView and the TextBox is visible side by side, however they do not fill the entire space, therefore we need to set the Dock property of each element to fill the space given by the SplitContainer (which itself has to fill the complete form).

> t.Dock <- DockStyle.Fill;;                                                         
val it : unit = ()
> txt.Dock <- DockStyle.Fill;;                                                      
val it : unit = ()
> txt.Multiline <- true;;
val it : unit = ()

Further reading:

Posted in Getting Started, GUI | Leave a comment