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

option

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

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

type FSharpOption<T> =
    | None
    | Some of T

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

let x = Some 1
let y = None

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

let z = 
    match x with
    | Some x -> x
    | None -> 0

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

let f (x: int option) =
    match x with
    | Some x -> x
    | None -> 0

可以写成:

let f = 
    function
    | Some x -> x
    | None -> 0

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

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 valueADTDescription
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的源代码如下:

let inline isSome option =
    match option with
    | None -> false
    | Some _ -> true

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

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源代码

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
posted @ 2023-09-09 17:11  大福是小强  阅读(9)  评论(0编辑  收藏  举报  来源