2

I often find myself using the |> operator when constructing functions to pass as parameters to methods like List.find. For example, I have this code snippet:

let parse_test_header (header : string list) : string * (string list) =
  let is_attr (line : string) : bool = ':' == get line 0 in
  let test_name = List.find (fun line -> line |> is_attr |> not) header in
  let test_attrs = List.filter is_attr header in
  (test_name, test_attrs)

For simplicity I would like to use |> without having to wrap it in a fun ... -> ... first, something like:

let parse_test_header (header : string list) : string * (string list) =
  let is_attr (line : string) : bool = ':' == get line 0 in
  let test_name = List.find (is_attr |> not) header in
  let test_attrs = List.filter is_attr header in
  (test_name, test_attrs)

However, this gives

31 |   let test_name = List.find (is_attr |> not) header in
                                  ^^^^^^^
Error: This expression has type string -> bool
       but an expression was expected of type bool

Is there some way to accomplish this?

1 Answer 1

4

"Reverse application" vs. composition

Consider how |> is implemented:

let ( |> ) x f = f x

You're passing is_attr as the first argument, which is a function, which means not would need to take a function as its argument. It doesn't so you get a type mismatch.

You're looking for function composition which can be tricky when used to compose polymorphic functions due to the value restriction, but can be implemented. Let's call this operator |..

let ( |. ) f g x = g (f x)

Now:

let parse_test_header (header : string list) : string * (string list) =
  let is_attr (line : string) : bool = ':' == get line 0 in
  let test_name = List.find (is_attr |. not) header in
  let test_attrs = List.filter is_attr header in
  (test_name, test_attrs)

Note that in your is_attr function you're using physical equality (==) rather than structural equality (=). This may not always do what you expect.

Making composition unnecessary

You're probably also looking for List.partition. The composition of is_attr and not is no longer necessary if we bind test_name to the second list resulting from the call List.partition is_attr header indicating results which are not attributes.

let parse_test_header (header : string list) : string * (string list) =
  let is_attr (line : string) : bool = ':' = get line 0 in
  let (test_attrs, test_name) = List.partition is_attr header in
  (List.hd test_name, test_attrs)

Additionally, is_attr can be defined in terms of String.starts_with.

let parse_test_header (header : string list) : string * (string list) =
  let is_attr = String.starts_with ~prefix: ":" in
  let (test_attrs, test_name) = List.partition is_attr header in
  (List.hd test_name, test_attrs)

Associativity

Now, |. associates left to right. This means we can compose similarly to the way |> works.

fun x -> x |> f |> g |> h

Is equivalent to:

f |. g |. h

If you wished to emulate Haskell's . operator for composition, you'd want right-to-left associativity. Associativity of OCaml operators is governed by the first character in the operator.

fun x -> f @@ g @@ h x

Is equivalent to:

f @. g @. h

Where @. is defined:

let ( @. ) f g x = f (g x)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.