6

I'm working through the basics of F# and am still at the point where I'm not sure what's possible or what's available. I'm thinking there ought to be a better way to do this:

I'm looking at the common demonstration scenario of using this code for added type protection

type CustomerId = CustomerId of int
type OrderId = OrderId of int

At some point I have persistence code that needs to unwrap the integer:

dbcmd.Execute(CID = ???, OID = ???)

Option A: bulky but works

dbcmd.Execute(CID = match CustomerId with (CustomerId cid) -> cid, OID = match OrderId with (OrderId oid) -> oid)

Option B is derived from the answer in Concise pattern match on single case discriminated union in F#

This requires 2 lines and if there are 4 or 5 things to unwrap I start to really dislike the 'distance' between the left and right side of the let statement -- I'm likely to eventually type something out of order

let (CustomerId cid, OrderId oid) = (CustomerId, OrderId)
dbcmd.Execute(CID = cid, OrderId = oid)

Option C: This is probably what I'll prefer if there's nothing better. It is clear but consumes more vertical space than I would have hoped

let (CustomerId cid) = CustomerId
let (OrderId oid) = OrderId
dbcmd.Execute(CID = cid, OrderId = oid)

Option D: This is sort of what I'm hoping exists. This doesn't actually work since this is the syntax for wrapping, not unwrapping, but you get the idea

dbcmd.Execute(CID = (CustomerId id), OID = (OrderId id))

Does concise syntax that resembles Option D exist?

Community
  • 1
  • 1
Clyde
  • 7,723
  • 8
  • 53
  • 83

2 Answers2

7

I usually use one of these options, choosing between them pretty much intuitively. Mostly I prefer option 1, but it doesn't work if I need to make the constructor private.

Option 1: specify patterns right in parameter declarations

You can do this, because function parameters don't have to be just plain identifiers, they can be patterns too.

let f (CustomerId cid) (OrderId oid) =
  let cmd = ...
  cmd.Execute( cid, oid )

Option 2: create special accessor functions

type CustomerId = CustomerId of int
   with static member toInt (CustomerId id) = id

cmd.Execute( CustomerId.toInt cid, ... )

Option 2b: same, but with instance member

type CustomerId = CustomerId of int
   with member this.asInt = match this with (CustomerId id) -> id

cmd.Execute( cid.asInt, ... )
Fyodor Soikin
  • 67,206
  • 8
  • 106
  • 148
  • Option 1 is where I want to go, the problem is that the CustomerId/OrderId values were inside of a larger record rather than 'loose'. However, I think that points at my true problem. The cmd execution should be factored out into its own function that *does* take individual loose parameters -- let the calling function deal with organizing the parameters needing to be passed in. – Clyde Mar 16 '16 at 23:41
  • 2
    You can nest patterns too. – Fyodor Soikin Mar 16 '16 at 23:52
  • 1
    Given `type Order = { Id : OrderId }`, you can easily deconstruct record and single case union in one go: `let f { Id = OrderId oid } = ...` – kaefer Mar 18 '16 at 20:28
4

You could also use a lambda without modifying or extending the type definition:

cid |> fun (CustomerId i) -> i
TheQuickBrownFox
  • 9,606
  • 1
  • 18
  • 30