需要了解使用泛型的模式匹配的基础知识

时间:2015-12-04 20:43:18

标签: f#

我需要帮助理解以下背后的概念:

我有这个:

type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure

但这不起作用:

let getRequest = function
    | Success input -> input 
    | Failure msg -> msg

但这样做:

let getRequest result = 
    match result with
    | Success input -> input 
    | Failure msg -> failwithf "Invalid input: %s" msg

为什么最初的“getRequest”会失败?

同样,我只是不理解模式匹配的基本规则。 有人可以对此有所了解吗?

整个代码在这里:

module Core

type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure

let bind nextFunction lastFunctionResult = 
    match lastFunctionResult with
    | Success input -> nextFunction input
    | Failure f -> Failure f

type Request = {Name:string; Email:string}

let validate1 input =
   if input.Name = "" then Failure "Name must not be blank"
   else Success input

let validate2 input =
   if input.Name.Length > 50 then Failure "Name must not be longer than 50 chars"
   else Success input

let validate3 input =
   if input.Email = "" then Failure "Email must not be blank"
   else Success input;;

let Validate = 
    validate1 
    >> bind validate2 
    >> bind validate3;;

// Setup
let goodInput = {Name="Alice"; Email="abc@abc.com"}
let badInput = {Name=""; Email="abc@abc.com"};;

let getRequest = function
    | Success input -> input 
    | Failure msg -> msg

// Test
let result = Validate goodInput;;
let request = getRequest result;;

2 个答案:

答案 0 :(得分:3)

鉴于此定义

let getRequest = function
| Success input -> input 
| Failure msg -> msg

我们可以推断其类型如下:

  1. 由于它是一个函数,因此对于某些尚未知的占位符类型,它具有类型?1 -> ?2
  2. 输入使用SuccessFailure联合案例,因此输入本身必须是某种类型Result<?3,?4>,而函数类型为Result<?3,?4> -> ?2
  3. 每个分支的类型必须与函数的返回类型相同。

    1. 查看第一个分支,我们发现这意味着?3 = ?2
    2. 查看第二个分支,这意味着?4 = ?2
    3. 因此,函数的整体类型必须是Result<?2,?2> -> ?2,或使用真实的F#表示法,Result<'a,'a> -> 'a其中'a可以是任何类型 - 这是一个通用的函数定义。

      但是Validate的类型为Request -> Result<Request, string>,因此其输出与getResult的定义不一致,因为成功和失败类型不同而且您不能只是直接将结果从一个传递给另一个。

      另一方面,用

      let getRequest = function
      | Success input -> input 
      | Failure msg -> failwithf "Invalid input: %s" msg
      

      我们会这样分析:

      1. 和以前一样,任何函数都有?1 -> ?2类型。
      2. 与以前一样,输入必须明确为Result<?3,?4>
      3. 但是分析分支,我们得到了不同的结果:

        1. 第一个分支再次导致约束?3 = ?2
        2. 但是第二个分支是不同的 - failwithf,该格式模式接受一个字符串并抛出一个异常(并且可以有任何返回类型,因为它永远不会正常返回),所以我们看到msg必须是一个字符串,因此?4 = string
        3. 所以整体类型为Result<'a,string> -> 'a,现在可以根据需要将Result<Request,string>传递给它。

答案 1 :(得分:1)

我认为你的问题实际上提供了一个很好的平台来解释模式匹配。

让我们首先考虑所涉及的类型,你的getRequest函数必须是一个带有某种类型签名getRequest : 'a -> 'b'的函数,即它需要一种类型的东西并返回另一种类型但是让我们看看是什么你写的。

let getRequest = 
    function
    | Success input -> input // this branch would return 'TSuccess
    | Failure msg -> msg // this brach would return 'TFailure

你不能拥有类型签名为'a -> 'b or 'c的函数。(编译此函数的唯一方法是'b'c可以被约束为相同类型,那么我们只需要一致'a -> 'b)。

但是等等,进入受歧视的联盟。

您的受歧视联盟的类型为Result<'TSuccess,'TFailure>。或者,按照我上面的描述来描述类型'd<'b,'c>。现在,我们绝对可以拥有一个类型签名为'a -> 'd<'b,'c>的函数。

'd<'b, 'c>只是一种涉及'b的通用类型,它涉及'c。类型签名包含所有这些内容,因此希望您可以看到我们可以在此类型中同时包含'TSuccess'TFailure,而且没有任何问题。

因此,如果我们想要返回可以包含不同类型组合的东西,我们已经找到了区分联合的完美用例。它可以在一种类型中包含'TSuccess'TFailure。然后,您可以使用模式匹配在这些结果之间进行选择。

let getRequest = 
    function
    | Success input -> printfn "%A" input // do something with success case
    | Failure msg -> printfn "%A" msg // do something with failure case

只要我们在每种情况下都有一致的返回类型,我们就可以插入我们想要的任何行为。

接下来为什么抛出异常是可以的。让我们看一下failwithf函数的类型签名:failwithf : StringFormat<'T,'Result> -> 'Tfailwithf函数采用字符串格式并返回某种类型'T

让我们再看看你的第二个功能

let getRequest result = 
    match result with
    | Success input -> input // type 'TSuccess
    | Failure msg -> failwithf "Invalid input: %s" msg // type 'T inferred to be 'TSuccess

函数签名是一致的,'a -> 'TSuccess