How can I DRY out this F# code? (Fluent Interface) - f#

So this is some of the wettest code I've ever written. But it's useful, which is annoying. The reason for all the repetition is because I want to keep the interface fluent. If I augmented the base class (which happens to be View in this case), it would only give back an instance of View, which would prevent me from doing something like
let label = theme.CreateLabel().WithMargin(new Thickness(5.0)).WithText("Hello")
because the Label.Text property is not implemented by the View base class.
So here is my fluent interface. Get ready. It's ugly, and repetitive. But it also works, and is convenient to use.
Have I missed an obvious way to DRY it out?
module ViewExtensions =
let private withTwoWayBinding<'TElement, 'TProperty, 'TViewModel, 'TView when 'TView :> IViewFor<'TViewModel>>(viewModel: 'TViewModel, view: 'TView, viewModelProperty: Expr<'TViewModel -> 'TProperty>, viewProperty: Expr<'TView -> 'TProperty>) (element: 'TElement) =
view.Bind(viewModel, ExpressionConversion.toLinq viewModelProperty, ExpressionConversion.toLinq viewProperty) |> ignore
element
let private withHorizontalOptions<'TElement when 'TElement :> View> options (element: 'TElement) =
element.HorizontalOptions <- options
element
let private withVerticalOptions<'TElement when 'TElement :> View> options (element: 'TElement) =
element.VerticalOptions <- options
element
let private withAlignment<'TElement when 'TElement :> View> horizontalOptions verticalOptions (control: 'TElement) =
control |> withHorizontalOptions horizontalOptions |> withVerticalOptions verticalOptions
let private withMargin<'TElement when 'TElement :> View> margin (element: 'TElement) =
element.Margin <- margin
element
let private withActions<'TElement> (actions: ('TElement -> unit)[]) (element: 'TElement) =
for action in actions do action(element)
element
type Xamarin.Forms.Entry with
member this.WithHorizontalOptions(options) = withHorizontalOptions options this
member this.WithVerticalOptions(options) = withHorizontalOptions options this
member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
member this.WithTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) = withTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) this
member this.WithMargin(margin) = withMargin margin this
member this.With(actions) = withActions actions this
member this.With(action: Entry -> unit) = this.With([|action|])
type Xamarin.Forms.Grid with
member this.WithHorizontalOptions(options) = withHorizontalOptions options this
member this.WithVerticalOptions(options) = withHorizontalOptions options this
member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
member this.WithMargin(margin) = withMargin margin this
member this.With(actions) = withActions actions this
member this.With(action: Grid -> unit) = this.With([|action|])
type Xamarin.Forms.StackLayout with
member this.WithHorizontalOptions(options) = withHorizontalOptions options this
member this.WithVerticalOptions(options) = withHorizontalOptions options this
member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
member this.WithMargin(margin) = withMargin margin this
member this.With(actions) = withActions actions this
member this.With(action: StackLayout -> unit) = this.With([|action|])
type Xamarin.Forms.Button with
member this.WithHorizontalOptions(options) = withHorizontalOptions options this
member this.WithVerticalOptions(options) = withHorizontalOptions options this
member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
member this.WithMargin(margin) = withMargin margin this
member this.WithText(text) = this.Text <- text; this
member this.With(actions) = withActions actions this
member this.With(action: Button -> unit) = this.With([|action|])
type Xamarin.Forms.Switch with
member this.WithHorizontalOptions(options) = withHorizontalOptions options this
member this.WithVerticalOptions(options) = withHorizontalOptions options this
member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
member this.WithTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) = withTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) this
member this.WithMargin(margin) = withMargin margin this
member this.With(actions) = withActions actions this
member this.With(action: Switch -> unit) = this.With([|action|])
type Xamarin.Forms.Label with
member this.WithHorizontalOptions(options) = withHorizontalOptions options this
member this.WithVerticalOptions(options) = withHorizontalOptions options this
member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
member this.WithMargin(margin) = withMargin margin this
member this.WithText(text) = this.Text <- text; this
member this.With(actions) = withActions actions this
member this.With(action: Label -> unit) = this.With([|action|])
UPDATE
So thanks to your help, the answer is yes, I was missing something obvious. As TheQuickBrownFox explained, if I change the fluent interface to something of the form
let label = theme.CreateLabel() |> withMargin(new Thickness(5.0)) |> withContent("Hello")
then the monster you see above can be replaced in its entirety by
module ViewExtensions =
let withTwoWayBinding<'TElement, 'TProperty, 'TViewModel, 'TView when 'TView :> IViewFor<'TViewModel>>(viewModel: 'TViewModel, view: 'TView, viewModelProperty: Expr<'TViewModel -> 'TProperty>, viewProperty: Expr<'TView -> 'TProperty>) (element: 'TElement) =
view.Bind(viewModel, ExpressionConversion.toLinq viewModelProperty, ExpressionConversion.toLinq viewProperty) |> ignore
element
let withHorizontalOptions options (element: #View) = element.HorizontalOptions <- options; element
let withVerticalOptions options (element: #View) = element.VerticalOptions <- options; element
let withAlignment horizontalOptions verticalOptions element = element |> withHorizontalOptions horizontalOptions |> withVerticalOptions verticalOptions
let withMargin margin (element: #View) = element.Margin <- margin; element
let withCaption text (element: #Button) = element.Text <- text; element
let withText text (element: #Entry) = element.Text <- text; element
let withContent text (element: #Label) = element.Text <- text; element
let withSetUpActions<'TElement> (actions: ('TElement -> unit)[]) (element: 'TElement) = (for action in actions do action(element)); element
let withSetUpAction<'TElement> (action: 'TElement -> unit) = withSetUpActions([|action|])
This code deletion is very pleasing indeed.

The idiomatic F# approach to fluent interfaces is just to use the pipe forward operator |>
module ViewHelpers
let withMargin margin element = ...
let withText text element = ...
open ViewHelpers
let label = theme.CreateLabel() |> withMargin (new Thickness(5.0)) |> withText "Hello"
I think you can also shorten your function signatures using flexible types:
let withMargin margin (element: #View) = ...

Related

How is the C# Delegate Action written in F#?

How to implement a C# Action in F#?
I have the following code in C# code-behind:
public MainWindow()
{
InitializeComponent();
ViewModel = new ViewModel();
DataContext = ViewModel;
}
private void ListView_PreviewMouseLeftButtonUp(object _, MouseButtonEventArgs e)
{
_closeAdorner();
// listView here equals object _
var listView = (ListView)e.Source;
var grid = (Grid)listView.Parent;
var selecteditem = (InnerRow)listView.SelectedItem;
ViewModel.Visit = selecteditem;
ViewModel.LastName = selecteditem.LastName;
var adornerLayer = AdornerLayer.GetAdornerLayer(grid);
if (adornerLayer == null)
throw new ArgumentException("datagrid does not have have an adorner layer");
var adorner = new DataGridAnnotationAdorner(grid);
adornerLayer.Add(adorner);
_closeAdorner = () => adornerLayer.Remove(adorner);
}
I am attempting to translate this into F#:
let handlePreviewMouseLeftButtonUp (obj: obj) (a, c) =
let e = (obj :?> MouseButtonEventArgs)
let listView = e.Source :?> ListView // This is the ListView control that was clicked.
let grid = listView.Parent :?> Grid
let selectedItem = c.InnerRows |> List.filter (fun r -> Some r.Id = c.SelectedInnerRow) |> List.head
let adorner = DataGridAdorner(grid)
let installAdorner =
let adornerLayer = AdornerLayer.GetAdornerLayer(grid)
if (adornerLayer.GetAdorners = []) then adornerLayer.Add(adorner) else adornerLayer.Remove(adorner)
The last line:
if (adornerLayer.GetAdorners = []) then adornerLayer.Add(adorner) else adornerLayer.Remove(adorner)
clearly does not compile and is not correct. How is the C# _closeAdorner written to have the same function in F#?
Thank you.
TIA
I am not sure what _closeAdorner, InnerRow, DataGridAdorner and other elements are. Anyway, here I fixed some (not all) of the syntax issues in your code:
let handlePreviewMouseLeftButtonUp(obj : obj) (e:MouseButtonEventArgs) =
let listView = e.Source :?> ListView // This is the ListView control that was clicked.
let grid = listView.Parent :?> Grid
let selectedItem = listView.SelectedItem :?> InnerRow// |> List.filter (fun r -> Some r.Id = c.SelectedInnerRow) |> List.head
let adorner = DataGridAdorner(grid)
let installAdorner =
let adornerLayer = AdornerLayer.GetAdornerLayer(grid)
if adornerLayer.GetAdorners(grid) = [||] then adornerLayer.Add(adorner) else adornerLayer.Remove(adorner)
()
In your constructor you can add the event handler like this:
listView.MouseUp.AddHandler(MouseButtonEventHandler(handlePreviewMouseLeftButtonUp))
You can also remove it:
listView.MouseUp.RemoveHandler(MouseButtonEventHandler(handlePreviewMouseLeftButtonUp))
To create generic Actions you do it like this:
let action = Action<_,_>(handlePreviewMouseLeftButtonUp)
Where the <_,_> corresponds to the number of parameters the action receives.

F# `This expression was expected to have type 'IDictionary<Type,obj>' but here has type 'Dictionary<Type,obj>'

I am trying to convert a C# class into F#:
type Aggregator<'T when 'T : (new : unit -> 'T)>()=
static let ApplyMethod = "Apply"
member val private _aggregations : IDictionary<Type, obj> = new Dictionary<Type, obj>()
member val AggregateType = typeof<'T> with get
member val Alias = Unchecked.defaultof<string> with get
However it seems that even this simple code cannot compile:
Program.fs:1189 This expression was expected to have type
'IDictionary<Type,obj>'
but here has type
'Dictionary<Type,obj>'
Does it mean that a field declared with an interface IDictionary<Type, obj> type cannot infer the value passed knowing that this particular value implements that interface Dictionary<Type, obj>?
Actually if I am explicitely upcasting to IDictionary<Type, obj>:
member val private _aggregations : IDictionary<Type, obj> =
(new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
This works, does it mean that F# is stricter than C# in that regard?
As pointed in the comment, F# does require an explicit:
type Aggregator<'T when 'T : (new : unit -> 'T)>()=
static let ApplyMethod = "Apply"
member val private _aggregations : IDictionary<Type, obj> = new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
member val AggregateType = typeof<'T> with get
member val Alias = Unchecked.defaultof<string> with get
Side notes:
Btw, not everything what you can do in C# is possible in F# (no protected access modifier for example).
Result of the conversion:
type Aggregator<'T when 'T : (new : unit -> 'T) and 'T : not struct> (overrideMethodLookup : IEnumerable<MethodInfo>)=
let aggregations : IDictionary<Type, obj> = (new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
let aggregateType = typeof<'T>
let mutable alias = Unchecked.defaultof<string>
do
alias <- typeof<'T>.Name.ToTableAlias();
overrideMethodLookup.Each(fun (method : MethodInfo) ->
let mutable step = Unchecked.defaultof<obj>
let mutable eventType = method.GetParameters().Single<ParameterInfo>().ParameterType;
if eventType.Closes(typedefof<Event<_>>) then
eventType <- eventType.GetGenericArguments().Single();
step <- typedefof<EventAggregationStep<_,_>>.CloseAndBuildAs<obj>(method, [| typeof<'T>; eventType |]);
else
step <- typedefof<AggregationStep<_,_>>.CloseAndBuildAs<obj>(method, [| typeof<'T>; eventType |]);
aggregations.Add(eventType, step)
) |> ignore
static let ApplyMethod = "Apply"
new() = new Aggregator<'T>(typeof<'T>.GetMethods()
|> Seq.where (fun x -> x.Name = ApplyMethod &&
x.GetParameters().Length = 1))
member this.Add<'TEvent>(aggregation: IAggregation<'T, 'TEvent>) =
if aggregations.ContainsKey(typeof<'TEvent>) then
aggregations.[typeof<'TEvent>] <- aggregation
else
aggregations.Add(typeof<'TEvent>, aggregation)
this
member this.Add<'TEvent>(application: Action<'T, 'TEvent>) =
this.Add(new AggregationStep<'T, 'TEvent>(application));
interface IAggregator<'T> with
member this.AggregatorFor<'TEvent>() =
if aggregations.ContainsKey(typeof<'TEvent>) then
aggregations.[typeof<'TEvent>].As<IAggregation<'T, 'TEvent>>()
else
null
member this.Build(events, session, state) =
events.Each(fun (x : IEvent) -> x.Apply(state, this)) |> ignore
state
member this.Build(events, session) =
(this :> IAggregator<'T>).Build(events, session, new 'T());
member this.EventTypes =
aggregations.Keys.ToArray();
member this.AggregateType =
aggregateType
member this.Alias =
alias
member this.AppliesTo(stream) =
stream.Events.Any(fun x -> aggregations.ContainsKey(x.Data.GetType()));

Assigning Property values using System.Reflection in F#

I have the following lines of code in C#:
internal static object AssignMatchingPropertyValues(object sourceObject, object targetObject)
{
Type sourceType = sourceObject.GetType();
PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var sourcePropertyInfo in sourcePropertyInfos)
{
var targetPropertyInfo = targetObject.GetType().GetProperty(sourcePropertyInfo.Name);
if (targetPropertyInfo != null)
{
targetPropertyInfo.SetValue(targetObject, sourcePropertyInfo.GetValue(sourceObject, null), null);
}
}
return targetObject;
}
I want to implement a functional equivalent in F# so I did something like this:
member this.AssignMatchingPropertyValues(sourceObject, targetObject)=
let sourceType = sourceObject.GetType()
let sourcePropertyInfos = sourceType.GetProperties(BindingFlags.Instance)
let assignedProperities = sourcePropertyInfos
|> Seq.map(fun spi -> spi, targetObject.GetType().GetProperty(spi.Name))
|> Seq.map(fun (spi,tpi) -> tpi.SetValue(targetObject, spi.GetValue(sourceObject,null),null))
()
The problem is that it does not work. I think b/c of immutability, I am getting a new collection. Is there a way to ref the original collection? Is this the right path in tackling this problem?
Here is a direct translation of your C#, which your F# code is not:
let AssignMatchingPropertyValues sourceObject targetObject =
let sourceType = sourceObject.GetType()
let targetType = targetObject.GetType()
let sourcePropertyInfos = sourceType.GetProperties(BindingFlags.Public ||| BindingFlags.Instance)
for sourcePropertyInfo in sourcePropertyInfos do
match targetType.GetProperty(sourcePropertyInfo.Name) with
| null -> ()
| targetPropertyInfo -> targetPropertyInfo.SetValue(targetObject, sourcePropertyInfo.GetValue(sourceObject, null), null)
targetObject
Seq.map is lazy and you aren't evaluating it anywhere. You can use Seq.iter:
sourcePropertyInfos
|> Seq.map(fun spi -> spi, targetObject.GetType().GetProperty(spi.Name))
|> Seq.iter(fun (spi,tpi) -> tpi.SetValue(targetObject, spi.GetValue(sourceObject,null),null))

Access lists defined in constructor on override onPaint method F#

I want to override the onPaint method to make it draw the objects in two lists defined in the constructor, problem being I can't access the lists from the overrided onPaint method, I get the error saying the list or constructor is not defined when trying to use listOfSquares or listOfCircles. So basically, how do I access these lists from that override?
type MainForm = class
inherit Form
val mutable g : Graphics // mutable means its not read-only
val mutable position : Point // position of the rectangle
new () as form = {g=null;position = new Point(0,0)} then
// double buffering
form.SetStyle (ControlStyles.UserPaint, true);
form.SetStyle (ControlStyles.DoubleBuffer, true);
form.SetStyle (ControlStyles.AllPaintingInWmPaint, true);
form.Width <- 900
form.Height <- 500
form.BackColor <- Color.White
form.Text <- "2D Graphics Editor";
let listOfSquares = ResizeArray()
let listOfCircles = ResizeArray()
let menu = new MenuStrip()
let file = new ToolStripDropDownButton("File") // Menu
ignore(menu.Items.Add(file))
let create = new ToolStripDropDownButton("Create") // Menu
ignore(menu.Items.Add(create))
let square = create.DropDownItems.Add("Square")
let circle = create.DropDownItems.Add("Circle")
let newFile = file.DropDownItems.Add("New file")
let saveFile = file.DropDownItems.Add("Save file")
let openFile = file.DropDownItems.Add("Open file")
square.Click.Add(fun _ -> listOfSquares.Add(new square(5.0, 5.0)) |> ignore)
circle.Click.Add(fun _ -> listOfCircles.Add(new circle(10.0, 10.0)) |> ignore)
newFile.Click.Add(fun _ -> MessageBox.Show("newFile") |> ignore)
saveFile.Click.Add(fun _ -> MessageBox.Show("saveFile") |> ignore)
openFile.Click.Add(fun _ -> MessageBox.Show("openFile") |> ignore)
let dc c = (c :> Control)
form.Controls.AddRange([|dc menu|]);
// show the form
form.Show()
// override of paint event handler
override form.OnPaint e =
let g = e.Graphics in
// draw objects in listOfSquares and listOfCircles
end
If you did want to use a primary constructor then you could do it like this, using let bindings for all your private fields and do bindings for the constructor's code. The let bindings are accessible to all non-static members.
See the F# documentation on classes to read about this syntax.
type MainForm() as form =
inherit Form()
let mutable g : Graphics = null
let mutable position : Point = Point(0,0)
let listOfSquares = ResizeArray()
let listOfCircles = ResizeArray()
do
form.SetStyle (ControlStyles.UserPaint, true);
// ... your other initialization code
// show the form
form.Show()
override form.OnPaint e =
let g = e.Graphics
// draw objects in listOfSquares and listOfCircles
You defined their scope as being the constructor rather than the object. Move their declarations up to where position and g are defined.
I think this satisfies your requirements:
type test =
val mutable private temp:int
new() as this = {temp=5} then
this.temp <- 6
The important bits are the private access modifier, the assignment of the private field in the secondary constructor using the {..} syntax and the use of this to access private members.
Here is your code rewritten to properly initialize your lists:
type MainForm =
inherit Form
val mutable g : Graphics // mutable means its not read-only
val mutable position : Point // position of the rectangle
val listOfSquares : ResizeArray
val listOfCircles : ResizeArray
new () as form = {g=null;position = new Point(0,0)} then
// double buffering
form.SetStyle (ControlStyles.UserPaint, true);
form.SetStyle (ControlStyles.DoubleBuffer, true);
form.SetStyle (ControlStyles.AllPaintingInWmPaint, true);
form.Width <- 900
form.Height <- 500
form.BackColor <- Color.White
form.Text <- "2D Graphics Editor";
listOfSquares <- ResizeArray()
listOfCircles <- ResizeArray()
let menu = new MenuStrip()
let file = new ToolStripDropDownButton("File") // Menu
ignore(menu.Items.Add(file))
let create = new ToolStripDropDownButton("Create") // Menu
ignore(menu.Items.Add(create))
let square = create.DropDownItems.Add("Square")
let circle = create.DropDownItems.Add("Circle")
let newFile = file.DropDownItems.Add("New file")
let saveFile = file.DropDownItems.Add("Save file")
let openFile = file.DropDownItems.Add("Open file")
square.Click.Add(fun _ -> listOfSquares.Add(new square(5.0, 5.0)) |> ignore)
circle.Click.Add(fun _ -> listOfCircles.Add(new circle(10.0, 10.0)) |> ignore)
newFile.Click.Add(fun _ -> MessageBox.Show("newFile") |> ignore)
saveFile.Click.Add(fun _ -> MessageBox.Show("saveFile") |> ignore)
openFile.Click.Add(fun _ -> MessageBox.Show("openFile") |> ignore)
let dc c = (c :> Control)
form.Controls.AddRange([|dc menu|]);
// show the form
form.Show()
// override of paint event handler
override form.OnPaint e =
let g = e.Graphics in
// draw objects in listOfSquares and listOfCircles
end
As #leafgarland demonstrated, if you don't need to use a secondary constructor, then use the primary constructor for much cleaner syntax.
type test() =
let mutable temp = 6
...
override form.OnPaint e =
let g = e.Graphics
printfn "%i" temp

Adding stuff to a list in f#

I'm doing a project called "2D Shape editor" in f#. I have done this project in c# before so I've got all the logics for how to connect two shapes. So I know that i will need a list to hold all theese shapes that I will be adding. But I simply can't get my addToList method to work.
My ShapeList:
let mutable ShapeList:List<RectangleZ> = [RectangleZ(100,100)]
My add methods:
let addToList (listan:List<RectangleZ>) (element:RectangleZ) = let ShapeList = ShapeList#[element] in ShapeList
//Method to add into the ShapeList
let addToList (listan:List<RectangleZ>) (element:RectangleZ) = element::ShapeList
//Other try on adding into shapeList
the button that should be adding rectangles to the ShapeList:
btn.Click.Add(fun _ -> new RectangleZ(500, 100) |> addToList ShapeList |>ignore |> saver)
//Button click method that should be adding the RectangleZ(500, 100) to my ShapeList
And ofcourse my rectangle:
type RectangleZ(x:int, y:int)=
let mutable thisx = x
let mutable thisy = y
let mutable thiswidth = 50
let mutable thisheight = 20
let brush = new SolidBrush(Color.Black)
member obj.x with get () = thisx and set x = thisx <- x
member obj.y with get () = thisy and set y = thisy <- y
member obj.width with get () = thiswidth and set width = thiswidth <- width
member obj.height with get () = thisheight and set height = thisheight <- height
member obj.thisColor = Color.FromArgb(167, 198, 253)
member obj.draw(paper:Graphics) = paper.FillRectangle(brush, thisx, thisy, 50, 20)
member obj.ShapeType = "Rectangle"
The element dosn't get added into the list for some reason in neither of my addToList functions. My Question is why?
List in F# are immutable. This means that when you add item to list like this:
let newlist = elem :: tail;;
old list (tail) doesn't changes, instead of that new list created. So, you need to return new list from your addToList function and than update mutable variable:
let addToList (listan:List<RectangleZ>) (element:RectangleZ) = element::listan
ShapeList <- addToList ShapeList newElement
In your code let ShapeList is local and doesn't affect global ShapeList variable.
let newList = oldList # [newElement]
You can use List.append with mutable lists, the below example worked fine with me:
let mutable season_averages = []
for i in 0 .. n_seasons do
season_averages <- [i] |> List.append season_averages
printfn "Seasons Average: %A" season_averages

Resources