문제

I'm stuck with a seemingly trivial problem: I'm unable to handle an exception in a function if it's written in a point-free manner.

Consider these two functions:

let divide1 x y =
    try
        x / y
    with
        | :? System.DivideByZeroException -> 42

let divide2 =
    try
        (/)
    with
        | :? System.DivideByZeroException -> fun _ _ -> 42

let x1 = divide1 5 0 // works fine
let x2 = divide2 5 0 // doesn't handle an exception

Although both functions are seemingly same, they have different types:

val divide1: int -> int -> int
val divide2: (int -> int -> int)

Obviously, divide2 does not even attempt to handle an exception. It simply returns an operator.

What can I do in order to make divide2 handle the exception in a proper manner (except specifically declaring its arguments)?

도움이 되었습니까?

해결책

This is one of the reasons why I find the point-free style problematic. It makes it difficult to use standard language constructs like try .. with (or standard loops and other F# features) and you need to replace them with custom combinators. In this case, you could define combinator tryWith2 that wraps a two-argument function in an exception handler:

let tryWith2 f h a b = 
  try f a b // Call the function with two arguments
  with e -> 
    // Try running the error handler with the exception
    match h e with 
    | Some g -> g a b // Error handler provided another function
    | _ -> reraise()  // Error was not handled - reraise

Then you could write the function in a point-free style like this (the error handling is still not-point-free, but I do not want to make this too silly :-))

let divide2 =
  tryWith2 (/) (function
      | :? System.DivideByZeroException -> Some(fun _ _ -> 42)
      | _ -> None)

let x1 = divide2 5 0 // returns 42
let x2 = divide2 5 1 // returns 5

Of course, the point free style is useful, even in F#. For example, when writing a DSL, it is a great way to compose declarative specification (because the primitives express something using higher-level of abstraction). Here, you need to express something that is quite close to normal F# code and, I believe, that is best expressed as normal F# code.

다른 팁

What you need to remember is that in divide2, you aren't returning the result of X divided by Y, you're returning a function that divides X by Y. The code for the let binding is being executed immediately because it isn't given function syntax. Let's look at both divide bindings with the longer function syntax:

let divide1 =
    fun x ->
        fun y ->
            try
                x / y
            with
                | :? System.DivideByZeroException -> 42

let divide2 =
    try
        fun x ->
            fun y ->
                x / y
    with
        | :? System.DivideByZeroException -> fun _ _ -> 42

When displayed this way, it should be clearer how the two definitions are different. The try block is in a completely different location, and executed at different points in time.

The only way to add logic such as exception handling to an existing function is to wrap it, either as you do in divide1, or with a higher order function as Tomas has shown.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top