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.
Related
Can someone tell me how I can update a subitem in a nested record?
I want to set isSelected to true for the Item with value = "B"
type MyItem = {isSelected:bool; value:string}
type MyModel = {list:MyItem list}
let a = {isSelected = false; value = "A"}
let b = {isSelected = false; value = "B"}
let c = {isSelected = false; value = "C"}
let m = {list = [a;b;c]}
let m2 = { m with list = { m.list with ??? = { ??? }}}
I will not use mutable data structures.
Immutability is great but when dealing with nested immutable structures it can get a bit hairy. Especially if it's deeply nested.
One way to deal with this is so called Lenses.
So I increased the nesting level of the example a bit so that the value of lenses are more visible.
module Lenses =
// This lens is a pair of function, a getter that get's inner value of an object
// and a setter that sets the inner value of an object
// The cool thing is that a lens is composable meaning we can create a lens
// that allows us to get and set a deeply nested property succinctly
type Lens<'O, 'I> = L of ('O -> 'I)*('I -> 'O -> 'O)
let lens (g : 'O -> 'I) (s : 'I -> 'O -> 'O) = L (g, s)
// Gets an inner value
let get (L (g, _)) o = g o
// Sets an inner value
let set (L (_, s)) i o = s i o
// Updates an inner value given an updater function that sees the
// inner value and returns a new value
let over (L (g, s)) u o = s (u (g o)) o
// Compose two lenses into one, allows for navigation into deeply nested structures
let compose (L (og, os)) (L (ig, is)) =
let g o = ig (og o)
let s i o = os (is i (og o)) o
L (g, s)
type Lens<'O, 'I> with
static member (-->) (o, i) = compose o i
open Lenses
// I made the model a bit more complex to show benefit of lenses
type MySelection =
{
isSelected: bool
}
// Define a lens that updates the property, this code can potentially be generated
// Scala does this with macros, in F# there are other possibilities
static member isSelectedL : Lens<MySelection, bool> = lens (fun o -> o.isSelected) (fun i o -> { o with isSelected = i })
type MyValue =
{
value: string
}
static member valueL : Lens<MyValue, string> = lens (fun o -> o.value) (fun i o -> { o with value = i })
type MyItem =
{
selection : MySelection
value : MyValue
}
static member selectionL : Lens<MyItem, MySelection> = lens (fun o -> o.selection) (fun i o -> { o with selection = i })
static member valueL : Lens<MyItem, MyValue> = lens (fun o -> o.value ) (fun i o -> { o with value = i })
type MyModel =
{
list: MyItem list
}
static member listL : Lens<MyModel, MyItem list> = lens (fun o -> o.list) (fun i o -> { o with list = i })
[<EntryPoint>]
let main argv =
// Define example model
let a = {selection = {isSelected = false}; value = {value = "A"}}
let b = {selection = {isSelected = false}; value = {value = "B"}}
let c = {selection = {isSelected = false}; value = {value = "C"}}
let m = {list = [a;b;c]}
// Print it
printfn "%A" m
// Update the model
let m2 =
let mapper (v : MyItem) =
// Grabs the nest value using lens composition
let nestedValue = v |> get (MyItem.valueL --> MyValue.valueL)
let isSelected = nestedValue = "B"
// Set the nested isSelected using lens composition
v |> set (MyItem.selectionL --> MySelection.isSelectedL) isSelected
// Maps nested list property
m |> over MyModel.listL (List.map mapper)
printfn "%A" m2
0
Use List.map:
let m2 =
{ m with list =
List.map (fun item ->
if item.value = "B" then
{ item with isSelected = true }
else
item)
m.list
}
This will create a new list where every item is the same as before, except the one we want to "update" because we replace that with a new item where isSelected is true.
I attempted to port a working C# sample to an OOP version of F#.
Remote actors (on a separate process) are not receiving messages.
I receive the following error:
[ERROR][3/23/2017 4:39:10 PM][Thread 0008][[akka://system2/system/endpointManage
r/reliableEndpointWriter-akka.tcp%3A%2F%2Fsystem1%40localhost%3A8090-1/endpointW
riter#1919547364]] AssociationError [akka.tcp://system2#localhost:8080] <- akka.
tcp://system1#localhost:8090: Error [Object reference not set to an instance of
an object.] [ at Akka.Serialization.Serialization.FindSerializerForType(Type o
bjectType)
at Akka.Remote.Serialization.DaemonMsgCreateSerializer.GetArgs(DaemonMsgCreat
eData proto)
at Akka.Remote.Serialization.DaemonMsgCreateSerializer.FromBinary(Byte[] byte
s, Type type)
at Akka.Serialization.Serialization.Deserialize(Byte[] bytes, Int32 serialize
rId, String manifest)
Here's the working C# version:
using (var system = ActorSystem.Create("system1", config))
{
var reply = system.ActorOf<ReplyActor>("reply");
//create a remote deployed actor
var remote1 = system.ActorOf(Props.Create(() => new SomeActor()).WithRouter(FromConfig.Instance), "remoteactor1");
var remote2 = system.ActorOf(Props.Create(() => new SomeActor()).WithRouter(FromConfig.Instance), "remoteactor2");
var remote3 = system.ActorOf(Props.Create(() => new SomeActor()).WithRouter(FromConfig.Instance), "remoteactor3");
var hashGroup = system.ActorOf(Props.Empty.WithRouter(new ConsistentHashingGroup(config)));
Task.Delay(500).Wait();
var routee1 = Routee.FromActorRef(remote1);
hashGroup.Tell(new AddRoutee(routee1));
var routee2 = Routee.FromActorRef(remote2);
hashGroup.Tell(new AddRoutee(routee2));
var routee3 = Routee.FromActorRef(remote3);
hashGroup.Tell(new AddRoutee(routee3));
Task.Delay(500).Wait();
for (var i = 0; i < 5; i++)
{
for (var j = 0; j < 7; j++)
{
var message = new SomeMessage(j, $"remote message: {j}");
hashGroup.Tell(message, reply);
}
}
Console.ReadLine();
}
Here's the port to F# using OOP:
use system = ActorSystem.Create("system1", config)
let reply = system.ActorOf<ReplyActor>("reply")
let props1 = Props.Create(fun () -> SomeActor() :> obj)
let props2 = Props.Create(fun () -> SomeActor() :> obj)
let props3 = Props.Create(fun () -> SomeActor() :> obj)
let remote1 = system.ActorOf(props1.WithRouter(FromConfig.Instance), "remoteactor1")
let remote2 = system.ActorOf(props2.WithRouter(FromConfig.Instance), "remoteactor2")
let remote3 = system.ActorOf(props3.WithRouter(FromConfig.Instance), "remoteactor3")
let hashGroup = system.ActorOf(Props.Empty.WithRouter(ConsistentHashingGroup(config)))
Task.Delay(500).Wait();
let routee1 = Routee.FromActorRef(remote1);
hashGroup.Tell(new AddRoutee(routee1));
let routee2 = Routee.FromActorRef(remote2);
hashGroup.Tell(new AddRoutee(routee2));
let routee3 = Routee.FromActorRef(remote3);
hashGroup.Tell(new AddRoutee(routee3));
Task.Delay(500).Wait();
for i = 0 to 5 do
for j = 0 to 7 do
let message = new HashMessage(j, sprintf "remote message: %i" j);
hashGroup.Tell(message, reply);
Console.ReadLine() |> ignore
Question:
Am I suppose to upcast SomeActor to the object type when invoking the Props.Create method?
let props1 = Props.Create(fun () -> SomeActor() :> obj)
let props2 = Props.Create(fun () -> SomeActor() :> obj)
let props3 = Props.Create(fun () -> SomeActor() :> obj)
The code above is the only difference that I'm aware of.
The only other difference is the tcp path.
C#'s TCP:
remote {
dot-netty.tcp {
port = 8090
hostname = localhost
}
F#'s TCP:
remote {
helios.tcp {
port = 8090
hostname = localhost
}
Props object is a descriptor for the creation process of target actor. Moreover, it must be serializable, as sometimes it may get included on messages passed through the network.
In order to work this way Props internally describes actor construction in form of (actor-type, actor-constructor-arguments). Props.Create(() => new Actor()) is only a helper here: what it actually does, is deconstruct the constructor expression into type info with arguments. This is why it works only with new Actor() expressions.
The problem with your F# code is that you're defining actor creation as a F# function, which props deconstructor doesn't know how to handle. You may still want create your actors using:
Props.Create(typeof<Actor>, [| arg1; arg2 |])
but then you need to keep the correctness of the constructor params by yourself. You can also use i.e. Akkling with it's typed version of props.
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))
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
I want to update a nested, immutable data structure (I attached a small example of a hypothetical game.) And I wonder whether this can be done a little more elegantly.
Every time something inside the dungeon changes we need a new dungeon. So, I gave it a general update member. The best way to use this, that I could come up with for the general case, is to specify the processing functions for each nesting and than pass the combined function to the update member.
Then, for really common cases (like applying a map to all the monsters on a specific level), I provide extra members (Dungeon.MapMonstersOnLevel).
The whole thing works, I would just like to know, if anyone can think of better ways of doing it.
Thanks!
// types
type Monster(awake : bool) =
member this.Awake = awake
type Room(locked : bool, monsters : Monster list) =
member this.Locked = locked
member this.Monsters = monsters
type Level(illumination : int, rooms : Room list) =
member this.Illumination = illumination
member this.Rooms = rooms
type Dungeon(levels : Level list) =
member this.Levels = levels
member this.Update levelFunc =
new Dungeon(this.Levels |> levelFunc)
member this.MapMonstersOnLevel (f : Monster -> Monster) nLevel =
let monsterFunc = List.map f
let roomFunc = List.map (fun (room : Room) -> new Room(room.Locked, room.Monsters |> monsterFunc))
let levelFunc = List.mapi (fun i (level : Level) -> if i = nLevel then new Level(level.Illumination, level.Rooms |> roomFunc) else level)
new Dungeon(this.Levels |> levelFunc)
member this.Print() =
this.Levels
|> List.iteri (fun i e ->
printfn "Level %d: Illumination %d" i e.Illumination
e.Rooms |> List.iteri (fun i e ->
let state = if e.Locked then "locked" else "unlocked"
printfn " Room %d is %s" i state
e.Monsters |> List.iteri (fun i e ->
let state = if e.Awake then "awake" else "asleep"
printfn " Monster %d is %s" i state)))
// generate test dungeon
let m1 = new Monster(true)
let m2 = new Monster(false)
let m3 = new Monster(true)
let m4 = new Monster(false)
let m5 = new Monster(true)
let m6 = new Monster(false)
let m7 = new Monster(true)
let m8 = new Monster(false)
let r1 = new Room(true, [ m1; m2 ])
let r2 = new Room(false, [ m3; m4 ])
let r3 = new Room(true, [ m5; m6 ])
let r4 = new Room(false, [ m7; m8 ])
let l1 = new Level(100, [ r1; r2 ])
let l2 = new Level(50, [ r3; r4 ])
let dungeon = new Dungeon([ l1; l2 ])
dungeon.Print()
// toggle wake status of all monsters
let dungeon1 = dungeon.MapMonstersOnLevel (fun m -> new Monster(not m.Awake)) 0
dungeon1.Print()
// remove monsters that are asleep which are in locked rooms on levels where illumination < 100 and unlock those rooms
let monsterFunc2 = List.filter (fun (monster : Monster) -> monster.Awake)
let roomFunc2 = List.map(fun (room : Room) -> if room.Locked then new Room(false, room.Monsters |> monsterFunc2) else room)
let levelFunc2 = List.map(fun (level : Level) -> if level.Illumination < 100 then new Level(level.Illumination, level.Rooms |> roomFunc2) else level)
let dungeon2 = dungeon.Update levelFunc2
dungeon2.Print()
Here's the same code using lenses as currently defined in FSharpx.
As other answers note, it's convenient to use records here; they give you structural equality for free among other things.
I also attach the corresponding lenses for the properties as static members; you can also define them in a module or as loose functions. I prefer static members here, for practical purposes it's just like a module.
open FSharpx
type Monster = {
Awake: bool
} with
static member awake =
{ Get = fun (x: Monster) -> x.Awake
Set = fun v (x: Monster) -> { x with Awake = v } }
type Room = {
Locked: bool
Monsters: Monster list
} with
static member locked =
{ Get = fun (x: Room) -> x.Locked
Set = fun v (x: Room) -> { x with Locked = v } }
static member monsters =
{ Get = fun (x: Room) -> x.Monsters
Set = fun v (x: Room) -> { x with Monsters = v } }
type Level = {
Illumination: int
Rooms: Room list
} with
static member illumination =
{ Get = fun (x: Level) -> x.Illumination
Set = fun v (x: Level) -> { x with Illumination = v } }
static member rooms =
{ Get = fun (x: Level) -> x.Rooms
Set = fun v (x: Level) -> { x with Rooms = v } }
type Dungeon = {
Levels: Level list
} with
static member levels =
{ Get = fun (x: Dungeon) -> x.Levels
Set = fun v (x: Dungeon) -> { x with Levels = v } }
static member print (d: Dungeon) =
d.Levels
|> List.iteri (fun i e ->
printfn "Level %d: Illumination %d" i e.Illumination
e.Rooms |> List.iteri (fun i e ->
let state = if e.Locked then "locked" else "unlocked"
printfn " Room %d is %s" i state
e.Monsters |> List.iteri (fun i e ->
let state = if e.Awake then "awake" else "asleep"
printfn " Monster %d is %s" i state)))
I also define print as a static member; again it's like a function in a module, and it's more composable than an instance method (though I won't compose it here).
Now to generate the sample data. I think { Monster.Awake = true } is more desciptive than new Monster(true). If you wanted to use classes I'd name the parameter explicitly, e.g. Monster(awake: true)
// generate test dungeon
let m1 = { Monster.Awake = true }
let m2 = { Monster.Awake = false }
let m3 = { Monster.Awake = true }
let m4 = { Monster.Awake = false }
let m5 = { Monster.Awake = true }
let m6 = { Monster.Awake = false }
let m7 = { Monster.Awake = true }
let m8 = { Monster.Awake = false }
let r1 = { Room.Locked = true; Monsters = [m1; m2] }
let r2 = { Room.Locked = false; Monsters = [m3; m4] }
let r3 = { Room.Locked = true; Monsters = [m5; m6] }
let r4 = { Room.Locked = false; Monsters = [m7; m8] }
let l1 = { Level.Illumination = 100; Rooms = [r1; r2] }
let l2 = { Level.Illumination = 50; Rooms = [r3; r4] }
let dungeon = { Dungeon.Levels = [l1; l2] }
Dungeon.print dungeon
Now comes the fun part: composing lenses to update the monsters for all rooms for a particular level in a dungeon:
open FSharpx.Lens.Operators
let mapMonstersOnLevel nLevel f =
Dungeon.levels >>| Lens.forList nLevel >>| Level.rooms >>| Lens.listMap Room.monsters
|> Lens.update (f |> List.map |> List.map)
// toggle wake status of all monsters
let dungeon1 = dungeon |> mapMonstersOnLevel 0 (Monster.awake.Update not)
Dungeon.print dungeon1
For the second dungeon I also use lenses but without lens composition. It's sort of a DSL defined by small composed functions (some of the functions are from lenses). Maybe there are lenses to express this more concisely, but I haven't figured it out.
// remove monsters that are asleep
// which are in locked rooms on levels where illumination < 100
// and unlock those rooms
let unlock = Room.locked.Set false
let removeAsleepMonsters = Room.monsters.Update (List.filter Monster.awake.Get)
let removeAsleepMonsters_unlock_rooms = List.mapIf Room.locked.Get (unlock >> removeAsleepMonsters)
let isLowIllumination = Level.illumination.Get >> ((>)100)
let removeAsleepMonsters_unlock_level = Level.rooms.Update removeAsleepMonsters_unlock_rooms
let removeAsleepMonsters_unlock_levels = List.mapIf isLowIllumination removeAsleepMonsters_unlock_level
let dungeon2 = dungeon |> Dungeon.levels.Update removeAsleepMonsters_unlock_levels
Dungeon.print dungeon2
I overused lenses and pointfree a bit here, partially on purpose, just to show what it could look like. Some won't like it, claiming it's not idiomatic or clear. Maybe so, but it's another tool that you can choose to use or not, depending on your context.
But more importantly, because Update is a Get followed by a function followed by a Set, this isn't as efficient as your code when it comes to processing lists: an Update in Lens.forList first gets the nth element in the list, which is an O(n) operation.
To summarize:
Pros:
Very concise.
Enables pointfree style.
Code involving lenses is generally oblivious of the source type representation (it can be a class, a record, a single-case DU, a dictionary, it doesn't matter).
Cons:
May be inefficient for some cases in current implementation.
Due to lack of macros, requires some boilerplate.
Thanks for this example, as a result I'll be revising the current design of lenses in FSharpx and see if it can be optimized.
I committed this code to the FSharpx repository: https://github.com/fsharp/fsharpx/commit/136c763e3529abbf91ad52b8127ce11cbb3dff28
I asked a similar question, but about haskell: Is there a Haskell idiom for updating a nested data structure?
The excellent answers mentioned a concept known as functional lenses.
Unfortunately, I don't know what the package is, or if it even exists, for F#.
Update: two knowledgeable F#-ists (F#-ers? F#as?) left useful links about this in comments, so I'll post them here:
#TomasPetricek suggested FSharpX and this website describing it
#RyanRiley gave the link for the package
It's awesome that these two guys took the time to read my answer, comment and improve it, as they're both developers of FSharpX!
More extraneous information: I was motivated to figure out how to do this by Clojure's assoc-in and update-in functions, which proved to me that it is possible in functional languages! Of course, Clojure's dynamic typing makes it simpler than in Haskell/F#. Haskell's solution involves templating, I believe.
I posted a similar question about Scala about a year back. The answers mention three concepts as a solution to this problem: Zippers, Tree rewriting, and Lenses.
I don't know why you want to use classes here. I think you can leverage the power of pattern matching if you use records for holding data and keeping them minimal:
// Types
type Monster = {
Awake: bool
}
with override x.ToString() =
if x.Awake then "awake" else "asleep"
type Room = {
Locked: bool;
Monsters: Monster list
}
with override x.ToString() =
let state = if x.Locked then "locked" else "unlocked"
state + "\n" + (x.Monsters |> List.mapi (fun i m -> sprintf " Monster %d is %s" i (string m)) |> String.concat "\n")
type Level = {
Illumination : int;
Rooms : Room list
}
with override x.ToString() =
(string x.Illumination) + "\n" + (x.Rooms |> List.mapi (fun i r -> sprintf " Room %d is %s" i (string r)) |> String.concat "\n")
type Dungeon = {
Levels: Level list;
}
with override x.ToString() =
x.Levels |> List.mapi (fun i l -> sprintf "Level %d: Illumination %s" i (string l)) |> String.concat "\n"
To me, putting functions for manipulating Dungeon inside the class is unnatural. The code looks better if you put them in a module and make use of above declarations:
/// Utility functions
let updateMonster (m: Monster) a =
{m with Awake = a}
let updateRoom (r: Room) l monstersFunc =
{ Locked = l;
Monsters = r.Monsters |> monstersFunc}
let updateLevel (l: Level) il roomsFunc =
{Illumination = il; Rooms = l.Rooms |> roomsFunc}
let updateDungeon (d: Dungeon) levelsFunc =
{d with Levels = d.Levels |> levelsFunc}
/// Update functions
let mapMonstersOnLevel (d: Dungeon) nLevel =
let monstersFunc = List.map (fun m -> updateMonster m (not m.Awake))
let roomsFunc = List.map (fun r -> updateRoom r r.Locked monstersFunc)
let levelsFunc = List.mapi (fun i l -> if i = nLevel then updateLevel l l.Illumination roomsFunc else l)
updateDungeon d levelsFunc
let removeSleptMonsters (d: Dungeon) =
let monstersFunc = List.filter (fun m -> m.Awake)
let roomsFunc = List.map (fun r -> if r.Locked then updateRoom r false monstersFunc else r)
let levelsFunc = List.map (fun l -> if l.Illumination < 100 then updateLevel l l.Illumination roomsFunc else l)
updateDungeon d levelsFunc
Then you can see manipulating these nested data structures is much easier. However, above functions still have redundancy. You can refactor more if you use lenses which come very natural with records. Check out the insightful article by Mauricio Scheffer, which is really close to this formulation.
I've implemented a lens library in C# via reflection. The core of the library is
this function
/// <summary>
/// Perform an immutable persistent set on a sub
/// property of the object. The object is not
/// mutated rather a copy of the object with
/// the required change is returned.
/// </summary>
/// <typeparam name="ConvertedTo">type of the target object</typeparam>
/// <typeparam name="V">type of the value to be set</typeparam>
/// <param name="This">the target object</param>
/// <param name="names">the list of property names composing the property path</param>
/// <param name="value">the value to assign to the property</param>
/// <returns>A new object with the required change implemented</returns>
private static T Set<T, V>
(this T This, List<string> names, V value)
where T : class, Immutable
{
var name = names.First();
var rest = names.Skip(1).ToList();
if (names.Count == 1)
{
var copy = This.ShallowClone();
copy.SetPrivatePropertyValue(names.First(), value);
return copy as T;
}
else
{
var copy = This.ShallowClone();
var subtree = copy
.GetPrivatePropertyValue<Immutable>(name)
.Set(rest, value);
copy.SetPrivatePropertyValue(names.First(), subtree);
return copy as T;
}
}
The above function is composed using helper library into various utilities,
one of which is an undo stack based on immutable persistent records. There
is an overload of this function
public static Maybe<T> MaybeSet<T,V>
(this T This, Expression<Func<T, V>> prop, V value)
where T : class, Immutable
{
if (!EqualityComparer<V>.Default.Equals(This.Get(prop.Compile()),value))
{
var names = ReactiveUI.Reflection.ExpressionToPropertyNames(prop).ToList();
return This.Set(names, value).ToMaybe();
}
else
{
return None<T>.Default;
}
}
which allows more natural type safe notation using LINQ expressions.
foo = foo.Set(f=>f.A.B.C, 10);
There is a lot of reflection going on in the library but the reduction
in boilerplate is worth the performance hit. See the spec. I only need to
tag the record as Immutable to get it to work. I don't have to
provide getters and settors.
class A : Immutable
{
public int P { get; private set; }
public B B { get; private set; }
public A(int p, B b)
{
P = p;
B = b;
}
}
class B : Immutable
{
public int P { get; private set; }
public C C { get; private set; }
public B(int p, C c)
{
P = p;
C = c;
}
}
class C : Immutable
{
public int P { get; private set; }
public C(int p)
{
P = p;
}
}
namespace Utils.Spec
{
public class ImmutableObjectPatternSpec : IEnableLogger
{
[Fact]
public void SetterSpec()
{
var a = new A
( p:10
, b: new B
( p: 20
, c : new C(30)));
var a_ = a.Set(p => p.B.C.P, 10);
a.Should().NotBe(a_);
a.B.C.P.Should().Be(30);
a_.B.C.P.Should().Be(10);
}
[Fact]
public void StringListGettersShouldWork()
{
var a = new A
( p:10
, b: new B
( p: 20
, c : new C(30)));
var a_ = a.Set(p => p.B.C.P, 10);
a_.Get(p=>p.B.C.P).Should().Be(10);
}
}
}
Perhaps reflection based lenses would reduce boiler plate in F#. Maybe
performance could be improved with caching of the accessors or maybe
IL generation.