简体   繁体   English

F#如何避免两次投射

[英]F# How to avoid twice cast

Case 1 情况1

  1. Take elements while is Some value from sequense 从序列中获取价值
  2. Print values of some 打印一些值
let a = seq { yield Some 1; yield Some 2; yield Some 3; yield None }

a
|> Seq.takeWhile Option.isSome // cast 1
|> Seq.map Option.get          // cast 2
|> Seq.iter (printfn "%A")

Case 2 情况二

  1. Filter sequense of Some vaue 一些值的过滤序列
  2. Print values of Some 打印某些值
a
|> Seq.filter Option.isSome    // cast 1
|> Seq.map Option.get          // cast 2
|> Seq.iter (printfn "%A")

Case 3 情况3

  1. Group by elements by type 按类型按元素分组
  2. Print values of each group 打印每组的值
type AB =
    | A of a : int
    | B of b : string

let a = seq{
    yield A 1
    yield A 2
    yield B "ds"
    yield B "fsdf"
}

let (|As|Bs|) = function
    | A _ -> As
    | B _ -> Bs

let matcher = function
    | A a-> printfn "%A" a
    | B b -> printfn "%A" b

a
|> Seq.groupBy (|As|Bs|)       // cast 1
|> Seq.map snd
|> Seq.iter (Seq.iter matcher) // cast 2

Why do I need to avoid double casting? 为什么我需要避免重复铸造?

  • To keep the code cleaner 保持代码清洁
  • To avoid throwing exceptions 为了避免引发异常

For "Case 2" you can use Seq.choose with the identity function, id : 对于“案例2”,您可以将Seq.choose与身份函数id

a
|> Seq.choose id
|> Seq.iter (printfn "%A")

The documentation for Seq.choose says Seq.choose文档

Applies the given function to each element of the list and returns the list comprised of the results for each element where the function returns Some with some value. 将给定函数应用于列表的每个元素,并返回由每个元素的结果组成的列表,其中函数返回带有某些值的Some。

Passing it the identity function will therefore return the content of every Option value that is Some . 传递给它的身份函数将因此返回每个Some Option值的内容。

If you're working with lists, you can do the same thing you do in Haskell (which, judging by your comments, you know already). 如果您使用列表,则可以在Haskell中执行相同的操作(从您的评论来看,您已经知道)。 F# also has pattern matching, and it works much the same way, except the language is not lazy, so you'd have to account for that. F#还具有模式匹配,它的工作方式几乎相同,除了该语言不是惰性的,因此您必须考虑到这一点。 For example: 例如:

let rec case2 xs = 
    match xs with
    | (Some x)::rest -> printfn "%A" x; case2 rest
    | None::rest -> case2 rest
    | [] -> ()

(note: if you're doing explicit recursion, it's a good idea to keep it to the "tail" variety; in Haskell this doesn't matter much due to laziness, but in .NET you can easily blow the stack if you're not careful) (请注意:如果您要进行显式递归,最好将其保留为“ tail”变量;由于懒惰,在Haskell中这无关紧要,但是在.NET中,如果不小心)


If you're working with sequences, the situation is a bit more complicated. 如果您要处理序列,情况会更加复杂。 For some things you can use computation expressions (they are somewhat related to the do notation in Haskell): 对于某些事情,您可以使用计算表达式(它们与Haskell中的do表示法有些相关):

let case2 xs = seq {
    for x in xs do
        match x with | Some a -> yield a | None -> ()
}

Or in some cases, standard library functions: 或在某些情况下,标准库函数:

let case2 xs = Seq.choose id xs

(note: the above example can't be eta-reduced due to value restriction) (注意:由于值限制,无法对上述示例进行eta归约)

But your first example ("early stop") can't be expressed declaratively like that. 但是您的第一个示例(“提前停止”)不能这样声明性地表示。 You still have to use takeWhile , but at least you can then use choose instead of map to avoid using partial functions: 你仍然需要使用takeWhile ,但至少你可以再使用choose ,而不是map ,以避免使用部分功能:

let case1 xs = xs |> Seq.takeWhile Option.isSome |> Seq.choose id

If you really-really want to have a single pattern match instead of two, it is possible to go one level deeper and use the IEnumerable interface directly: 如果您确实希望单个模式匹配而不是两个模式匹配,则可以更深入一层并直接使用IEnumerable接口:

let case1 (xs: seq<_>) = seq {
  use e = xs.GetEnumerator()
  let mutable stop = false

  while not stop && e.MoveNext() do
    match e.Current with
    | Some x -> yield x
    | None -> stop <- true
}

Note that this uses mutable variables, and while it looks ugly at first glance, it's worth remembering that iterating over .NET sequences (aka IEnumerable<'t> ) is an inherently mutation-based process. 请注意,这使用可变的变量,乍一看看上去很难看,但值得记住的是,在.NET序列(又名IEnumerable<'t> )上进行迭代是一个固有的基于突变的过程。 Look above: the e.MoveNext() call changes the state of enumerator e . 看上面: e.MoveNext()调用更改了枚举数e的状态。 You have to deal with this fact if you want to go down to this level. 如果您想下降到这个水平,则必须处理这个事实。

And of course, I could have eliminated the stop variable by replacing it with recursion: 当然,我可以通过将其替换为递归来消除stop变量:

let case1 (xs: seq<_>) = seq {
  use e = xs.GetEnumerator()

  let rec loop() = seq {
    if e.MoveNext() then
      match e.Current with
      | Some x -> yield x; yield! loop()
      | None -> ()
  }

  yield! loop()
}

But this is rather silly: if I'm willing to deal with the mutable enumerator, I might as well just go all in. 但是,这很愚蠢:如果我愿意处理可变的枚举器,那么我不妨全力以赴。


As for your third example - I don't understand what you're even trying to do there. 至于您的第三个示例 -我什至不知道您在尝试做什么。 Or, rather, I do understand what it does, but I don't see what it would mean to get rid of the second match. 或者,我确实了解它的作用,但我看不出摆脱第二场比赛意味着什么。 Perhaps you could illustrate how you'd do the same thing in Haskell? 也许您可以说明如何在Haskell中做同样的事情?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM