Author Topic: F# - selecting points and entities  (Read 2735 times)

0 Members and 1 Guest are viewing this topic.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
F# - selecting points and entities
« on: May 05, 2015, 12:58:54 AM »
Hi All,

just starting to do some real work with F# and was wondering how the other fsharpers here would approach the following situation.

A simple scenario is we need to ask the user for 2 points and they could exit at any time so we need to be able to exit the current function gracefully. There are a few ways to do this but I'd like to stick with a more functional approach rather than if else's etc.

Here's my take for adding a leader say at the point of an entity, the leader text may be data from the selected entity and why I pass the id along with the points:

Code - C#: [Select]
  1. let addSomething () =
  2.     let ed = AcAp.DocumentManager.MdiActiveDocument.Editor
  3.     let entResult = ed.GetEntity("Select an entity at leader start point:\n")
  4.     match entResult.Status with
  5.     | PromptStatus.OK ->
  6.         let pointResult = ed.GetPoint("Select insertion point for object text:\n")
  7.         match pointResult.Status with
  8.         | PromptStatus.OK -> doSpecialLeader entResult.ObjectId entResult.PickedPoint pointResult.Value // function that draws leader an text
  9.         | _ -> ()
  10.     | _ -> ()
  11.  

How would you do this?
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - selecting points and entities
« Reply #1 on: May 05, 2015, 01:42:07 AM »
Hi,

By my side, as addsomething is typically an imperative function (unit -> unit) with only a side effect (calling another imperative function), and as there's no need for else stuff, I'd write it in this simple way:

Code - F#: [Select]
  1. let addSomething () =
  2.     let ed = AcAp.DocumentManager.MdiActiveDocument.Editor
  3.     let entResult = ed.GetEntity("Select an entity at leader start point:\n")
  4.     if entResult = PromptStatus.OK then
  5.         let pointResult = ed.GetPoint("Select insertion point for object text:\n")
  6.         if pointResult = PromptStatus.OK then
  7.             doSpecialLeader entResult.ObjectId entResult.PickedPoint pointResult.Value
Speaking English as a French Frog

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: F# - selecting points and entities
« Reply #2 on: May 05, 2015, 02:10:50 AM »
Hi,

By my side, as addsomething is typically an imperative function (unit -> unit) with only a side effect (calling another imperative function), and as there's no need for else stuff, I'd write it in this simple way:

Thanks Gile, that makes perfect sense and is pretty much what I started with, just trying to use the FP side of F# to get the hang of it.

As you say, being an imperative function there is probably no real advantage to using pattern matching unless you have multiple patterns to match (i.e. a switch case or if else sequence).

It's easy to want to change and use functional every where but one of the main reasons I'm moving to F# is for the conciseness and imperative coding in F# definitely saves some typing.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - selecting points and entities
« Reply #3 on: May 05, 2015, 06:49:13 AM »
You're welcome Mick.
I'm glad to see some other Fsharper, we're so few over there.
Speaking English as a French Frog

kaefer

  • Guest
Re: F# - selecting points and entities
« Reply #4 on: May 05, 2015, 08:19:21 AM »
I've found it helpful in some instances to employ techniques which avoid deeply nested if-then-else clauses. For an overview of the issue see e.g. Railway oriented programming.

Code - F#: [Select]
  1. // Discriminated Union as a result type to encapsulate either
  2. // success, which goes on to the next stage, Cancellation
  3. // or Failure with an error string
  4. type Res<'T> =
  5. | Next of 'T
  6. | Cancel
  7. | Fail of string
  8.  
  9. // Define an operator along the lines of the pipeline operator,
  10. // which expects anything derived from the PromptResult type
  11. // as the first argument and calls the secon dargument function
  12. // if successful
  13. let (|?>) (pr : #PromptResult) f =
  14.     match pr.Status with
  15.     | PromptStatus.OK -> f pr
  16.     | PromptStatus.Cancel -> Cancel
  17.     | _ -> Fail <| sprintf "error expecting %s" (pr.GetType().Name)

Your example now becomes ...

Code - F#: [Select]
  1. let addSomething () =
  2.     ed.GetEntity "Select an entity at leader start point:"
  3.     |?> fun entResult ->
  4.     ed.GetPoint "Select insertion point for object text:"
  5.     |?> fun pointResult ->
  6.     // Your foo goes here ...
  7.     Next()  // Return success with no value (unit)
  8.  
  9. addSomething () |> function
  10. | Fail msg -> ed.WriteMessage("Houston, we've had a problem: {0}", msg)
  11. | _ -> ()

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: F# - selecting points and entities
« Reply #5 on: May 05, 2015, 05:34:44 PM »
Thanks kaefer, nice work.

Thanks for the link!
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - selecting points and entities
« Reply #6 on: May 09, 2015, 06:32:24 AM »
Hi,

Another way (not as elegant as kaefer's one) should be using a Success/Failure Workflow.

Defining the generic success/failure workflow builder
Code - F#: [Select]
  1. type Attempt<'a> = (unit -> 'a option)
  2.  
  3. type AttemptBuilder() =
  4.     let succeed x = (fun () -> Some(x)) : Attempt<'a>
  5.    let fail = (fun () -> None) : Attempt<'a>
  6.     let runAttempt (a : Attempt<'a>) = a()
  7.    let bind p rest = match runAttempt p with None -> fail | Some r -> (rest r)
  8.    let delay f = (fun () -> runAttempt (f()))
  9.  
  10.    member b.Bind(p, rest) = bind p rest
  11.    member b.Delay(f) = delay f
  12.    member b.Let(p, rest) : Attempt<'a> = rest p
  13.     member b.Return(x) = succeed
  14.     member b.Zero() = fail
  15.  
  16. let attempt = new AttemptBuilder()

Building an Attempt value for the PromptResult derived types:
Code - F#: [Select]
  1. let failIfNotOk (pr : #PromptResult) =
  2.     attempt {  if pr.Status = PromptStatus.OK then return pr }

Using the workflow in your function.
Assuming doSpecialLeader returns the newly created leader ObjectId, drawLeader returns an ObjectId option (ignored here):
Code - F#: [Select]
  1.     let drawLeader = attempt {
  2.         let! entResult = failIfNotOk (ed.GetEntity("Select an entity at leader start point:\n"))
  3.         let! pointResult = failIfNotOk (ed.GetPoint("Select insertion point for object text:\n"))
  4.         return doSpecialLeader entResult.ObjectId entResult.PickedPoint pointResult.Value }
  5.  
  6.     drawLeader() |> ignore


You can use the first PromptResult values within the workflow:
Code - F#: [Select]
  1.     let drawLeader = attempt {
  2.         let! entResult = failIfNotOk (ed.GetEntity("Select an entity at leader start point:\n"))
  3.         let opts = new PromptPointOptions("Select insertion point for object text:\n")
  4.         opts.UseBasePoint <- true
  5.         opts.BasePoint <- entResult .PickedPoint
  6.         let! pointResult = failIfNotOk (ed.GetPoint(opts))
  7.         return doSpecialLeader entResult.ObjectId entResult.PickedPoint pointResult.Value }
  8.  
  9.     drawLeader() |> ignore
« Last Edit: May 09, 2015, 06:56:42 PM by gile »
Speaking English as a French Frog

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: F# - selecting points and entities
« Reply #7 on: May 10, 2015, 11:31:26 PM »
Thanks Gile, Computation Expressions (similar to monads) are very powerful indeed and very flexible.
Great examples All!
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - selecting points and entities
« Reply #8 on: May 16, 2015, 10:11:57 AM »
Still learning...

For such a simple task, all this can be written more concisely.

Helpers:
Code - F#: [Select]
  1. // Minimal success/failure workflow builder
  2. type AttemptBuilder() =
  3.     member b.Bind(x, f) = Option.bind f x
  4.     member b.Return(x) = Some x
  5.     member b.Zero() = None
  6.  
  7. let attempt = new AttemptBuilder()
  8.  
  9. // Specific PromptResult attempt
  10. let failIfNotOk (pr : #PromptResult) =
  11.     attempt { if pr.Status = PromptStatus.OK then return pr }
  12.  
  13. // Editor.GetPoint extension method
  14. type Editor with
  15.     member ed.GetPoint(pt, msg) =
  16.         ed.GetPoint(new PromptPointOptions(msg, UseBasePoint = true, BasePoint = pt))

Using:
Code - F#: [Select]
  1. attempt {
  2.     let! entResult = failIfNotOk (ed.GetEntity("\nSelect an entity at leader start point: "))
  3.     let! pointResult = failIfNotOk (ed.GetPoint(entResult.PickedPoint, "\nSelect insertion point for object text: "))
  4.     return doSpecialLeader entResult.ObjectId entResult.PickedPoint pointResult.Value }
  5. |> ignore
« Last Edit: May 16, 2015, 10:59:13 AM by gile »
Speaking English as a French Frog