簡體   English   中英

F#如何避免兩次投射

[英]F# How to avoid twice cast

情況1

  1. 從序列中獲取價值
  2. 打印一些值
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")

情況二

  1. 一些值的過濾序列
  2. 打印某些值
a
|> Seq.filter Option.isSome    // cast 1
|> Seq.map Option.get          // cast 2
|> Seq.iter (printfn "%A")

情況3

  1. 按類型按元素分組
  2. 打印每組的值
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

為什么我需要避免重復鑄造?

  • 保持代碼清潔
  • 為了避免引發異常

對於“案例2”,您可以將Seq.choose與身份函數id

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

Seq.choose文檔

將給定函數應用於列表的每個元素,並返回由每個元素的結果組成的列表,其中函數返回帶有某些值的Some。

傳遞給它的身份函數將因此返回每個Some Option值的內容。

如果您使用列表,則可以在Haskell中執行相同的操作(從您的評論來看,您已經知道)。 F#還具有模式匹配,它的工作方式幾乎相同,除了該語言不是惰性的,因此您必須考慮到這一點。 例如:

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

(請注意:如果您要進行顯式遞歸,最好將其保留為“ tail”變量;由於懶惰,在Haskell中這無關緊要,但是在.NET中,如果不小心)


如果您要處理序列,情況會更加復雜。 對於某些事情,您可以使用計算表達式(它們與Haskell中的do表示法有些相關):

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

或在某些情況下,標准庫函數:

let case2 xs = Seq.choose id xs

(注意:由於值限制,無法對上述示例進行eta歸約)

但是您的第一個示例(“提前停止”)不能這樣聲明性地表示。 你仍然需要使用takeWhile ,但至少你可以再使用choose ,而不是map ,以避免使用部分功能:

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

如果您確實希望單個模式匹配而不是兩個模式匹配,則可以更深入一層並直接使用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
}

請注意,這使用可變的變量,乍一看看上去很難看,但值得記住的是,在.NET序列(又名IEnumerable<'t> )上進行迭代是一個固有的基於突變的過程。 看上面: e.MoveNext()調用更改了枚舉數e的狀態。 如果您想下降到這個水平,則必須處理這個事實。

當然,我可以通過將其替換為遞歸來消除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()
}

但是,這很愚蠢:如果我願意處理可變的枚舉器,那么我不妨全力以赴。


至於您的第三個示例 -我什至不知道您在嘗試做什么。 或者,我確實了解它的作用,但我看不出擺脫第二場比賽意味着什么。 也許您可以說明如何在Haskell中做同樣的事情?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM