Code Quotations: Code-as-Data for F#
This tutorial will cover F# Code Quotations in-depth. You'll learn what Code Quotations are, how to use them, and where to apply them in your applications. We'll work through several real-world examples to highlight the important features -- and potential pitfalls -- of Code Quotations.
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Progressive f# tutorials nyc dmitry mozorov & jack pappas on code quotations code as-data for f#
1. F# Quotations:
“Code as Data”
Jack Pappas
Dmitry Morozov
SkillsMatter Progressive F# Tutorials NYC
September 19th, 2013
2. Code as Data
• Code & Data – What is the difference?
• Is there a difference?
• Programs exist to process data
– The F# compiler processes data (your source
code)
• Source code is no different than other kinds of
data
3. This Session
• What is a “quoted expression”?
– A simple, immutable data structure
– Represents a fragment of F# code
• Syllabus
– Background
– Working with Quotations
– Manipulating Quotations
– Applications of Quotations
4. Background
• The idea of treating code as data first
appeared in Lisp
Lisp F#
Code (1 2 3) [1; 2; 3]
Code as Data ‘(1 2 3) <@ [1; 2; 3] @>
5. Background
• A language that has the natural ability to
manipulate itself is extremely powerful
• “Amplify” a developer’s productivity using
metaprogramming techniques
6. Background
• Other languages can manipulate their own
code
– clang: C++ program, manipulates C++
– Roslyn: C# library, manipulates C#
– f2c: C program, manipulates FORTRAN and C
• What makes Lisp and F# different?
– Reflective meta-programming
– Built-in language feature vs. external
functionality
7. Background
• Now that we can manipulate code as data,
what can we do with it?
• Transform
– modify, convert, translate
• Analyze
– Static analysis
– Advanced testing techniques (e.g., verification)
– Collect and record code metrics (e.g., complexity)
8. Background
• Execute
– Interpret the code/data
– Translate to another lang., compile, execute
– Specialized hardware (e.g., GPU, FPGA)
– Specialized execution (e.g., auto-parallelization)
– Execute remotely (e.g., web service)
– Some combination of the above
– (Rarely) execute in same context, e.g., by
translating to IL and executing
9. Use Cases
• LINQ support in F#
– Extension of this idea:
Cheney, Lindley, Wadler.
“The essence of language-integrated query”
• Use as an IL when developing type providers
– Quotations much easier to work with
– Create quotations, translate to IL
11. Comparing Alternatives
• What alternatives exist to quotations?
– F# AST
– LINQ Expression Trees
– CIL (MSIL)
• Imperative: Expression Trees, CIL
– C# lambdas compiled into Expression Trees by
the C# compiler
• Functional: F# AST, Quotations
12. Comparing Alternatives
• F# AST
– Designed for the compiler’s internal use
– Carries more information than quotations
– Requires additional library
– Unwieldy for simple use cases
13. Comparing Alternatives
• LINQ Expression Trees
– Introduced in .NET 3.5; initially somewhat limited
– Expanded in .NET 4.0; now roughly 1:1 with CIL
– Doesn’t support certain functional features like
closures
14. Comparing Alternatives
• Common Intermediate Language (CIL)
– Low-level; relatively difficult to work with
– Supports all CLR features
– F# Quotations don’t support all CLR features
●
Notably, throw and rethrow – these need to be
wrapped in a function and compiled using the proto
compiler
●
No fault/filter clauses
• F# Quotations don’t support open generics
15. Quotations: How-To
• Open some namespaces
• Define a module, then some functions within
the module
open System.Reflection
open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations
module Foo =
let addTwo x = x + 2
16. Quotations: How-To
• Apply [<ReflectedDefinition>] to your function
– Or, apply it to your module as a shortcut for applying it to
all functions in the module
module Foo =
/// Dummy class used to retrieve
/// the module type.
type internal Dummy = class end
[<ReflectedDefinition>]
let addTwo x = x + 2
17. Quotations: How-To
• Using Reflection, get the MethodInfo for your
function
• Call Expr.TryGetReflectedDefinition with the
MethodInfo to get the Expr for the method
18. Quotations: How-To
[<EntryPoint>]
let main argv =
let fooType = typeof<Foo.Dummy>.DeclaringType
let addTwoMethodInfo =
match fooType.GetMethod "addTwo" with
| null -> None
| x -> Some x
addTwoMethodInfo
|> Option.map Expr.TryGetReflectedDefinition
|> Option.iter (printfn "addTwo: %A")
19. Quotations: How-To
• For each active pattern in Quotations.Patterns,
there is a corresponding static member of the
Expr type.
– These are used to construct and destructure each
quotation “case”.
• Two types of quotations: typed and untyped
– Typed: Expr<‘T>
– Untyped: Expr
20. Quotations: How-To
• Expr<‘T> is just a wrapper around Expr
– Use the .Raw property for Expr<‘T> -> Expr
– Use the Coerce pattern for Expr -> Expr<‘T>
• Quoting an expression
– Typed: <@ fun x -> x + 2 @>
– Untyped: <@@ fun x -> x + 2 @@>
21. Quotations: How-To
• “Splicing” – a way to combine quotations
– Inserts one quotation into another
– Typed:
let bar = <@ 3 + "Blah".Length @>
let foo = <@ fun x -> x + %bar @>
– Untyped:
let bar = <@@ 3 + "Blah".Length @@>
let foo = <@@ fun x -> x + %%bar @@>
22. Quotations: How-To
• Note: You don’t always need
[<ReflectedDefinition>] to use quotations
• Using our previous example, this is equivalent
let addTwo = <@ fun x -> x + 2 @>
23. Computations on Trees
• When working with Quotations, it’s critical
you’re comfortable with trees and recursion
• Tree computations in imperative languages
usually built around the “visitor” pattern
– Visitor class usually holds private, mutable state
– Often implemented as an abstract base class per
tree type, and one virtual method per node type
– Mechanics of the traversal intermixed with
computation happening at nodes
24. Computations on Trees
• Possible to implement a functional alternative
to the “visitor” pattern?
• Knuth did – decades ago!
• Introducing “attribute grammars”
– Initially used as a means of describing how LL/LR
parsers annotate a parse tree
– Later, used to describe how “attributes” are
computed from on tree
25. Computations on Trees
• Attribute grammars are declarative
– They do not define any traversal order
• Attribute grammars allow you to define purely
functional, monadic computations over trees
– For those familiar with Haskell – attribute
evaluation is actually co-monadic
• AGs on ASTs (or Quotations) can be used to
implement purely functional AOP
26. Computations on Trees
• Three main kinds of attributes
• Inherited (L-attributes)
– Attribute “inherited” from parent node
– Compare to the reader workflow
• Synthesized (S-attributes)
– Attribute “synthesized” by a child node, passed
back up to parent node
– Compare to the writer workflow
27. Computations on Trees
• Threaded (SL-attributes)
– Attribute value inherited from parent node,
modified by child, then passed back up to parent
– Combination of Inherited and Synthesized
– Compare to the state workflow
• Attribute values can be int, string, UDTs, etc.
• They can also be tree nodes
– We can use this to define tree transformations
28. Exercises
• Implement a function(s) which, given an Expr:
– Print the equiv. F# source code
– Invert the mathematical operators (+)/(-), (*)/(/)
– Compute the set of methods called by the Expr
●
Bonus: Compute this transitively
– Determine if the Expr can ever raise an exn
– Determine if the Expr is pure (side-effect free)
– Rewrite it so all strings are passed through
String.Intern