F#奇妙游(31):数据缺失的处理

option

在处理数据时,缺失数据和无效数据是一个常见的问题。在Python中,通常使用None来表示缺失数据。

在F#中,我们可以使用FSharpOption来表示缺失数据。FSharpOption是一个泛型类型,它有两个值:Some和None。Some表示一个值,而None表示一个缺失的值。FSharpOption的定义如下:

FSharp 复制代码
type FSharpOption<T> =
    | None
    | Some of T

从ADT的组合数来分析,这个和类型的组合数是 1 + C T 1+C_T 1+CT 。在通常的情况下,我们在F#中都把这个写作option<T>,或者T option。而构造一个option<T>的值,我们可以使用SomeNone这两个构造器,例如:

FSharp 复制代码
let x = Some 1
let y = None

一般的,我们可以使用match表达式来处理option<T>的值,例如:

FSharp 复制代码
let z = 
    match x with
    | Some x -> x
    | None -> 0

如果是定义输入参数是option<T>类型的函数,还有一个语法糖:

FSharp 复制代码
let f (x: int option) =
    match x with
    | Some x -> x
    | None -> 0

可以写成:

FSharp 复制代码
let f = 
    function
    | Some x -> x
    | None -> 0

后面这个情况非常适用于配合|>操作符使用,例如:

FSharp 复制代码
let x = Some 1
x |> (function Some x -> x | None -> 0)

在对一个T option的集合类型时,我们就可以很方便的用上面的语法糖来构造一些小的判定函数。

FSharp.Core.Option

而在实际的工作中,上面的这些都是不需要的,因为F#已经提供了FSharp.Core.Option模块来处理option<T>类型的值。下表按照ADT分析的结果,列出了FSharp.Core.Option模块中的函数:

Function or value ADT Description
isSome option ('a option -> bool) 返回 true, 如果对象不是None.
isNone option ('a option -> bool) 返回 true, 如果对象是None.
count option ('a option -> int) count inp 展开成为 match inp with None -> 0 | Some _ -> 1.
get option ('a option -> 'a) 得到option所包含的值.
toObj value ('a option -> 'a) 转换成一个对象.
toNullable option ('a option -> System.Nullable<'a>) 转换成Nullable值.
toArray option ('a option -> 'a array) 转换成一个数组,长度是0或者1.
toList option ('a option -> 'a list) 转换成一个列表,长度是0或者1.
ofObj value ('a -> 'a option) 转换成一个option,空值为None.
ofNullable value (System.Nullable<'a> -> 'a option) 转换成一个option,null变为None.
flatten option ('a option option -> 'a option) flatten inp 展开成为 match inp with None -> None| Some x -> x
contains value option ('a -> 'a option -> bool) 如果option为Some value返回true否则返回false.
forall predicate option (('a -> bool) -> 'a option -> bool) forall p inp 展开成为 match inp with None -> true| Some x -> p x.
exists predicate option (('a -> bool) -> 'a option -> bool) exists p inp 展开成为 match inp with None -> false| Some x -> p x.
defaultValue value option ('a -> 'a option -> 'a) 如果对象Some value得到对应value, 否则返回默认值.
defaultWith defThunk option ((unit -> 'a) -> 'a option -> 'a) 如果对象Some value得到对应value, 否则求值defThunk并返回结果.
iter action option (('a -> unit) -> 'a option -> unit) iter f inp 展开成为 match inp with None -> ()| Some x -> f x.
orElse ifNone option ('a option -> 'a option -> 'a option) 如果option为Some value直接返回, 否则返回ifNone.
bind binder option (('a -> 'b option) -> 'a option -> 'b option) bind f inp 展开成为 match inp with None -> None | Some x -> f x
filter predicate option (('a -> bool) -> 'a option -> 'a option) filter f inp 展开成为 match inp with None -> None| Some x -> if f x then Some x else None.
map mapping option (('a -> 'b) -> 'a option -> 'b option) map f inp 展开成为 match inp with None -> None| Some x -> Some (f x).
orElseWith ifNoneThunk option ((unit -> 'a option) -> 'a option -> 'a option) 如果option为Some value直接返回, 否则求值ifNoneThunk并返回结果.
fold folder state option (('a -> 'b -> 'a) -> 'a -> 'b option -> 'a) fold f s inp 展开成为 match inp with None -> s| Some x -> f s x.
foldBack folder option state (('a -> 'b -> 'b) -> 'a option -> 'b -> 'b) fold f inp s 展开成为 match inp with None -> s| Some x -> f x s.
map2 mapping option1 option2 (('a -> 'b -> 'c) -> 'a option -> 'b option -> 'c option) map f option1 option2 展开成为 match option1, option2 with Some x, Some y -> Some (f x y)| _ -> None.
map3 mapping option1 option2 option3 (('a -> 'b -> 'c -> 'd) -> 'a option -> 'b option -> 'c option -> 'd option) map f option1 option2 option3 展开成为 match option1, option2, option3 with Some x, Some y, Some z -> Some (f x y z) | _ -> None.

其实这许多的功能,只需要略微看看源代码就知道,基本上就是替换为对应match表达式的代码。例如isSome的源代码如下:

FSharp 复制代码
let inline isSome option =
    match option with
    | None -> false
    | Some _ -> true

这样的好处是,可以对一个option<T>的集合类型使用mapfilter等函数,例如:

FSharp 复制代码
let x = [Some 1; None; Some 2]
x |> List.filter Option.isSome

便于写出非常简洁的代码。

结论

  1. 在F#中,使用option<T>来表示缺失数据;
  2. 使用FSharp.Core.Option模块来处理option<T>类型的值;
  3. 使用|>操作符配合function语法糖来处理option<T>类型的值。

Option源代码

FSharp 复制代码
namespace Microsoft.FSharp.Core

open Microsoft.FSharp.Core.Operators

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Option =

    [<CompiledName("GetValue")>]
    let get option =
        match option with
        | None -> invalidArg "option" (SR.GetString(SR.optionValueWasNone))
        | Some x -> x

    [<CompiledName("IsSome")>]
    let inline isSome option =
        match option with
        | None -> false
        | Some _ -> true

    [<CompiledName("IsNone")>]
    let inline isNone option =
        match option with
        | None -> true
        | Some _ -> false

    [<CompiledName("DefaultValue")>]
    let inline defaultValue value option =
        match option with
        | None -> value
        | Some v -> v

    [<CompiledName("DefaultWith")>]
    let inline defaultWith ([<InlineIfLambda>] defThunk) option =
        match option with
        | None -> defThunk ()
        | Some v -> v

    [<CompiledName("OrElse")>]
    let inline orElse ifNone option =
        match option with
        | None -> ifNone
        | Some _ -> option

    [<CompiledName("OrElseWith")>]
    let inline orElseWith ([<InlineIfLambda>] ifNoneThunk) option =
        match option with
        | None -> ifNoneThunk ()
        | Some _ -> option

    [<CompiledName("Count")>]
    let inline count option =
        match option with
        | None -> 0
        | Some _ -> 1

    [<CompiledName("Fold")>]
    let inline fold<'T, 'State> ([<InlineIfLambda>] folder) (state: 'State) (option: 'T option) =
        match option with
        | None -> state
        | Some x -> folder state x

    [<CompiledName("FoldBack")>]
    let inline foldBack<'T, 'State> ([<InlineIfLambda>] folder) (option: option<'T>) (state: 'State) =
        match option with
        | None -> state
        | Some x -> folder x state

    [<CompiledName("Exists")>]
    let inline exists ([<InlineIfLambda>] predicate) option =
        match option with
        | None -> false
        | Some x -> predicate x

    [<CompiledName("ForAll")>]
    let inline forall ([<InlineIfLambda>] predicate) option =
        match option with
        | None -> true
        | Some x -> predicate x

    [<CompiledName("Contains")>]
    let inline contains value option =
        match option with
        | None -> false
        | Some v -> v = value

    [<CompiledName("Iterate")>]
    let inline iter ([<InlineIfLambda>] action) option =
        match option with
        | None -> ()
        | Some x -> action x

    [<CompiledName("Map")>]
    let inline map ([<InlineIfLambda>] mapping) option =
        match option with
        | None -> None
        | Some x -> Some(mapping x)

    [<CompiledName("Map2")>]
    let inline map2 ([<InlineIfLambda>] mapping) option1 option2 =
        match option1, option2 with
        | Some x, Some y -> Some(mapping x y)
        | _ -> None

    [<CompiledName("Map3")>]
    let inline map3 ([<InlineIfLambda>] mapping) option1 option2 option3 =
        match option1, option2, option3 with
        | Some x, Some y, Some z -> Some(mapping x y z)
        | _ -> None

    [<CompiledName("Bind")>]
    let inline bind ([<InlineIfLambda>] binder) option =
        match option with
        | None -> None
        | Some x -> binder x

    [<CompiledName("Flatten")>]
    let inline flatten option =
        match option with
        | None -> None
        | Some x -> x

    [<CompiledName("Filter")>]
    let inline filter ([<InlineIfLambda>] predicate) option =
        match option with
        | None -> None
        | Some x -> if predicate x then Some x else None

    [<CompiledName("ToArray")>]
    let inline toArray option =
        match option with
        | None -> [||]
        | Some x -> [| x |]

    [<CompiledName("ToList")>]
    let inline toList option =
        match option with
        | None -> []
        | Some x -> [ x ]

    [<CompiledName("ToNullable")>]
    let inline toNullable option =
        match option with
        | None -> System.Nullable()
        | Some v -> System.Nullable(v)

    [<CompiledName("OfNullable")>]
    let inline ofNullable (value: System.Nullable<'T>) =
        if value.HasValue then
            Some value.Value
        else
            None

    [<CompiledName("OfObj")>]
    let inline ofObj value =
        match value with
        | null -> None
        | _ -> Some value

    [<CompiledName("ToObj")>]
    let inline toObj value =
        match value with
        | None -> null
        | Some x -> x

module ValueOption =

    [<CompiledName("GetValue")>]
    let get voption =
        match voption with
        | ValueNone -> invalidArg "option" (SR.GetString(SR.optionValueWasNone))
        | ValueSome x -> x

    [<CompiledName("IsSome")>]
    let inline isSome voption =
        match voption with
        | ValueNone -> false
        | ValueSome _ -> true

    [<CompiledName("IsNone")>]
    let inline isNone voption =
        match voption with
        | ValueNone -> true
        | ValueSome _ -> false

    [<CompiledName("DefaultValue")>]
    let inline defaultValue value voption =
        match voption with
        | ValueNone -> value
        | ValueSome v -> v

    // We're deliberately not using InlineIfLambda, because benchmarked code ends up slightly slower at the time of writing (.NET 8 Preview)
    [<CompiledName("DefaultWith")>]
    let inline defaultWith defThunk voption =
        match voption with
        | ValueNone -> defThunk ()
        | ValueSome v -> v

    [<CompiledName("OrElse")>]
    let inline orElse ifNone voption =
        match voption with
        | ValueNone -> ifNone
        | ValueSome _ -> voption

    [<CompiledName("OrElseWith")>]
    let inline orElseWith ([<InlineIfLambda>] ifNoneThunk) voption =
        match voption with
        | ValueNone -> ifNoneThunk ()
        | ValueSome _ -> voption

    [<CompiledName("Count")>]
    let inline count voption =
        match voption with
        | ValueNone -> 0
        | ValueSome _ -> 1

    [<CompiledName("Fold")>]
    let inline fold<'T, 'State> ([<InlineIfLambda>] folder) (state: 'State) (voption: voption<'T>) =
        match voption with
        | ValueNone -> state
        | ValueSome x -> folder state x

    [<CompiledName("FoldBack")>]
    let inline foldBack<'T, 'State> ([<InlineIfLambda>] folder) (voption: voption<'T>) (state: 'State) =
        match voption with
        | ValueNone -> state
        | ValueSome x -> folder x state

    [<CompiledName("Exists")>]
    let inline exists ([<InlineIfLambda>] predicate) voption =
        match voption with
        | ValueNone -> false
        | ValueSome x -> predicate x

    [<CompiledName("ForAll")>]
    let inline forall ([<InlineIfLambda>] predicate) voption =
        match voption with
        | ValueNone -> true
        | ValueSome x -> predicate x

    [<CompiledName("Contains")>]
    let inline contains value voption =
        match voption with
        | ValueNone -> false
        | ValueSome v -> v = value

    [<CompiledName("Iterate")>]
    let inline iter ([<InlineIfLambda>] action) voption =
        match voption with
        | ValueNone -> ()
        | ValueSome x -> action x

    [<CompiledName("Map")>]
    let inline map ([<InlineIfLambda>] mapping) voption =
        match voption with
        | ValueNone -> ValueNone
        | ValueSome x -> ValueSome(mapping x)

    [<CompiledName("Map2")>]
    let inline map2 ([<InlineIfLambda>] mapping) voption1 voption2 =
        match voption1, voption2 with
        | ValueSome x, ValueSome y -> ValueSome(mapping x y)
        | _ -> ValueNone

    [<CompiledName("Map3")>]
    let inline map3 ([<InlineIfLambda>] mapping) voption1 voption2 voption3 =
        match voption1, voption2, voption3 with
        | ValueSome x, ValueSome y, ValueSome z -> ValueSome(mapping x y z)
        | _ -> ValueNone

    [<CompiledName("Bind")>]
    let inline bind ([<InlineIfLambda>] binder) voption =
        match voption with
        | ValueNone -> ValueNone
        | ValueSome x -> binder x

    [<CompiledName("Flatten")>]
    let inline flatten voption =
        match voption with
        | ValueNone -> ValueNone
        | ValueSome x -> x

    [<CompiledName("Filter")>]
    let inline filter ([<InlineIfLambda>] predicate) voption =
        match voption with
        | ValueNone -> ValueNone
        | ValueSome x ->
            if predicate x then
                ValueSome x
            else
                ValueNone

    [<CompiledName("ToArray")>]
    let inline toArray voption =
        match voption with
        | ValueNone -> [||]
        | ValueSome x -> [| x |]

    [<CompiledName("ToList")>]
    let inline toList voption =
        match voption with
        | ValueNone -> []
        | ValueSome x -> [ x ]

    [<CompiledName("ToNullable")>]
    let inline toNullable voption =
        match voption with
        | ValueNone -> System.Nullable()
        | ValueSome v -> System.Nullable(v)

    [<CompiledName("OfNullable")>]
    let inline ofNullable (value: System.Nullable<'T>) =
        if value.HasValue then
            ValueSome value.Value
        else
            ValueNone

    [<CompiledName("OfObj")>]
    let inline ofObj value =
        match value with
        | null -> ValueNone
        | _ -> ValueSome value

    [<CompiledName("ToObj")>]
    let inline toObj value =
        match value with
        | ValueNone -> null
        | ValueSome x -> x
相关推荐
Oneforlove_twoforjob5 分钟前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
engchina21 分钟前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
向宇it21 分钟前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
诚丞成1 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
清梦20201 小时前
经典问题---跳跃游戏II(贪心算法)
算法·游戏·贪心算法
Smile灬凉城6661 小时前
反序列化为啥可以利用加号绕过php正则匹配
开发语言·php
paixiaoxin1 小时前
CV-OCR经典论文解读|An Empirical Study of Scaling Law for OCR/OCR 缩放定律的实证研究
人工智能·深度学习·机器学习·生成对抗网络·计算机视觉·ocr·.net
lsx2024061 小时前
SQL MID()
开发语言
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶1 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv