【问题标题】:Scala's notion of "partial functions"' & the ".orElse" method in F#Scala 的“部分函数”概念和 F# 中的“.orElse”方法
【发布时间】:2018-12-17 15:17:43
【问题描述】:

在 Scala 中,“部分函数”的概念与 F# 的 function 关键字允许我实现的非常相似。然而,Scala 的部分函数也允许通过orElse 方法进行组合,如下所示:

def intMatcher: PartialFunction[Any,String] = {
  case _ : Int => "Int"
}

def stringMatcher: PartialFunction[Any,String] = {
  case _: String => "String"
}

def defaultMatcher: PartialFunction[Any,String] = {
  case _ => "other"
}

val msgHandler =
  intMatcher
  .orElse(stringMatcher)
  .orElse(defaultMatcher)

msgHandler(5) // yields res0: String = "Int"

我需要知道是否有办法在 F# 中实现相同的合成功能。

【问题讨论】:

    标签: scala f# partialfunction


    【解决方案1】:

    您在 Scala 中编写它的方式等同于在 C# 中使用扩展方法。对于函数式编程来说,它并不是特别惯用的。要在 F# 中严格使用可组合函数,您可以这样做。

    // reusable functions
    let unmatched input = Choice1Of2 input
    
    let orElse f =
        function
        | Choice1Of2 input -> f input
        | Choice2Of2 output -> Choice2Of2 output
    
    let withDefault value =
        function
        | Choice1Of2 _ -> value
        | Choice2Of2 output -> output
    
    // problem-specific functions
    let matcher isMatch value x =
        if isMatch x then Choice2Of2 value
        else Choice1Of2 x
    
    let isInt (o : obj) = o :? int
    let isString (o : obj) = o :? string
    
    let intMatcher o = matcher isInt "Int" o
    let stringMatcher o = matcher isString "String" o
    
    // composed function
    let msgHandler o =
        unmatched o
        |> orElse intMatcher
        |> orElse stringMatcher
        |> withDefault "other"
    

    这里,Choice1Of2 表示我们还没有找到匹配项并且包含不匹配的输入。而Choice2of2 表示我们找到了匹配项并包含输出值。

    【讨论】:

      【解决方案2】:

      我可能会在这里使用部分活动模式,这样您就可以使用模式匹配。 Some(T) 匹配,None 不匹配。

      let (|Integer|_|) (str: string) =
         let mutable intvalue = 0
         if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
         else None
      
      let (|Float|_|) (str: string) =
         let mutable floatvalue = 0.0
         if System.Double.TryParse(str, &floatvalue) then Some(floatvalue)
         else None
      
      let parseNumeric str =
         match str with
           | Integer i -> "integer"
           | Float f -> "float"
           | _ -> "other"
      

      https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/active-patterns

      但值得注意的是,在您提供的这种人为的情况下,您可以只使用一个 match 语句。我假设您的目标是拆分匹配条件。

      let msgHandler (x: obj) = 
          match x with
          | :? int -> "integer"
          | :? float -> "float"
          | _ -> "other"
      

      【讨论】:

      • 显然,部分模式的代价是,如果您不涵盖所有可能的情况,您将不会收到警告。
      • 这很接近了。但是,在您的情况下,与正确匹配相关的 printf 行为不会由活动模式匹配器处理,而是留给主要的 parseNumeric 函数来处理。我正在寻找一个函数中的匹配及其相关行为的封装,就像 Scala 的情况一样。
      • 您可以返回字符串而不是打印它。我会更新我的答案。
      • 我发布了自己的答案,展示了我如何在一个地方处理匹配和相关行为的封装。你的不是我想要的,因为它仍然将处理箭头的左侧交给主处理函数。在我的回答中,我清楚地展示了它是如何反转的,并且取决于活动模式本身来决定返回什么
      • @shayan 这就是在 F# 中完成基于模式的编程的简单方法。您考虑一个案例(即箭头的左侧)并基于该案例调度功能。您自己的答案不会改变这一点,它只是将每个案例放在自己的功能中。这可以使用 F# 轻松完成,但不是解决问题的更好方法。
      【解决方案3】:

      我想出了两个解决方案来实现我的确切目标。一种是通过使用主动模式:

      let orElse(fallback: 'a -> (unit -> 'b) option) (matcher: 'a -> (unit -> 'b) option) (arg:  'a) :  (unit -> 'b) option = 
          let first = matcher(arg)
          match first with
          | Some(_) -> first
          | None -> fallback(arg)
      
      let (|StringCaseHandler|_|)(arg: obj) = 
          match arg with
          | :? string -> Some(fun () ->  "string")
          | _ -> None
      
      let (|IntCaseHandler|_|)(arg: obj) = 
          match arg with
          | :? int -> Some(fun () ->  "integer")
          | _ -> None
      
      let (|DefaultCaseHandler|_|)(arg: 'a) = 
          Some(fun () -> "other")
      
      let msgHandler = 
          ``|StringCaseHandler|_|`` |> 
              orElse ``|IntCaseHandler|_|`` |> 
              orElse ``|DefaultCaseHandler|_|``
      

      具有活动模式的解决方案是安全的,因为它不会在没有正确匹配的情况下抛出MatchFailureException;而是返回 None

      第二个涉及为 'a -> 'b 类型的函数定义扩展方法,并且我也尽可能接近 Scala 的“部分函数”orElse 行为,如果结果函数不产生正确匹配:

      [<Extension>]
      type FunctionExtension() =
          [<Extension>]
          static member inline OrElse(self:'a -> 'b,fallback: 'a -> 'b) : 'a -> 'b = 
                  fun arg -> 
                      try 
                          self(arg) 
                      with
                      | :? MatchFailureException -> fallback(arg)
      
      let intMatcher : obj -> string = function 
                                       | :? int -> "integer"
      let stringMatcher : obj -> string = function 
                                          | :? string -> "string"
      let defaultMatcher : obj -> string = function 
                                           | _ -> "other"
      
      let msgHandler: obj -> string = intMatcher
                                          .OrElse(stringMatcher)
                                          .OrElse(defaultMatcher)
      

      【讨论】:

      • 活动模式实际上只是具有奇怪名称的函数,允许它们用于模式匹配。但由于您没有在模式匹配中使用它们,您可能应该只编写名为 stringCaseHandlerintCaseHandlerdefaultCaseHandler 的函数。
      • 同意。我的看起来很奇怪
      • 如果你真的想要异常,我想你可能不应该这样,你可以很容易地定义你的默认函数来抛出一个而不是返回 "other"
      猜你喜欢
      • 2017-03-30
      • 1970-01-01
      • 2011-12-02
      • 2012-08-04
      • 2017-12-19
      • 2011-03-07
      • 2015-08-17
      • 2019-03-09
      • 1970-01-01
      相关资源
      最近更新 更多