Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# – mapping a list with an accumulator

Tags:

f#

I am new to F# and functional programming in general. Given a scenario where you want to iterate over a sequence or list of strings, and map that to a new list of a different type, WITH an accumulator, what is the correct functional approach? I can achieve this in F# using mutable variables, but I am struggling to find the right function to use for this. It's similar to map I think, but there is the notion of state.

In other words, I want to transform a list of strings into a list of win forms radio buttons, but for each new button I want to add 20 to the previous y coordinate. Something like:

new RadioButton(Text=str,Location=new Point(20,y+20),Width=350)
like image 970
user3410575 Avatar asked Mar 12 '14 12:03

user3410575


3 Answers

You can use List.fold:

open System.Drawing
open System.Windows.Forms

let getButtons () =
    let strings = ["a"; "b"; "c"]
    let (_, pointsRev) = List.fold (fun (offset, l) s -> (offset+20, (new RadioButton(Text=s, Location = new Point(20, offset), Width = 350))::l)) (0, []) strings
    pointsRev |> List.rev

The state is a pair containing the current offset and the current output list. The output list is built in reverse order so has to be reversed at the end.

You could also use Seq.map2:

let points = Seq.map2 (fun offset s -> new RadioButton(Text=s, Location = new Point(20, offset)) (Seq.initInfinite ((*)20)) strings |> List.ofSeq
like image 83
Lee Avatar answered Oct 20 '22 20:10

Lee


You can access and change variable by reference alike

let x = ref 0
x := !x + 5
new Point(20,!x+20)

and you can use such variable inside closures.

Also you can use mapi : http://msdn.microsoft.com/en-us/library/ee353425.aspx

And add value to y based on i alike new Point(20,i*20+20)

like image 39
cnd Avatar answered Oct 20 '22 19:10

cnd


Using List.fold is a great idea (see the accepted answer).

Being an F# beginner myself, I split the fold out into a separate function and renamed some variables so I could understand things more clearly. This seems to work:

let buttonNames = ["Button1Name"; "Button2Name"]

let createRadioButton (offset, radioButtons) name = 
    let newRadioButton = new RadioButton(Text=name, Location=new Point(20, offset), Width=350)
    (offset + 20, newRadioButton::radioButtons)

let (_, buttonsReversed) = buttonNames |> List.fold createRadioButton (0, []) 

let buttons = buttonsReversed |> List.rev
like image 22
Mark Bell Avatar answered Oct 20 '22 19:10

Mark Bell