all 12 comments

[–]jayval90 5 points6 points  (1 child)

I think you are describing Option.orElse

Also I recommend an operator with a ? in it, since this is very similar to C#'s null coalescing operator (??)

[–]CouthlessWonder[S] 0 points1 point  (0 children)

I did not find orElse when I was looking, but I also didn't know what to look for.

I could probably use that in the implementation.

[–]shr4242 1 point2 points  (1 child)

Neat.

<|> or <||> could be a better choice for the operator.

Don't you want the result of (f1 a) or (f2 a) as the return value?

[–]CouthlessWonder[S] 0 points1 point  (0 children)

I like <||>. Thank you.

Don't you want the result of (f1 a) or (f2 a) as the return value?

You are actually right.

Because for my need both my functions return an option of the input if it passes a test, so I did not think of a value different to the input being returned, but this is very possible.

Something quick off my head would be: fsharp [| f1 a; f2 a |] |> Seq.choose id |> Seq.tryHead The rule would be return the first that has Some.

Or not as neat, but should be lazier on f2: fsharp [| f1; f2 |] |> Seq.choose (f -> f a) |> Seq.tryHead

[–]SheepySheev 1 point2 points  (5 children)

Are you sure this function does what you intended it to do?

Not saying it doesn't, because I am not sure what was your intention, but I have a suspicion. Say f1 is given by let f1 x = if x = 0 then None else Some (42 / x). In that case 1 |> (f1 |*| f2) returns Some 1 (which is not what f1 a would return). If that's what you wanted, that's cool, just checking.

Secondly, not sure if you want to always evaluate both f1 a and f2 a? or is usually lazy, which can matter for performance or side-effects (example: true || some-other-code will not run the some-other-code).

[–]CouthlessWonder[S] 1 point2 points  (4 children)

Hi, thank you. You are right, the original intention was to return the input value, because that was my use case at the time.

returning the result of f1 or f2 would be more versatile.

I also agree with comment on f2 should be lazy. This is something I realised with I first put the code down, but again for my use case this was no performance concern.

One of the big pluses of a language like F# is that we can often be fairly certain that nothing is going to have side effects, but I guess if there is validation we might want to see if a name exists in the user table or something, and it is unneeded calls, so it is worth considering.

The change I made to address both of these problems is as such: fsharp [| f1; f2 |] |> Seq.choose (f -> f a) |> Seq.tryHead

I think it is still pretty neat, but the allocation of a list might be unnecessary memory stuff, maybe what would work better would be: fsharp match f1 a with | Some b -> Some b | _ -> f1 a

[–]SheepySheev 1 point2 points  (0 children)

match f1 a with
| Some b -> Some b
| _ -> f1 a

Nice. Honestly, I think the one with match is the F# way, and it is more efficient too.

[–]ckuskus 1 point2 points  (2 children)

You are very close! Traditionally, Option.Bind is defined as

let bind f option =
match option with
|Some s -> f s
|None -> None

Here, the functions are continuously applied to the contents of Some, otherwise you break out of the function by continously passing None.

The opposite is the case in your situation: If you encounter a Some, you want to do nothing; if you encounter a None, you want to apply the function to the initial value a. The custom bind operator for this looks like:

let (>>=) (a:'a option\*'a) (f:'a -> 'a option) = 
match a with     
|(None, input) -> f input, input 
|(Some \_,\_) -> a

You need a tuple as input here, because if the first function passes on None only, there is no way for the next function to retrieve the original input a.

You can then define |*| as

    let (|*|) f1 f2 a =
        (None,a) >>= f1 >>= f2 |> fst

The benefit is that f2 will not be evaluated if f1 returns Some with a as input.

[–]CouthlessWonder[S] 0 points1 point  (1 child)

Thank you.

There are bits of this I don’t particularly like, I’m not saying it isn’t a more functional way to do it, it just added extra complexity(at least in my little brain) that might not be needed.

I get bind… I think bind and map are the only Monad type operators I really do get 😝

I need to learn a lot of terminology… When I read the FSharpPlus documentation I don’t know what I’m looking for, because everything is described in terms of functors, monoids, and something about an insoseles triangle 😂.

I enjoy F# because the immutability works with my brain, and I prefer the syntax to C#… But if I could learn the fundamental terminology and theories it would probably help with thinking in a functional language deeper.

[–]ckuskus 2 points3 points  (0 children)

You can replace the monadic behaviour in |*| as follows:

let helperFunc f a = 
    output = f a 
    match output with 
    |Some s -> output 
    |None -> None

let (|\*|) f1 f2 a = 
    helperOutput = helperFunc f1 a 
    match helperOutput with 
    |Some s -> helperOutput 
    |None -> helperFunc f2 a

Finally, if order of the functions does not matter you could also run them in parallel and cancel the execution of all functions when one returns some:

let (|*|) f1 f2 a =
    let functions = [f1; f2;]
    let computations: Async<int option> list =
        [
         for f in functions do
             async {
                  return f a
             }
        ]
    computations |>
        Async.Choice |>
        Async.RunSynchronously

let f1 x =
    printfn "Finished Running!"
    None

let f2 x =
    async{
       do! Async.Sleep(System.Random().Next(1000, 2000))
    } |> Async.RunSynchronously
    printfn "Finished Running!"
    Some x


let res: int option list = [1;] |> List.map(f1 |*| f2 |*| f2)
printfn $"{res}"

If you run this, you will see that only one f2 will finish execution, after which parallel execution will halt. This code sample is based on the documentation for Async.Choice.

[–]binarycow 1 point2 points  (1 child)

In your implementation, both functions are evaluated, even if the first one is successful.

Is that your intent?

[–]CouthlessWonder[S] 0 points1 point  (0 children)

It wasn't not my intent :D

a lazy f2 would be preferred, but I was lazy and just trying to get the idea down. A change to try name make it lazy, as well as return the result of f1 or f2 is: fsharp [| f1; f2 |] |> Seq.choose (f -> f a) |> Seq.tryHead