MixedPipeDirectionAnalyzer
Problem
The forward pipes (|>, ||>, |||>) and the backward pipes (<|, <||, <|||) are left-associative and share a precedence level, so they can be chained together in a single expression.
When both directions appear in the same chain, the reader has to start in the middle and work outwards two ways at once.
// Triggers analyzer
let add x y = x + y
let a b c = b |> add <| c
let wrap prefix suffix = prefix + suffix
let d items =
items
|> List.map string
|> String.concat ", "
|> wrap
<| "!"
Arity does not matter, only direction. Every combination of a forward pipe with a backward pipe is reported:
// Triggers analyzer
let add3 x y z = x + y + z
let e x y z = (x, y) ||> add3 <| z
let f x y z = x |> add3 <|| (y, z)
let add x y = x + y
let g x y = add <|| (x, y) |> string
Fix
Pick one direction. The following equivalent version uses only forward pipes and does not trigger the analyzer:
// Does not trigger analyzer
let wrap prefix suffix = prefix + suffix
let forwardPipeline items =
items
|> List.map string
|> String.concat ", "
|> fun joined -> wrap joined "!"
When the chain resists a single direction, bind the intermediate value with let:
// Does not trigger analyzer
let wrap prefix suffix = prefix + suffix
let boundPipeline items =
let joined =
items
|> List.map string
|> String.concat ", "
wrap joined "!"
A chain that runs in one direction is not reported, whatever mix of arities it uses. A backward pipe on its own stays available as the idiomatic way to drop a trailing parenthesis around a multi-line argument:
// Does not trigger analyzer
let h b = failwith <| sprintf "unexpected: %s" b
let add x y = x + y
let i x y = (x, y) ||> add |> string
Scope
Only operators on the same chain are compared. Parenthesizing an inner pipeline makes it a separate expression. A pipe nested inside a lambda or a parenthesized argument belongs to a different expression and is left alone:
// Does not trigger analyzer
let lambdaNestedPipeline items =
items
|> List.map (fun item -> string <| item + 1)
|> List.length
let parenthesizedInnerPipeline items =
ignore <| (items |> List.length)
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val string: value: 'T -> string
--------------------
type string = System.String
ionide-analyzers