4

I created a list in f# named tickets that contains 10 records called Ticket.The records are all initialized with their specific seat number and empty customer name.

type Ticket = {seat:int; customer:string}  
let mutable tickets = [for n in 1..10 -> {Ticket.seat = n; Ticket.customer = ""}]

I want to write a function to book a specific seat in the list(add a customer name to the seat). How can i edit an item in the list and have other items still retain their value

John Palmer
  • 25,356
  • 3
  • 48
  • 67
Charles uuu
  • 51
  • 1
  • 5
  • What particular part are you stuck on? – John Palmer Apr 30 '15 at 11:18
  • @JohnPalmer I read somewhere that in f# i have to create a new list if i want to change an Item in the list. This is what i came up with. But the new list now has only one Item. let bookTicket()= Console.WriteLine("Enter seat number: ") let seatNo = int(Console.ReadLine()) Console.WriteLine("Enter customer name: ") let customerName = string(Console.ReadLine()) tickets <- [{Ticket.seat = seatNo; Ticket.customer = customerName}] – Charles uuu Apr 30 '15 at 11:31
  • @CharlesUko and this is true, in this case (if you really want to mutate things) I would just use `System.Collection.Generic.List<'t>` as you might be used to from C# - you can then get the old ticket with `let oldTicket = list.[itemNr]`, create a new record with `let newTicket = { oldTicket with customer = "newCustomerName" }` and put it back into the list with `list.[itemNr] <- newTicket` – Random Dev Apr 30 '15 at 11:32
  • maybe you even want to use an `Dictionary` (or you could try to work out the problem using F#'s `Map<'k,'v>`) – Random Dev Apr 30 '15 at 11:34
  • 1
    This is close to a duplicate of this question:http://stackoverflow.com/questions/11455367/is-replacing-a-list-element-an-anti-pattern – John Palmer Apr 30 '15 at 11:55
  • 2
    @JohnPalmer good point - I think a (functional) list is the wrong data-representation here anyway – Random Dev Apr 30 '15 at 11:59

3 Answers3

5

The functional F# list type is immutable, which means that you cannot change it. The typical way of working with it would be to return a new, modified, copy.

To do that, you can write a function bookSeat that takes list<Ticket> together with number & new name and produces a new list<Ticket> with that one field updated:

let bookSeat seatNo name tickets = 
  tickets |> List.map (fun ticket ->
    if ticket.seat = seatNo then { ticket with customer = name }
    else ticket )

bookSeat 3 "Tomas" tickets
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
0

Here is a way to use a list of mutable (ref-cells) tickets:

let maxSeats = 10
let tickets : (int * Ticket option) ref list =
    [1..maxSeats]
    |> List.map (fun s -> ref (s, None) )

let bookSeat seat name =
    match List.tryFind (fun r -> let (s,_) = !r in s = seat) tickets with
    | Some r -> 
        r :=
            match !r with
            | (s, Some ticket) -> (s, Some { ticket with customer = name })
            | (s, None)        -> (s, Some { seat = s; customer = name })
    | None ->
        failwith "seat not found"

obvious you can make tickets itself mutable too, if you want to add seats instead of initializing them with all the obvious seats

a better approach(?)

Still I think that this is the wrong way to do it - I think you want a Map:

type Ticket = {seat:int; customer:string}  
type Tickets = Map<int, Ticket>

let bookSeat seat name (tickets : Tickets) =
    match Map.tryFind seat tickets with
    | Some oldTicket ->
        tickets 
        |> Map.remove seat
        |> Map.add seat { oldTicket with customer = name }
    | None ->
        tickets
        |> Map.add seat { seat = seat; customer = name }

note that these are all immutable values, so bookSeat will return a new Ticket-reservation-map

hyprid using Dictionary

or you can use a common .net Dictionary and mutate this:

type Ticket = {seat:int; customer:string}  
let tickets = System.Collections.Generic.Dictionary<int, Ticket>()

let bookSeat seat name =
    match tickets.TryGetValue seat with
    | (true, oldTicket) ->
        tickets.[seat] <- { oldTicket with customer = name }
    | (false, _) ->
        tickets.[seat] <- { seat = seat; customer = name }

Here you don't have to pass the Tickets around - they will be mutated in place (but still the Ticket-objects themselves are still immutable)

Note that this right now is not thread-safe so be careful.

Random Dev
  • 51,810
  • 9
  • 92
  • 119
0

I think the most idiomatic option here would be to simply use a string array. Given you know in advance the size and you want to be able to update it, this is the structure that fills both those needs most idiomatically. So,

// create 10-element string array initialized with null
let (tickets : string array) = Array.zeroCreate 10 

...

tickets.[3] <- "New Customer"

keep it simple.

Granted this is not "purely-functional" (but any purely functional solutions here just kick the non-pure parts down the road anyway), but if the goal is just to get the job done, this will do it.

Dax Fohl
  • 10,654
  • 6
  • 46
  • 90