I worked on extending the F# ODSL (Optimization Domain Specific Language, link1 and link2) during the weekend. This is the first time I use F# quotations in a non-trivial fashion. I tried some quotation examples in Programming F# when I was learning the language and read code examples in F# PowerPack (for LINQ integration) and other libraries before.
Only after I actually write some programs in it, I can appreciate this F# feature more and have some of my own thoughts. In this post, I’d like to share these thoughts. I don’t intend to go into detailed F# code, but maybe in the future I will write a separate blog on my work on extending the F# ODSL.
General idea of F# Quotations
The great idea of quotation at least traces back to Lisp, where program is also a kind of data – the execution behavior of a piece of program is completely controllable by the user, just treat it as input data and write a custom evaluator for it. The default Lisp evaluator is eval, we can easily write a custom one to change the default behavior [LispEval].
In F#, we can also treat a piece of F# code as data by quoting it:
let quotedProgramAsData =
<@
let y = 1 + 2 * 30
20 * y
// code here:
// an F# program using a subset of F# language features
@>
When we create the variable quotedProgramAsData, the .Net runtime will not compute the expression in the quotation; instead it generates the following value:
val quotedProgramAsData : Quotations.Expr<int> =
val quotedProgramAsData : Quotations.Expr<int> =
Let (y,
Call (None, Int32 op_Addition[Int32,Int32,Int32](Int32, Int32),
[Value (1),
Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
[Value (2), Value (30)])]),
Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
[Value (20), y]))
Let’s visualize it in a tree form:
F# compiler generates this tree structure by free and the rest is how you deal/evaluate this tree. If the above code is not in quotation, F# compiler will generate code that put the value of the second parameter (1 + 2 * 30) of Let to the first parameter (y), and continue to generate code for the third parameter (20 * y). But because it is in the quotation, only the expression tree is generated without any explicit execution behavior for them.
With different purposes, we can write different evaluators for the quoted F# code. In the following I list some of the application areas.
Domain specific language (DSL)
There are different kinds of domain specific languages. In his book Domain Specific Languages, Martin Fowler summarizes them into external and internal ones:
DSLs come in two main forms: external and internal. An external DSL is a language that's parsed independently of the host general purpose language: good examples include regular expressions and CSS. External DSLs have a strong tradition in the Unix community. Internal DSLs are a particular form of API in a host general purpose language, often referred to as a fluent interface. The way mocking libraries, such as JMock, define expectations for tests are good examples of this, as are many of the mechanisms used by Ruby on Rails. Internal DSLs also have a long tradition of usage, particularly in the Lisp community. (http://martinfowler.com/books.html#dsl)
Quotations can be used as a very good language feature for implementing internal domain specific languages.
As stated as before, the quoted program is anyway an F# program. How could it be look like a domain specific one? Remember F# has a rich set of syntax while a domain language takes a small subset of it is usually enough expressive.
Take a look at the following program, which expresses the quadratic optimization for Support Vector Machines:
// solve the SVM using ODSL
let dsl_solver =
let index = [ 0..n-1]
Solver
<@
qp()
let alpha = vararray1 (index)
maximise (sum index (fun i -> alpha.[i]) -
(sum index (fun i -> sum index (fun j ->
coef.[i,j] * alpha.[i] * alpha.[j])))) // Eq. (1)
where
[
foreach index (fun i -> 0.0 <= alpha.[i]) // Eq. (2)
foreach index (fun i -> alpha.[i] <= C) // Eq. (2)
sum index (fun i -> alpha.[i] * y.[i]) = 0. // Eq. (3)
]
@>
and its optimization formulation:
Subject to constraints:
The correspondence between the DSL code and mathematical formulates is very clear. In the future work, we can even vectorize this DSL to eliminate the usage of sum: range -> lambda (int->value) -> value function, which will make the program and the formulas more similar.
Note that in the above example qp, maximise, sum and where are not magic, but are all F# functions whose behaviors are defined in the DSL. For example, F# function qp() only indicates the following code is a quadratic programming.
Compare this code with the code in my SVM post: it is clearer and thus easier to write.
The restriction of such a DSL inside F# is that the syntax of this DSL should follow that of F# -- it should be a valid program recognizable by the F# lexer and parser. This is why DSL people like Lisp which has so simple and flexible syntax (just brackets), but sometimes it is also boring and confusing when one gets lost in the brackets.
High performance computing
We know that F#’s Seq module and C#’s LINQ share some common features, e.g. chained operations and the laziness. Because these operations are so fundamental, LINQ team has spent enormous time optimizing LINQ; on the other hand, F#’s implementation is quite a standard one without heavy optimization because F# compiler team is not big and they have other important tasks to do. (F#’s Seq module actually does some optimization, e.g. for arrays and lists special routines are called instead of the general ones for IEnumerable<T> objects. But LINQ does more!)
So one idea of speeding up sequence operations in F# is to use LINQ’s equivalent functions. This could be implemented by putting F#’s sequence operations in a quotation and rewrite them in LINQ expression as done in F# PowerPack. Interested readers can read the source code of LINQ for F# in PowerPack.
The above example talks about basic data structures. As a data mining guy myself, let’s move to numerical computing: we can quote a piece of numerical code in F# and translates it into Fortran/C and a JIT Fortran/C compiler compiles the code translated code into native machine code which uses special instruction set in that platform (e.g. vectorized instructions in P4 CPU). Or even we can compile this piece of F# into GPU and utilize the parallel computing there. [Syme_ML06]
But any fancy optimization has some overhead too. For example, the compiling time may cost some time.
Education: Compiler courses
The last application area is education. There are few CS departments teaching functional programming. Some of them will mention it when teaching programming language concepts. F# is a push for FP into industry and main stream. Hope in the near future, schools will open tiny or selected courses for F#. See the recent F# in Education Workshop [Education] for details.
Ok. Let’s focus on compiler courses. Compilers, different from functional programming, are taught in nearly all CS departments. However the projects in compiler courses are a huge pain to students. They are so complicated! And the focus of the first half of the course (lexing and parsing) and the second half (code generation and optimization) are kind of separate. The theories behind the two halves are different. Sometimes, the course instructor focuses too more on lexing and parsing and don’t have enough time for students to work on code generation and optimization, which in my mind are more important in a compiler course. While lexing and parsing will occur in other CS courses too, e.g. computational theory, code generation and optimization will only be in a compiler course.
By using the F# quotations, F# compiler generates the expression tree for free. Just so convenient and straightforward! Considering that the F# syntax you can put in quotation is a capable set of imperative language + functional language, the expression tree for us to do the code generation and optimization is non-trivial. By using tools like .Net Reflector, we can also study how the standard F# compiler generates the same piece of code and learn the tricks there. Students can work on code generation for closures, and tail-recursive-calls – important language features that are not implemented or fully implemented in many main stream languages.
References
Some references in the above text:
[LispEval] Christian Queinnec , Lisp in Small Pieces.
[Syme_ML06] Leveraging .NET Meta-programming Components from F#, ML Workshop, 2006.
[Education] F# in Education Workshop, http://research.microsoft.com/en-us/events/fsharpined/.
A very good paper by Leijen and Meijer:
[Leijen&Meijer] Domain specific embedded compilers, Sigplan Notices, vol. 35, no. 1, pp. 109-122, 2000.
F# quotation extensively uses active patterns for expression tree pattern matching:
[Syme_Active] Extensible Pattern Matching via a Lightweight Language Extension, ICFP 2007.
Here are some relevant blog posts:
[Petricek] F# Overview (IV.) - Language Oriented Programming, http://tomasp.net/blog/fsharp-iv-lang.aspx.
[RubyDSL] Building a DSL in Ruby, http://jroller.com/rolsen/entry/building_a_dsl_in_ruby.
Two nice answers by Petricek & Harrop discussing Quotations (F#, Ocaml and Lisp) and DSL on Stackoverflow:
Acknowledgement
I’d like to thank Nathan Brixius for discussions on F# ODSL!
Highly descriptive blog, I enjoyed that bit. Will there be a part 2?
ReplyDeletewhoah this weblog is great i love studying your articles. Keep up the good work! You recognize, many people are searching around for this information, you could aid them greatly.
ReplyDeleteyurtdışı kargo
ReplyDeleteresimli magnet
instagram takipçi satın al
yurtdışı kargo
sms onay
dijital kartvizit
dijital kartvizit
https://nobetci-eczane.org/
MN77R
شركة مكافحة حشرات APrh5JgE9n
ReplyDelete