Turning off TryWith in calculation expressions

(Having not received the "grok" FParsec, I followed the advice that I read somewhere and started writing a small parser myself. For some reason, I noticed that it seemed like an opportunity to try to monadize it, and now I have problems with N ... )

This is my "Result" type (simplified)

type Result<'a> = | Success of 'a | Failure of string 

Here is the calculation expression builder

 type ResultBuilder() = member m.Return a = Success(a) member m.Bind(r,fn) = match r with | Success(a) -> fn a | Failure(m) -> Failure(m) 

In this first example, everything works (compiles) as expected:

 module Parser = let res = ResultBuilder() let Combine p1 p2 fn = fun a -> res { let! x = p1 a let! y = p2 a return fn(x,y) } 

My problem is here: I would like to catch any malfunction in the "combining" function and return the malfunction, but it says that I must define "zero".

  let Combine2 p1 p2 fn = fun a -> res { let! x = p1 a let! y = p2 a try return fn(x,y) with | ex -> Failure(ex.Message) } 

Having no idea what I should return to Zero, I just entered member m.Zero() = Failure("hello world") , and now it says I need TryWith .

So:

 member m.TryWith(r,fn) = try r() with | ex -> fn ex 

And now he wants Delay, so member m.Delay f = (fun () -> f()) .

At what point he says (in ex -> Failure ), This expression should have type 'unit', but has type 'Result<'a>' , and I throw my hands and speak to you guys ...

Link to play: http://dotnetfiddle.net/Ho1sGS

+2
source share
2 answers

The with block should also return the result from the evaluation expression. Since you want to return Result.Failure, you need to define the member m.ReturnFrom a = a and use it to return Failure from the with block. In the try block, you must also indicate that fn returns Success if it does not throw.

 let Combine2 p1 p2 fn = fun a -> res { let! x = p1 a let! y = p2 a return! try Success(fn(x,y)) with | ex -> Failure(ex.Message) } 

Update:

The initial implementation showed a warning, not an error. The expression in the with block was not used, since you returned from the try block, so you can simply add |> ignore . In this case, if fn throws, the return value is m.Zero() , and the only difference is that you get "hello world" instead of ex.Message . Illustrated by the example below. Full script here: http://dotnetfiddle.net/mFbeZg

The original implementation using |> ignore to disable the warning:

 let Combine3 p1 p2 fn = fun a -> res { let! x = p1 a let! y = p2 a try return fn(x,y) with | ex -> Failure(ex.Message) |> ignore // no warning } 

Run it:

 let comb2 a = let p1' x = Success(x) let p2' y = Success(y) let fn' (x,y) = 1/0 // div by zero let func = Parser.Combine2 p1' p2' fn' a func() let comb3 a = let p1' x = Success(x) let p2' y = Success(y) let fn' (x,y) = 1/0 // div by zero let func = Parser.Combine3 p1' p2' fn' a func() let test2 = comb2 1 let test3 = comb3 1 

Result:

 val test2 : Result<int> = Failure "Attempted to divide by zero." val test3 : Result<int> = Failure "hello world" 
+1
source

If you want to support try ... with inside the calculation builder, you need to add TryWith (as you tried), as well as several other members, including Delay and Run (depending on how you want to implement Delay ). To be able to return a failure, you also need to support return! by adding ReturnFrom :

 type ResultBuilder() = member m.Return a = Success(a) member m.Bind(r,fn) = match r with | Success(a) -> fn a | Failure(m) -> Failure(m) member m.TryWith(r,fn) = try r() with ex -> fn ex member m.Delay(f) = f member m.Run(f) = f() member m.ReturnFrom(r) = r 

Now you can do the following:

 let Combine2 p1 p2 fn = fun a -> res { let! x = p1 a let! y = p2 a try return fn(x,y) with ex -> return! Failure(ex.Message) } 

The trick is that the normal branch only uses return (representing success), but the exception handler uses return! to return an explicitly generated result using Failure .

However, if you are interested in parsers, you need to use a different type - what you describe here is more like a monad. To implement parser combinators, you need a type that represents the parser, not the result of the parser. See, for example, in this article .

+2
source

Source: https://habr.com/ru/post/1212973/


All Articles