|
|
-
(This article is cross-posted from http://tomasp.net/articles/aspnet-in-fsharp.aspx)
As I mentioned earlier, I spent three months as an intern in Microsoft Research in Cambridge last year and I was working with Don Syme and James Margetson from the F# team. Most of the time I was working on the F# Web Toolkit, which I introduced on the blog some time ago [1], but I also worked on a few additions that are now part of the F# release. Probably the most useful addition is a new implementation of the CodeDOM provider for the F# language which makes it possible to use ASP.NET smoothly from F# (but it can be used in some other scenarios as well) together with two ASP.NET sample applications that you can explore and use as a basis for your web sites. This was actually a part of the distribution for a few months now (I of course wanted to write this article much earlier...), so you may have already noticed, but anyway, I'd still like to write down a short description of these ASP.NET samples and also a few tips for those who're interested in writing web applications in F#.
F# and ASP.NET
Let's start by looking at the ASP.NET examples. You can find them in the samples directory in your F# installation under the Web/ASP.NET path. The directory also contains html files with description of the projects and a guide to configuring them, but I'll describe both of these topics in this post. The distribution contains two sample projects:
- AspNetIntro - this project is (almost) the simples possible F# web site, so it can be used as a template for your web sites. It shows how to configure the CodeDOM provider, how to write a simple page with code-behind and how to use the
App_Code directory and data-binding.
- PersonalWebSite - this is a more complex web site ported from the C# sample called Personal Web Site Starter Kit [2]. It demonstrates many of the standard ASP.NET 2.0 techniques including data access controls, master pages, membership and custom HTTP handlers.
ASP.NET Introduction using F#
To start playing with ASP.NET you'll need to open the project (I recommend copying it to your working directory first). If you're using Visual Studio, you can select File - Open - Web Site... in the menu and select the directory with your project as demonstrated at Figure 1 below. The organization of ASP.NET projects is different than organization of ordinary F# projects - in ASP.NET the project is just a directory and it contains all the files in the directory (this is also the reason why you have to open it using a different command). The Figure 2 shows how the files of the ASP.NET Introduction project are organized in the Solution Explorer:
 Figure 1: Open Web Site
 Figure 2: Solution Explorer
As you can see, there are 6 files in the project. The Default.aspx.fs and Default.aspx together form one web page and the DataBinding.aspx.fs with DataBinding.aspx form the second web page. The App_Code directory contains application logic that can be used from other pages in the project and in our sample project it contains only one file (logic.fs). Finally, the web.config file contains configuration of the whole application.
Before we look at the pages you may want to check the web.config file, because it needs to contain the correct reference to the CodeDOM provider implementation including a version of the current F# installation. At the time of writing this article, the latest version is 1.9.3.14, but if you're not sure what version you are using, you can just start the fsi.exe from the F# installation which prints the version number. The web.config file is an xml file and it should contain the following content (with the right version number). The samples in the distribution should contain the correct version number, but the incorrect configuration is a common issue when working with ASP.NET in F#, so it is useful to know what the configuration should look like: <?xml version="1.0"?>
<configuration><system.web>
<compilation debug="true">
<compilers>
<compiler
language="F#;f#;fs;fsharp" extension=".fs"
type="Microsoft.FSharp.Compiler.CodeDom.FSharpAspNetCodeProvider,
FSharp.Compiler.CodeDom, Version=<strong style="color:red;">1.9.3.14</strong>,
Culture=neutral, PublicKeyToken=a19089b1c74d0809"/>
</compilers>
</compilation></system.web>
</configuration>
Now, let's look at the Default.aspx and Default.aspx.fs files that together represent a simple page. The page contains one button and one label (a control that can display some text) and when the user clicks on the button, the result of some calculation is displayed in the label (the calculation is executed on the server-side). The following code is a (slightly simplified) content of the Default.aspx file, which defines the HTML markup together with the ASP.NET controls that are on the page: <%@ Page Language="F#"
CodeFile="Default.aspx.fs" Inherits="FSharpWeb.Default" %>
<html>
<body><form runat="server">
<asp:Button
ID="btnTest" RunAt="server" Text="Click me!"
OnClick="ButtonClicked" /><br />
<asp:Label ID="lblResult" RunAt="server" />
</form></body>
</html>
You can easily identify two server-side controls, because these are written using prefix asp and also contain the RunAt="server" attribute, which means that the control is processed on the server-side. You can use standard HTML notation for setting attributes of the controls. We set the ID attribute to both of the controls, which is important because it allows us to use them in the code-behind code. The OnClick attribute of the button sets an event handler - a member of the FSharpWeb.Default type from the code-behind file that will be called on the server-side, when a user clicks on the button (which submits the HTML form and causes a page reload, so the event can be processed on the server). The file also contains the first special line, which tells the ASP.NET engine that the page is written in F# and it also tells what source file contains the code-behind code and what is the name of type declared in that file. The code-behind file (Default.aspx.fs) has the following content: #light
namespace FSharpWeb
open System
open System.Web
open System.Web.UI.WebControls
type Default() =
inherit Page()
[<DefaultValue>]
val mutable btnTest : Button
[<DefaultValue>]
val mutable lblResult : Label
member this.ButtonClicked(sender, e) =
this.lblResult.Text <-
(sprintf "Factorial of 5 is: %d"
(FSharpWeb.Logic.factorial 5))
As we can see, the file contains an F# object type (named Default), inherited from the base ASP.NET Page type. Thanks to the recent improvements in F# it is possible to write the type using the new implicit class syntax (note the parentheses after the type name), which means that you can place additional initialization code to the type declaration directly (following the inherit clause and F# will treat this as a constructor.
The class contains a mutable field for every control declared in the declarative markup with the name same as the ID attribute in the markup file (btnTest and lblResult). The ASP.NET initializes these controls automatically when it creates the page (that's why the fields are marked using mutable), so we don't need to initialize these fields in our code - F# doesn't usually allow uninitialized fields, but if you place the DefaultValue before the field declaration, it will be initialized to the default value (null), which is correct, because the field will be later initialized by the ASP.NET runtime. Finally, the type contains a member called ButtonClicked, which is a same name we used in the markup for the OnClick attribute of the button control. This is an event handler that will be called when user clicks on the button, it performs some calculation (the factorial function is declared in the logic.fs file) and sets a property of the other control that we have on the page.
Personal Web Site in F#
 Figure 3: Personal Web Site
The second sample ASP.NET project is a port of quite a complex ASP.NET web site which also uses MS SQL database. The database can be created manually as described in Welcome.html in the PersonalWebSite directory, but I also uploaded the database to my web, so you can just download it and attach it in the SQL server (this is described below and the files for download can be found at the end of the article). The PWS_AspNetDb is a database managed by ASP.NET that contains user information - the database below already contains the required role (called Administrators) and one user (admin with password admin123!). The second database file (PWS_WebPersonal) contains the application data, mainly information about photos and galleries (as you can see from the screenshot in Figure 3).
The application uses many advanced ASP.NET features including the following:
- Master Pages - the overall structure of the web site is stored in a master page (
Default.master) and the other pages use the asp:Content control to fill the place holders in the master page.
- Site Maps - the map of the web site is defined in the
web.sitemap file and is used for generating the menu in the Default.master file.
- Themes - the design of the page including CSS files and images are managed using ASP.NET Themes - there are two standard themes in the
App_Themes directory.
- Membership - the web uses standard ASP.NET technology for managing user accounts including the controls for manipulating with users (e.g.
asp:CreateUserWizard control is used in Register.aspx file).
- Data Controls - ASP.NET data-access controls are used for reading the data from the application logic layer (implemented as a module in F#)
Configuring the databases
To configure the databases required by the demo, you can either follow the steps described in the Welcome.html file (which is part of the sample project), or if you already have some version of SQL Server installed on your machine, you can download the sample databases below (a bunch of MDF and LDF files) and attach them to the SQL Server. I'll explain how to do this using full version of SQL Server, but the steps needed with Express edition are very similar.
Once you downloaded the files, you'll need to launch SQL Server Management Studio and connect to the server instance. After doing that you should see an "Object Explorer" window, where you can right click on the "Databases" group and select "Attach..." from the pop-up menu. In the opened dialog window, you can add the databases (by selecting the MDF file) and click the "OK" button to attach the databases. Finally, you'll need to modify the web.config file in the Personal WebSite sample to include the following section: <connectionStrings>
<add name="Personal" providerName="System.Data.SqlClient" connectionString=
"Data Source=.;Integrated Security=True;Database=PWS_WebPersonal" />
<remove name="LocalSqlServer"/>
<add name="LocalSqlServer" connectionString=
"Data Source=.;Integrated Security=True;Database=PWS_AspNetDb" />
</connectionStrings>
The Data Source parameter in the connection string specifies the instance of the SQL Server, where the databases are attached. By default this is the name of your computer (which can be written shortly using dot "."). When using the SQL Express the default instance name is ".\SQLExpress". The Database parameter specifies the name of the database, which you can see in the "Object Explorer" in the SQL Server Management Studio. After following these steps, you should be able to run the demo application.
Data-access in F#
Most of the ASP.NET technologies used in the web site are used in almost the same way as in C#, so I will not discuss them in larger details, but the last item in the list - the use of ASP.NET data controls together with F# is I believe quite interesting, so I'll write a few notes about it. Let's look for example at the Photos.aspx file, which displays a list of photos in a gallery. The most important parts of the file related to data access are shown in the following code snippet: <asp:DataList RunAt="server" DataSourceID="photoSource" EnableViewState="false">
<ItemTemplate>
<!-- ... -->
<a href='Details.aspx?AlbumID=<%# base.Eval("PhotoAlbumID") %>'>
<img src="Handler.ashx?PhotoID=<%# base.Eval("PhotoID") %>&Size=S"
class="photo_198" style="border:4px solid white"
alt='Thumbnail of Photo Number <%# base.Eval("PhotoID") %>' />
</a>
<!-- ... -->
</ItemTemplate>
</asp:DataList>
<asp:ObjectDataSource ID="photoSource" RunAt="server"
TypeName="PersonalWebSite.PhotoManager"
SelectMethod="GetPhotos">
<SelectParameters>
<asp:QueryStringParameter Name="AlbumID" Type="Int32"
QueryStringField="albumID" DefaultValue="0"/>
</SelectParameters>
</asp:ObjectDataSource>
The first ASP.NET control used in the code is asp:DataList, which is a control that simply displays a collection of some items and uses a specified ItemTemplate for displaying every single item. In our case we're working with collection of records and the record has several members (including PhotoID and PhotoAlbumID). To display a value of a record member in the ASP.NET page we have to use ASP.NET Eval method as you can see in the sample. The Eval method reads the specified member of the item in collection and writing the code in <%# ... %> tells ASP.NET that we want to render the result of the expression as a part of the item template. The record that we're using in this example is very simple and has the following structure: type Photo =
{ PhotoID:int;
PhotoAlbumID:int;
PhotoCaption:string; }
This is actually one of the places where the F# version is shorter, because in C# version you have to write this as a class with properties, which makes the code quite longer.
The second server-side control (asp:ObjectDataSource) in the first code snippet isn't visual, which means that it will not produce any HTML output. It serves just as a declarative data-source for the data list and the properties of this control specify how the content for the data list should be loaded. There are various other data-source controls in ASP.NET (e.g. asp:SqlDataSource which loads data directly from the database), but the one that we're using in the example uses a specified .NET/F# type and calls a method of the given object when it needs to retrieve the data. In F# this is even easier, because we can use a name of the module and the code that retrieves the data can be a function in the module.
As you can see the property TypeName is set to PersonalWebSite.PhotoManager, which can be treated as a module called PhotoManager in a namespace PersonalWebSite and the SelectMethod property, which specifies a name of the function that will be called when loading the data is set to GetPhotos. Finally, the SelectParameters specifies that the function expects one argument called AlbumID and that the value of this argument should be retrieved from the query string (that is from the URL of form photo.aspx?albumIdD=42).
Now it is pretty easy to implement the module that matches this specifications and can be used as a data source (we could of course use FLinq for loading the data, but I wanted to keep things simple for now, so we're using standard SqlDataReader object from .NET): namespace PersonalWebSite
// ...
module PhotoManager =
// Function will be called by ASP.NET when loading data
// for the asp:DataList control in the 'Photos.aspx' page
let GetPhotos (albumID:int) =
// Open connection & create command
use conn = new SqlConnection(" ... ")
use cmd = new SqlCommand("GetPhotos", conn)
// Can the user see 'private' photos?
let usr = HttpContext.Current.User
let filter = not (usr.IsInRole("Friends") || usr.IsInRole("Administrators"))
cmd.CommandType <- CommandType.StoredProcedure)
cmd.Parameters.Add(SqlParameter("@AlbumID", albumID)) |> ignore
command.Parameters.Add(SqlParameter("@IsPublic", filter)) |> ignore
conn.Open()
// Read all photos into a .NET ResizeArray type
let list = new ResizeArray<_>()
use reader = command.ExecuteReader()
while (reader.Read()) do
list.Add({ PhotoID = unbox (reader.get_Item("PhotoID"))
PhotoAlbumID = unbox (reader.get_Item("AlbumID"))
PhotoCaption = unbox (reader.get_Item("Caption")) })
// Return the created collection
list
I hope this article explained some of the interesting things that you may find in the ASP.NET samples provided with the F# distribution and I think that the Personal WebSite Sample shows that F# can be used for writing quite complicated web applications as well. Of course, my initial motivation for writing web applications in F# is the possibility of using meta-programming to develop client-side code (the code that runs on the client as a JavaScript) in F# too, which makes development of modern "Ajax"-style applications easier, so if you're interested in this project you may want to look at F# Web Tools [1].
Downloads & References
|
-
In my bachelor thesis I included a short introduction that covered all of the important aspects of the F# programming language and I thought that it may be useful to extend it a little bit to cover also a topics that were not important for my thesis and post it as an article, so there is one and relatively short article that introduces all the interesting F# features. The article got however a bit longer than I expected, so I decided to split it into a three parts that would introduce three different paradigms that are supported by F#. Of course, this series won’t teach you everything about F#, but it tries to cover the main F# design goals and (hopefully) presents all the features that make F# interesting and worth learning. In this first part I will shortly introduce F# and the supported paradigms that will be discussed in the upcoming articles.
Introducing F#
In one of the papers about F#, the F# designers gave the following description: "F# is a multi-paradigm .NET language explicitly designed to be an ML suited to the .NET environment. It is rooted in the Core ML design and in particular has a core language largely compatible with OCaml". In other words this means that the syntax of the F# language is similar to ML or OCaml (don’t worry if you don’t know these languages, we’ll look at some examples shortly), but the F# language targets .NET Framework, which means that it can natively work with other .NET components and also that it contains several language extensions to allow smooth integration with the .NET object system.
Another important aspect mentioned in this description is that F# is multi-paradigm language. This means that it tries to take the best from many programming languages from very different worlds. The first paradigm is functional programming (the languages that largely influenced the design of F# in this area are ML, OCaml and other), which has a very long tradition and is becoming more important lately for some very appealing properties, including the fact that functional code tends to be easier to test and paralellize and is also extensible in a ways where object oriented code makes extending difficult.
The second paradigm is widely adopted object oriented programming, which enables interoperability with other .NET languages. In F# it is often used for implementing elementary data types (meaning that the operations on the type are well known and change very rarely), for grouping a set of elementary functions that are together used to perform some complicated operation (i.e. implementing an interface) and also when working with object oriented user interface frameworks.
Finally, the third paradigm supported by F# is language oriented programming (the design of F# in this area is largely influenced by ML, Haskell and also by LINQ). In general, language oriented programming is focused on developing executors for some code which has a structure of a language (be it a declarative language like XML, or a fully powerful language like some subset of F#). In this overview, I will focus on two techniques provided by F# that allow you to give a different meaning to a blocks of F# code. In a programming language theory, this is often called internal domain specific languages, because the code is written in the host language, but is specifically designed as a way for solving problems from some specific domain. An example of such language (and an associated executor) is a block of code that is written as a linear code, but is executed asynchronously (in F# this can be implemented using computation expressions), or a query that is written in F#, but is executed as a SQL code by some database server (this can be implemented using F# quotations).
F# Overview - Links
In the rest of this article series we will look at all these three paradigms supported by F# starting with functional programming and basic F# types used when writing code in a functional way, continuing with object oriented programming and the support for .NET interoperability which is closely related to the OOP in F#. Lastly, we will look at the language oriented programming paradigm including some of the most important .NET and F# library functions that make it possible. The rest of the articles from the series are available in my other blog:
Other F# Resources
There are many other places where you can find useful information about F#. First of all, there is an official F# web site [1] where you can find the language specification, documentation and other useful resources. There are also two books written about F# (one already published [2], second will be available soon [3]). Lastly, there are also a lot of community resources including an F# community web site with discussion board [4], wiki [5] and several blogs [6,7,8,9]. Finally, there are also some projects developed by the F# community that are available at CodePlex - the first one includes various F# code samples [9, 10] and the second is based on my thesis and tries to solve several web development problems [11].
|
-
My last post at the hubFS was an article that I wrote earlier to my personal blog and I wrote one more article some time ago that is related to F# and I wasn't posted here. The article is a part of a short series that discusses how the same LINQ-related problem can be solved in F# and in C# 3.0 (and in fact, my implementation of the C# version was largely motivated by the F# solution). Because I think that the articles are closely related, I'll write just a short summary here and add links to both articles available in my other blog.
The articles deal with a problem how can one build a LINQ (database) query at runtime. A typical example of a query that developers want to build at runtime is a query where the user can modify the filtering condition (where clause) - for example the application may want to allow combining several conditions, like c.Country == <entered value> with c.Name.StartsWith(<entered value>). In addition you may want to allow combining these single conditions using various operators (at least or and and).
The article about the F# version also shortly introduces the F# comprehensions and quotations that are used when writing LINQ queries using F#. Then it gives a few examples of using LINQ for database queries and finally, it fully describes the example that is shortly introduced below. In the article about the C# version, I described several extensions that are very useful when writing the queries in the F#-style as well as a solution that I use to allow a mechanism similar to F# holes (that are shortly introduced below). Finally, the F# article also contains a version of the FLINQ sample that is compatible with Visual Studio 2008 Beta 2.
Building LINQ Queries at Runtime in F#
In F#, the queries are represented using quotations which can be easily combined thanks to the support for holes (written as underscore). The following example first defines two basic conditions (one of them always returns true and the second returns false). Later, we define two combinators for combining conditions using || and && operators. Finally, we build a condition that is composed from several elementary conditions (stored in a dictionary called dict). To do this we can use the Fold function which takes an initial condition and combines it with all the conditions in the dictionary: // Basic conditions that always return true/false
let falseCond = « fun (c:Customer) -> false »
let trueCond = « fun (c:Customer) -> true »
// Condition combinators
let (||*) f g = « fun (c:Customer) -> (_ c) || (_ c) » f g
let (&&*) f g = « fun (c:Customer) -> (_ c) && (_ c) » f g
// Which combinator will we use?
let (^^) = if generateOr then (||*) else (&&*)
// Build the expression by folding all items in a dictionary
let expr =
dict.Fold(fun key propSelector e ->
Console.Write("Enter value for '{0}':\n> ", key);
let enteredVal = Console.ReadLine();
let currentCond =
« fun (c:Customer) -> ((_ c):string).IndexOf(_:string) <> -1 »
propSelector « §enteredVal »
(currentExpr ^^ e))
(if generateOr then falseCond else trueCond)
Building LINQ Queries at Runtime in C#
In C# the representation of the lambda expression can be either a delegate (Func<...>) or an expression tree (Expression<Func<...>>) and the decision whether to generate a delegate or an expression tree depends on the expected type, which means that the code needs to define a type alias. This is possible thanks to the using statement (though this can be avoided by using helper functions, but in this example I'll instead use aliases). Primitive conditions are easy to define once we have a type alias CustomerCondition (which stands for an expression tree of a lambda expression taking a Customer as an argument and returning a bool). When defining a combinators, we need a way for calling an expression tree in another expression tree and this is done by the method Expand (which is not part of LINQ and is further discussed in the article). Finally, the code that generates the result uses similar technique as the previous F# version: // Basic conditions that always return true/false
CustomerCondition trueCond = (c) => true;
CustomerCondition falseCond = (c) => false;
// Condition combinators
CustomerConditionCombinator combineOr =
(f, g) => (c) => f.Expand(c) || g.Expand(c);
CustomerConditionCombinator combineAnd =
(f, g) => (c) => f.Expand(c) && g.Expand(c);
// Which combinator will we use?
CustomerConditionCombinator combinator =
generateOr ? combineOr : combineAnd;
// Build the expression by folding all items in a dictionary
var expr = dict.Fold((e, item) => {
Console.Write("Enter value for '{0}':\n> ", item.Key);
string enteredVal = Console.ReadLine();
CustomerPropSelector propSelector = item.Value;
CustomerCondition currentCond = (c) =>
propSelector.Expand(c).IndexOf(enteredVal) != -1;
return combinator(e, currentCond);
}, generateOr ? falseCond : trueCond);
|
-
The project that I'm going to introduce in this article has been public for some time already, but I wrote about it only on my personal web site and didn't post the article to the hubFS, which is what I usually do with my other F# articles, so some of the hubFS readers may have missed it. (Cross-posted from: http://tomasp.net/articles/fswebtools-intro.aspx)
I started thinking about working on "Ajax" framework quite a long time ago - the key thing I really wanted from the beginning was using the same language for writing both client and server side code and the integration between these two sides, so you could write an event handler and specify if it should be executed on the client or on the server side. About a year ago I visited Cambridge (thanks to the MVP program) and I had a chance to talk with Don Syme [^]. Don showed me a few things in F# and suggested using F# for this project, so when I was later selected to do an internship at MSR, this was one of the projects that I wanted to work on.
The original reason for using F# was its support for meta-programming ([8], which makes it extremely easy to translate part of the page code-behind code to JavaScript. During my internship, the F# team was also working on a feature called computational expressions, which proved to be extremely useful for the F# Web Tools as well - I bet you'll hear a lot about this from Don soon, so I'll describe only the aspects that are important for this project. Aside from these two key features that F# has, I also quite enjoyed programming in F# itself - I already used it for a few things during the last year, but I could finally work on a large project in F# (and discuss the solution with the real experts!) and I don't believe I would be able to finish the project of similar complexity during less than three months in any other language (but this is a different topic, which deserves separate blog post).
What makes "Ajax" difficult?
Traditional "Ajax" application consists of the server-side code and the client-side part written in JavaScript (the more dynamicity you want, the larger JS files you have to write), which exchanges some data with the server-side code using XmlHttpRequest, typically in JSON format. I think this approach has 3 main problems, which we tried to solve in F# Web Tools. There are a few projects that try to solve some of them already - the most interesting projects are Volta from Microsoft [1], Links language [3] from the University of Edinburgh and Google Web Toolkit [2], but none of the projects solve all three problems at once.
1. Limited client-side environment
First of the problems with "Ajax" style applications is that significant part of the application runs on the client-side (in a web browser). Currently majority of the web applications use JavaScript to execute code in the browser, so in the F# Web Tools we wanted to use JavaScript as well, however in the future, when installation of Silverlight [4] becomes more common, we would like to allow using Silverlight as an alternative.
F# Web Tools allows you to write client-side code in F# (so if you don't know JavaScript, you don't have to learn it!) and also use your existing knowledge of .NET and F# classes and functions (so you can use some of the .NET and F# types when writing client-side code). The code you write in F# is of course executed in JavaScript, so it runs in any browser that supports JavaScript - the current implementation is tested with IE and Firefox, but it could be easily tested with other browsers.
2. Discontinuity between server and client side
The second major problem with "Ajax" applications is that the web application has to be written as two separate parts - client-side part (when written in JavaScript) consists of several JS files and the server-side part (for example in ASP.NET) is written as a set of ASPX and C# or VB files. Also when using JavaScript, both sides use different formats to store the data, so bridging this gap is difficult. In Silverlight [4] (or in GWT [2]), the gap is still there, even though both parts are written using the same technology - the client-side part is usually even a separate project.
In F# Web Tools we wanted to make this discontinuity as small as possible - You can write both server and client-side code in a same file (as a code-behind code). You can also call server-side functions from the client-side code and you can use certain data-types (including your own) in both sides and you can send them as an arguments from one side to the other. What's also important is that these calls are done without blocking the browser, but without the usual cumbersome programming style (calling a function, setting a callback and writing the rest of the code in the callback).
3. Components in web frameworks are only server-side
The third key problem appears once we tightly integrate client and server side code, because there is one more step that has to be done - most of the web frameworks have some way for composing web site from smaller pieces (in ASP.NET this is done using controls) and by defining the interaction between these pieces, however they allow defining the interaction only for the server-side, which is rather problematic in "Ajax" applications, where most of the interaction between components is done on the client-side.
Since F# Web Tools is built using ASP.NET, we wanted to allow same compositionality as ASP.NET - to achieve this, controls written using F# Web Tools can wrap both server and client side functionality. Controls than expose both server and client side properties and events that can be used by the page to implement server-side, respectively client-side interaction between components.
Example - "Ajax" dictionary
I will demonstrate some of the F# Web Tools features using an "Ajax" dictionary application (see screenshot on the right side) which displays possible matching words as you type the word you want to find. This is one of the typical "Ajax" tasks, so it's a good example to start with. First, we need to define the code-behind file for the ASP.NET page - the page itself is quite simple, it contains just two controls - textbox for entering word that you're looking for (txtInput) and generic element for displaying results (ctlOutput), so let's look at the code-behind: // F# record type, which will be used for sending lookup
// results from the server-side to the client-side
type SearchResult = { English:string; Other:string; }
// Code-behind type for the ASP.NET page
[<MixedSide>]
type Suggest =
inherit ClientPage as base
// Controls for entering text and displaying result
val mutable txtInput : TextBox
val mutable ctlOutput : Element
(* .. interaction of components will go here .. *)
Aside from the code-behind class, we also defined a type (SearchResult), which will be used for returning loaded results from server to the client side - it is just a F# record containing two strings (word in English and word in the language we're translating to). Now let's look at the methods that define the interaction logic. The first method that we will look at is a method running on the server-side that takes entered text as an argument and returns a collection of results (ResizeArray<SearchResult>) - it is a static method, so it can't modify anything else on the page (I will write about non-static methods in one of the next articles): // Searches the dictionary for specified prefix
static member LoadSuggestions(prefix) =
server
{ let db = DictionaryDb(connectionString)
let res =
SQL <@ { for p in §db.Dictionary
when p.English.StartsWith(§prefix)
-> {English=p.English; Other=p.Other} }
|> truncate 10 @>
return (new ResizeArray<_>(res)) }
The method is all wrapped in server computational expression (this is one of the new F# features - it will be in details described in the Expert F# [6] book, but I'll definitely write a few lines about it as well) - for now you can read it as (almost) ordinary F# code wrapped in a block that specifies how the code should be executed. The server block is executed as ordinary F# code, but wrapping the code in this block allows us to call it from the client side later. The server block also changes the type of the method, so it doesn't return ResizeArray<SearchInfo>, but Server<ResizeArray<SearchInfo>>, where the Server type helps to ensure that the code will be executed only on the correct side.
Now, let's look at the members that define client-side interaction. The entry point on the client-side is a method called Client_Load, which is executed when the page is loaded in the browser: /// Initialization on the client side - attach event handlers
[<ReflectedDefinition>]
member this.Client_Load(sender, e) =
client
{ do this.txtInput.ClientKeyUp.AddClient(this.UpdateSuggestions) }
In this case we just register an event handler that will be called whenever user types something to the txtInput textbox. It is important to note that this code will be executed in JavaScript - even though when writing it, it is easy to forget about this! You can see the method that is used as an event handler in the next code sample (this whole method will be executed in JS as well): [<ReflectedDefinition>]
member this.UpdateSuggestions(sender, e) =
client
{ do! asyncExecute
(client_async
{ let sprefix = this.txtInput.Text
let! sugs = serverExecute(Suggest.LoadSuggestions(sprefix))
do! this.DisplayResponse(sugs) }) }
If you look at the method, you can see that it contains call to the asyncExecute function and the argument given to the function is entire block of F# code marked using client_async computational expression. The asyncExecute function executes the given block asynchronously, which means that it doesn't block the calling function (and it doesn't block browser GUI) - you can look at it as if it created new thread (but it's actually using one trick from functional programming called continuation passing style, because JavaScript doesn't support threads). In the client_async block, we first read the value from the textbox and then call the LoadSuggestions static method to get collection with matching words. The call to the server-side functions is done using serverExecute function (which is a special function, because it bridges the gap between client and the server automatically). Once we get the result we can call the DisplayResponse method to display the results. You can see that we used a let! and do! instead of let and do here - this is because we're calling a methods that are written using F# computational expressions (server or client blocks). In general when you're calling any ordinary code, you can use the operators without the exclamation mark, but when calling a code wrapped in a block you have to use let! or do!. [<ReflectedDefinition>]
member this.DisplayResponse (sugs:ResizeArray<Result>) =
client
{ let sb = StringBuilder()
let array = sugs.ToArray()
do Array.iter (fun (res) ->
sb.Append("<li><strong>" + r.Source +
"</strong> - " + r.Target + "</li>") |> ignore)
do this.ctlOutput.InnerHtml <- "<ul>" + (sb.ToString()) + "<ul>" }
In this last sample code, we just generate HTML code from the data we received from the server and display them. It is interesting to note, that the code is running on the client-side (it's wrapped in the client block), but you can still use some F#/.NET types and functions in the code (we're using ResizeArray, StringBuilder classes and Array.iter function). This is possible because F# Web Tools re-implements subset of the F#/.NET functionality for the client-side code. And that's all - I described slightly simplified version of one of the F# Web Tools demos, but you can get the full source code if you check out our CodePlex project. You can also look at the live Dictionary sample [^].
More information
I think this example gives you a general idea what is the F# Web Tools and why it is interesting. I will definitely write more about it in the future, because I didn't describe all important features in this example. If you're interested in this project, you can also read the Paper we submitted to the ML Workshop or slides from the presentation I did at the end of my internship in MSR Cambridge (see below). This project is also available to the community at CodePlex, so you can look at the source code (including two more samples).
Related projects and links
|
-
The growth of computer CPU speed is slowly being replaced by the growth of number of CPUs (or cores) in the computer at least for the close future. This causes a revolution in the way software is written, because traditional and most widely used way of writing concurrent applications using threads is difficult and brings several serious issues. Some predictions say that within a few years, almost every computer will have about 16 cores, so there is a huge need for programming paradigms or idioms that help developers write concurrent software easily (see also The Free Lunch Is Over [^] written by Herb Sutter).
Functional programming languages (especially pure functional languages) are interesting from this point of view, because the program doesn't have side-effects which makes it very easy to parallelize it (programs in pure functional languages can't have any side-effects by design, in other functional languages like F# the side-effects can be eliminated by following functional programming style).
Finding primes example
Let's say I want to find all prime numbers between 1 million and 1.1 million (this is example of an operation that can be nicely divided between more processors). First we will need function to test whether n is a prime: // Tests whether n is prime - expects n > 1
let is_prime n =
// calculate how large divisors should we test..
let max = int_of_float (Math.Sqrt( float_of_int n ))
// try to divide n by 2..max (stops when divisor is found)
not ({ 2 .. max } |> Seq.filter ( fun d -> n%d = 0) |> Seq.nonempty)
To find all primes in the specified range we could use the following code: let primes = [1000000 .. 1100000] |> List.filter is_prime
This code subsequently executes the is_prime function for all numbers that we want to test, but with multiple CPU cores it would be nice if the function divided the numbers into several parts and executed every part on different thread, so application would take benefit from multiple cores available in the system. The is_prime function doesn't have any side-effects so executing it several times in parallel can't change the result of operation (if the order of primes in the returned list doesn't change).
I wrote a function that does exactly what I described in the previous paragraph. To execute the operation in parallel, you can use the following code (the only difference is that I replaced List module with my ParallelList module): let primes = [1000000 .. 1100000] |> ParallelList.filter is_prime
On my notebook (with Intel Core Duo processor) the first code (using List.filter) takes about 2,3sec and using ParallelList.filter the operation takes only 1.3sec. The program isn't 2 times faster, because there is some overhead for creating and synchronizing threads, but in this case the speed increase is significant.
Aside from the filter function, the ParallelList module contains also the map function (which does the same thing as the List.map). There is also a function set_thread_count that you can use to configure how many threads should be used when executing parallel operations (the default value is 2).
Performance and future work
The performance of these functions is the key issue. Currently the ParallelList functions work can't be used for small number of repetitions of simple function, because the overhead is larger than the profit from parallel execution. If the operation takes less than 0.01ms and the number of repetitions is less than 1000 the List functions are usually better, but for operations taking longer time the results of ParallelList are better even with smaller number of repetitions. For operation taking about 0.1ms the ParallelList gives better results for more than 200 repetitions and for operation taking more than 1ms the number of repetitions is not very important (ParallelList is better even for 10 repetitions). These are inaccurate results that I got on my notebook, so if you're thinking of using the ParallelList, be sure to do some tests in your scenario! You can see some tests that I did in the demo project in stats.fs source file.
As I said earlier, ParallelList supports only filter and map functions, so implementing more functions would be useful. It would be also useful to provide some alternatives for functions that can't be executed in parallel (like fold_left) that could be used in some situations. I'd also like to implement functions for working with other collection types like Seq (IEnumerable) and array in the future.
I'm interested in your ideas and suggestions, so if you find something that could be improved in the code, or if you have any other idea, let me know!
Downloads
|
-
On 2nd of November I did a presentation on F# and functional programming at the Czech .NET User Group meeting.
Because I spent quite a lot of time with puting the presentation together I wanted to make it available to wider
audience, so I translated the slides and examples to English (anyway, translating the content took me only a few minutes :-)).
In case that some of the readers prefer Czech version, I attached the original documents too.
In the presentation I tried to introduce some basic concepts of functional programming (immutable values,
lazy evaluation) to the audience with no experience with functional programming, as well as present some
of the most interesting features of F# (like strict type system based on type inference, .NET interoperability and
metaprogramming). The whole contents of the presentation is following:
- Functional programming in F# - Introduction to the F# type system
- Some useful functional idioms - How to do Foldl/Map/Filter functions and Lazy evaluation in C#
- Interactive scripting - What is important for scripting, mathematical simulation
- Interoperability between F# and other .NET languages - How to use .NET libraries from F# and F# libraries from ohter .NET languages
- F# as a language for ASP.NET - How to use F# as a language for ASP.NET development
- Meta-programming in F# - Meta-programming features in F# and the FLINQ project
Downloads
|
-
Introduction
F# quotations allows you to easily write programs that manipulate with data representation
of program source code. If you're not familiar with quotations I recommend reading my
previous article [1] that contains short introduction
to this topic first. Quotations can be used for example for translating subset of the F# language to
another code representation or another language.
To get the quotation data of the expression you can either use <@ .. @>
operator or resolveTopDef function. In the first case the code written
between the "<@" and "@>" is converted to data during the compilation.
The resolveTopDef function allows you to get quotation data of top-level
definition (function) from compiled library at runtime (you have to use --quotation-data
command line switch while compiling the library). I mentioned that quotations can be used to represent
only subset of the F# language. Currently, one of the quotation limitations is that it's not possible to
enclose the whole class in the quotation operators. It is also not possible to get the representation
of the whole class at runtime nor the representation of class members (for example methods).
Class quotations
In one my project I wanted to be able to translate the class written in F# to another language,
so I wanted to make this possible. This option will be probably implemented in the future versions
of F#, so it will be possible to do this using more elegant method, but if you want to experiment
with quotations and use classes, you can use my solution to implement the prototype.
In the prototype I implemented, you have to write the class and one module that contains
the actual code for the methods. This makes it possible to get the structure of the class using
standard .NET reflection classes and to extract quotation data for the class members using
F# resolveTopDef function. Of course, you can't create instance of module, but for metaprogramming
we only need to be able to get the quoted code and unless you want to use the class from code, you can leave
the implementation of the class methods empty.
First I had to design the data structures for representing the class. If I were adding this feature to the F#
metaprogramming, I would probably extended the expr data type to make this possible in
consistent way. However I didn't want to modify the F# source code, so I designed the following types
that are similar to the expr type (I reversed the order of type declarations, so it
is easier to understand):
/// Structure that contains information about class -
/// consists of name, name of base class and members
type classInfo = {
name:string;
baseName:string option;
members:classMember list;
};;
/// Member of the class
/// For working with this type use cmfCtor, cmfMethod, cmfField, cmfProp..
type classMember;;
/// Type for working with class members
/// Query function tests whether member is of specified type
/// Make function creates member
type 'a classMemberFamily with
member Query : classMember -> 'a option
member Make : 'a -> classMember
end;;
/// Constructor declaration
/// - parameter should be lambda expression
val cmfCtor : (expr) classMemberFamily;;
/// Method delcaration
/// - method name, expression (should be lambda) and return type
/// (types of parameters are stored in lambda expression)
val cmfMethod : (string*expr*Type option) classMemberFamily;;
/// Field declaration
/// - field name, type and init expression
val cmfField : (string*Type*expr option) classMemberFamily;;
/// Property declaration
/// - property name, getter and setter (both should be lambda expr) and type
val cmfProp : (string*expr*expr*Type) classMemberFamily;;
This representation is still very limited - for example it isn't possible to represent
polymorphic methods. In the previous code, the classMember can be used to
represent any member (similarly to the F# expr that can represent any expression).
The cmf[Something] values are equivalent to the
ef[Something] from F# quotation library and allows you to work with
classMember type.
The following example shows how to write simple class with module that can be used for extracting
the quotation data:
#light
// Module with implementation of methods
module Person_Meta = begin
// Simulates field of the class
let name = ref ""
// Represents constructor
let ctor (n) =
name := n
// Represents method
let Say (pre:string) =
let s = pre^", my name is "^(!name)^"." in
print_string s
// Represents property
let get_Name () =
(!name)
let set_Name (value) =
name:=value
end
// The real class
// Members are just a placeholders and the quotations
// are extracted from the previous module
type Person = class
val mutable name : string;
new((n:string)) = { name = ""; }
member this.Say(pre:string) = ()
member this.Name with get() = "" and set((v:string)) = ()
end
In the last code sample, I'll show how the functions I wrote can be used for working
with the previous piece of code. To get the representation that I mentioned earlier of
the class Person, you can use function getClassFromType. This function
extracts structure of the class (from the class itself) and quoted code from the module
with the _Meta suffix. The following example is quite simple. It prints
basic class information (name and base name) and then iterates through all the members and
prints all available information for every member. For printing the expr type
(which represents the quoted code) I used printf function with the output_any formatter.
#light
do
// Get the class info for class 'Person'
let clsInfo = getClassFromType ((typeof() : Person typ).result)
// Print name and optional base name
Console.WriteLine("Class '{0}':", clsInfo.name)
if (clsInfo.baseName <> None) then
Console.WriteLine(" base: {0}", clsInfo.baseName)
// Iterate through class members
clsInfo.members |> List.iter ( fun m ->
match cmfMethod.Query m with
| Some (name, expr, ret) ->
Method "printf ( %s : %a ) = " name output_any ret
| _ ->
match cmfField.Query m with
| Some (name, typ, init) ->
printf "Field ( %s : %a) " name output_any typ
| _ ->
match cmfProp.Query m with
| Some (name, getter, setter, typ) ->
printf "Property ( %s : %a )\n" name output_any typ
printf "get = %a\n" output_any getter
printf "set = %a\n\n" output_any setter
| _ ->
match cmfCtor.Query m with
| Some (expr) ->
printf "Ctor = %a\n\n" output_any expr
| _ ->
failwith "Error!"
)
Conclusion
The aim of this article isn't to present fully working solution, but to suggest a few enhancements
to the F# metaprogramming that I think would be useful. You can use the source code attached to this
article to find (and experiment with) some interesting use cases of metaprogramming that require working
with classes, for example translating classes written in F# to another language. The biggest limitation
of the solution I presented is, that you have to write every implementation twice - it occurs in the
body of the class and in the module used for metaprogramming too (however the code looks very similar).
If you want to translate F# code, you don't need to implement methods/properties of the real class, but
if the program creates instances of the class at runtime (and uses the quoted code to perform some analysis etc.),
you'll need to write both implementations.
Downloads
Links and references
|
-
Some time ago, I wrote an article about useful utility called F# quotations visualizer. This utility can be used to show visual representation of F# quotations, which can represent (subset of) source code written in F#. There are two ways that you can use to get F# quotations - first is using operators <@@ ... @@> (this returns quotation of the code written inside the operator), second method is to get quotation of top level definition from compiled F# assembly (you have to explicitly enable this using command line switch --quotation-data while compiling assembly).
Because I added several new features to the original Quotations visualizer, I decided to publish the latest version - here is the list of main improvements:
- Application is rewritten using active patterns (new F# language feature)
- It is possible to extract quotations from compiled F# assembly (if it contains quotation data)
- Added support for several missing language constructs
Active patterns
Active patterns is a new (experimental) feature in the F# language. You can find some information about this feature in Don Syme's article [1]. In simple words, active patterns allows you to write "switch" consisting of functions (patterns) that return 'a option type. When the code is executed, it finds first pattern whose query returned a value (Some(...)) and the body of selected pattern is executed.
This feature makes it very easy to work with F# quotations because all ef[Something] values that are used for querying quotations have the required signature (the Query function that returns 'a option type), so they can be used with the active patterns feature.
In the quotations visualizer, we need to match the expression (type expr) with all possible expression families (ef[Something]) and choose the first one that matches. To see how active patterns work, you can look at the following part of the function that does this. First, without active patterns: match efForLoop.Query(expr) with
| Some(nfrom,nto,body) ->
// Statement: for i=start to end do body; done;
// .. create tree node ..
| _ ->
match efWhileLoop.Query(cond,body) with
// Statement: while condition do body; done;
// .. create tree node ..
| _ ->
match efCond.Query(t,(cond,trbody,flbody)) with
// if (cond) then trbody; else flbody;
// .. create tree node ..
| _ ->
// unknown expression
And with active patterns the source looks like this: #light
let EFForLoop = efForLoop
let EFWhileLoop = efWhileLoop
let EFCond = efCond
match expr with
| EFForLoop(nfrom,nto,body) ->
// Statement: for i=start to end do body; done;
// .. create tree node ..
| EFWhileLoop(cond,body) ->
// Statement: while condition do body; done;
// .. create tree node ..
| EFCond(t,(cond,trbody,flbody)) ->
// if (cond) then trbody; else flbody;
// .. create tree node ..
| _ ->
// unknown expression
This is very simple example and there are many situations where active patterns are even more helpful. You may have also noticed that the code doesn't contain any semicolons. This is the result of another new feature called lightweight syntax (it is turned on by the #light directive) - if you turn it on the whitespace becomes significant and compiler can understand structure of the code using whitespace instead of semicolons, parentheses and begin/end keywords. This feature is described in the F# manual [2].
Extracting quotations from assembly
When you specify --enable-quotation-data switch to the F# compiler, it stores quotation of every top level definition (functions in modules) in the assembly. This quotation can be later retrieved using resolveTopDef function, however to use the function for loading top-level definitions from another assembly, you first have to load quotation data from the assembly. The following snippet shows how to do this. let asm = Assembly.LoadFile(name) in
asm.GetManifestResourceNames() |> Array.to_list
|> List.filter (fun rn ->
rn.StartsWith(pickledDefinitionsResourceNameBase))
|> List.iter (fun rn ->
explicitlyRegisterTopDefs asm rn (readToEnd (asm.GetManifestResourceStream(rn))))
This code first loads the assembly (using LoadFile method which accepts assembly file path). Than it gets all managed resources of the assembly and selects only those resources, whose name starts with pickledDefinitionsResourceNameBase (this is a constant declared in Microsoft.FSharp.Quotations.Raw module). Now we have all resources containing F# quotation data, and we can use explicitlyRegisterTopDefs function to load quoted top level definitions. The explicitlyRegisterTopDefs method takes three parameters - assembly, name of the resource and resource data (byte array). When the top level definitions are registered using this method, it is possible to load quotations of functions declared in loaded assembly - and this is exactly what happens when you click on the "Open F# assembly" link in the application. If you are interested in the complete code, look at the attached source code of quotations visualizer application.
Links
Downloads
|
-
You probably already saw my post regarding CodeDOM generator for the F# language and how to use it with ASP.NET. To make it more accessible for everyone, I created project at the new Microsoft community site called CodePlex [^].
BTW: CodePlex looks like a really good site. It is based on Visual Studio Team System (which means that developers of the project can do most of the work directly from Visual Studio). It provides management of "Work Items" (TODO list), source control and many other useful things! For example if you have any feature requests or bug requests, send them to the discussion and I can easilly create work item from the message in the discussions.
If you are interested in this project and you want to help with developing of some parts, or if you are working on a project that is related to CodeDOM and F#, please let me know. Any help or feedback is kindly welcome!
|
-
CodeDOM and providers
CodeDOM (Code Document Object Model) is set of objects (located in System.CodeDom namespace) that can be used for representing logical structure of .NET source code. For example you can use CodeTypeDeclaration class to represent class declaration or CodeAssignStatement to represent assignment in the body of method. CodeDom is language independent and CodeDOM structure can be translated to source code in specified language using code generator (class that implements ICodeGenerator namespace). The CodeDOM structure can be also compiled to assembly using code compiler (implementation of ICodeCompiler interface). Microsoft provides several code providers with .NET Framework (for C#, VB.Net, JScript and C++), but you can add your own by by implementing previously mentioned interfaces.
In the .NET Framework, CodeDOM is used for automatic code generation. For example when you add web reference to your project, the wrapper class that calls web service is generated using CodeDom. Some more examples are typed dataset (generated from XSD files) and generator for data layer in the upcoming LINQ project, but the most interesting use for CodeDOM for me is in ASP.NET 2.0.
Typical ASP.NET web page consists of several aspx/ascx files and associated code behind files written in one of the .NET languages. When building web page, ASP.NET uses CodeDOM for building aspx/ascx files. It parses these files and generates according CodeDOM structure. For example <asp:Button id="btn" runat="server" /> declaration is translated to declaration of btn member and code that assigns new instance of Button object to this member. This generated structure is compiled to .NET assemblies using code compiler and the web page is ready to use. If you use any in-line code in ASP.NET page, it is included in the CodeDOM structure as literal, which means that if the generated CodeDOM is compiled using C# code compiler, it can contain literals only in C# language (because you can't mix languages in one source file).
ASP.NET and F#
In ASP.NET 1.1 you could compile code behind files using different compiler than the rest of the page (source generated from aspx/ascx). This method is used by ASP.NET in F# demo by Robert Pickering [1]. The more complex approach that is available in ASP.NET 2 is to write CodeDomProvider implementation for F#. This makes it possible to write F# in-line code in web pages and this method allows you to fully benefit of ASP.NET compilation model (for example you can use Publish Web Site command in Visual Studio).
FSharpCodeProvider & AspNetFsharpCodeProvider
I wanted to implement CodeDomProvider for the F# language, for the reasons mentioned above. Currently, I'm presenting version of provider that is able to compile ASP.NET pages quite well, however I implemented only features required by ASP.NET, so the provider is not complete. There are also some problems that has to be handled specially (for example, F# doesn't support partial classes), so apart from standard provider (FsharpCodeProvider), I also implemented AspNetFsharpCodeProvider that contains several 'hacks' for ASP.NET.
How to use it?
Important: For correct functionality of F# CodeDOM compiler, you have to modify your system variable PATH to include 'bin' directory of your F# installation folder. This is because, code compiler uses fsc.exe for compilation and there is no simple way for finding where the F# is installed.
The most simple way to start creating ASP.NET web sites in F# is to use VS.NET project template that you can find in the downloads section. Using this template, you can create web page that is already configured to use F# language. After installation, select File - New - WebSite.. and in the dialog select language 'Visual C#' (it is not possible to add another language AFAIK) and than select 'F# WebSite' template.
If you want to configure ASP.NET web site to use F# code provider manually, you have to do the following steps. First modify the web.config file to include the following elements in the compilation section: <?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true">
<compilers>
<compiler language="F#;f#;fs;fsharp" extension=".fs"
type="EeekSoft.FSharp.CodeDom.AspNetFSharpCodeProvider, fscodeprovider"/>
</compilers>
</compilation>
</system.web>
</configuration>
This adds reference to the assembly that contains the AspNetFShaprCodeProvider class and it configures ASP.NET to use F# code generator for aspx files which Language attribute is set to "F#". When this is done, you can add F# page by adding aspx and code behind fs files. In the Page directive, set the Language attribute to "F#" and define the code behind class in the fs file.
The following example shows code behind class for simple web page (that contains one button with ID set to btnOk): namespace FSharpWeb
open System;;
open System.Web.UI.WebControls;;
// Code behind class declaration
type Default = class inherit System.Web.UI.Page as base
// Declaration of button control
val mutable btnOk : Button;
// All fields must be initialized in constructor
new() = { btnOk = null; }
// Set text of button in onload
override this.OnLoad (e) =
btnOk.Text <- "Hello world!";
base.OnLoad (e);
end;;
There is still one little difference when compared with C#, which is that it is required to manually declare all controls (with ID) that are on the page (btnOk in this example). This is because F# doesn't (currently) support partial classes and so the partial class with control declarations that is generated by ASP.NET is dropped by the CodeDOM provider. The rest is very similar to how you develop ASP.NET pages in other languages; you can see more examples if you download attached example application. More information regarding object oriented programming in F# can be found in F# manual available at F# homepage [3].
Other CodeDom provider notes
As I mentioned earlier, the CodeDOM generator I created is still very limited and it is tested only with ASP.NET. You can find list of not implemented features in the release notes (see downloads). In the future, I'd like to extend it to support generating code for accessing web services too (code is generated using wsdl tool). If you have any interesting idea, how it could be used or if you have any other comments, ideas, bug fixes, etc., please let me know (at tomas@tomasp.net).
Downloads & links
|
-
This article describes very simple code that I wrote while learning how to work with the F# quotations library. Using the F# quotations you can get tree representation of the quoted expression. This allows you to write code that takes code written in F# as data and performs some code analysis or compiles/translates that code to different language. This is very similar to the new C# 3.0 expression trees where you can get expression tree from lambda expression and translate this tree for example to SQL (using DLINQ). However expression trees in C# 3.0 are very limited when compared with F# quotations, so that's one of the many reasons why F# is interesting language.
Introduction to F# quotations
If you want to see how F# represents some expressions you can try it in the FSI (F# interactive). First you need to open three modules that contain functions for working with quotations: open Microsoft.FSharp.Quotations;;
open Microsoft.FSharp.Quotations.Raw;;
open Microsoft.FSharp.Quotations.Typed;;
Now you can see the internal representation of quotations by writing quoted expression (In VS.Net you can select code and hit Alt+Enter to sent selected code to F# interactive). <@ 1 + 2 @>
val it : int expr
= <@
Microsoft.FSharp.MLLib.Pervasives.op_Addition (Int32 1) (Int32 2)
@>
One more example with two operators: <@ 1 + 10/5 @>
val it : int expr
= <@
Microsoft.FSharp.MLLib.Pervasives.op_Addition (Int32 1)
(Microsoft.FSharp.MLLib.Pervasives.op_Division (Int32 10) (Int32 5))
@>
It is not difficult to understand that addition in the first example is represented as function call (function application in the terminology of functional languages) where the function is op_Addition operator and parameters are two integers. This is actually little simplification, because in functional programming you should look at this code as two function applications. The first application binds first parameter of op_Addition to 1 and the result is again function. The second application uses the function returned by the first application and passes 2 as parameter. You can look at F# quotations using both approaches and in the rest of the article I will use the first one.
To demonstrate how to work with F# quotations I decided to write simple transformation of limited mathematical expressions from F# quotations to my data type. The discriminated union that I'm using as target of transformation is similar to the union in F# source file VS.Net template (as you can see it supports only four most basic mathematical operations and all values are stored as integers): type simple_expr =
| Int of int
| Add of simple_expr * simple_expr
| Sub of simple_expr * simple_expr
| Mul of simple_expr * simple_expr
| Div of simple_expr * simple_expr;;
Printing and evaluation of this structure is simple, so I won't describe these here. If you want to look at these functions see attached source code. (function for evaluation is part of VS.Net template too).
Working with quotations
Finally, let's do the interesting part :-). As I mentioned you can get quotations using <@ ... @>. When you want to perform analysis of quotations you need to get the underlying raw representation using the <@@ ... @@> operators. For decomposing the quoted expression we can use Query functions on the expression families defined in the Raw module. This may sound a bit confusing, but in the simple words - the Raw module contains several values that define language constructs (like constants, variables, function application and others) and you can use these values for matching and decomposing quoted expression. Following code should clear this a bit: let what_is x =
match Raw.efInt32.Query x with
| Some (_) -> "number";
| _ ->
match Raw.efApps.Query x with
| Some (_) -> "application";
| _ ->
"something else...";
// Prints "number"
print_string (what_is <@@ 1 @@>);
// Prints "application"
print_string (what_is <@@ 1 + 2 @@>);
Now we can start writing function that converts mathematical expression quotation to simple_expr structure. It will be recursive function with similar structure as previous. The first match for conversion of numbers will be simple. In the second match we'll have to do two things. First we'll need to determine what function is applied (whether it is addition, subtraction, etc..) and than we'll have to loop through parameters passed to this function and call the function recursively each parameter. let rec parse x =
match Raw.efInt32.Query x with
// x contains the number so we can simply return Int(x)
| Some (x) -> Int(x);
| _ ->
match Raw.efApps.Query x with
| Some (op,args) ->
// op contains quoted reference to function (operator)
// so we need to extract name of applied function from it
let opname = (
match Raw.efAnyTopDefn.Query op with
// operators are top-level definitions so we extract name
// from 'a' that contains info about the definition
| Some (a,_) -> let t1,t2 = a.topDefPath in t2
| _ -> failwith ("Function is not a top level definition.");
) in
// Recursively translate arguments to simple_expr
// and convert returned list to array
let av = List.to_array(List.map (fun arg -> parse arg) args) in
// Finally, match the operation name with basic
// operator names and return according simple_expr value
(match opname with
| "op_Addition" -> Add(av.(0),av.(1))
| "op_Subtraction" -> Sub(av.(0),av.(1))
| "op_Multiply" -> Mul(av.(0),av.(1))
| "op_Division" -> Div(av.(0),av.(1))
| _ ->
// some other operation - error
failwith "Not supported operation");
| _ ->
// something else than efApps and efInt32 - error
failwith "Not supported construction in expression.";;
As I mentioned, the more interesting part of the code is when the quotation matches with efApps which in our case means that the expression is mathematical operation. From the functional point of view efApps means series of function applications. The result of Query function of efApps is tuple containing expression with information about applied function (operator in our case) and parameters of this application.
For extracting operator name we match the first value with efAnyTopDef which returns information about top-level definitions. Returned structure contains assembly name as well as the function name that we need. In the second step we recursively call parse function to all arguments of operator. Once we know the function name and we have arguments translated to simple_expr, we can return the according simple_expr value.
(Cross-posted from: http://tomasp.net/blog/fsquotations.aspx)
|
-
So this is my first post to hubFS :-). First I'd like to give thanks to optionsScalper for creating blog for me. Before writing some more interesting posts on F# I'd like to introduce myself...
I'm student of computer science from Czech Republic (studying at Charles University of Prague). I'm also Microsoft C# MVP and I'm student consultant at our university. As student consultant I'm organizing presentations on several (mostly) Microsoft technologies and I'm also often talking there. Aside from studying I worked on many .NET projects including mostly ASP.NET development.
I started learning F# some time ago (but I'm still very far from knowing it well) and I was impressed with so many interesting possibilities of this language. So far, my favorite F# feature is meta-programming (also known as F# quotations), so my first posts will be focused on this topic.
My homepage is http://tomasp.net and most of the content that I publish somewhere else is also available at my homepage. You can contact me at tomas@tomasp.net
|
|
|