Ocaml 如何减少此函数中的代码混乱?

Ocaml 如何减少此函数中的代码混乱?,ocaml,Ocaml,下面的函数tally非常简单:它将字符串s作为参数,在非字母数字字符上拆分,并统计结果单词的数字,不区分大小写 open Core.Std let tally s = let get m k = match Map.find m k with | None -> 0 | Some n -> n in let upd m k = Map.add m ~key:k ~data:(1 + get m k) in let re = Str.r

下面的函数tally非常简单:它将字符串s作为参数,在非字母数字字符上拆分,并统计结果单词的数字,不区分大小写

open Core.Std

let tally s =
  let get m k =
    match Map.find m k with
    | None   -> 0
    | Some n -> n
  in

  let upd m k = Map.add m ~key:k ~data:(1 + get m k) in

  let re = Str.regexp "[^a-zA-Z0-9]+" in
  let ws = List.map (Str.split re s) ~f:String.lowercase in

  List.fold_left ws ~init:String.Map.empty ~f:upd
我认为这个函数比它应该读的更难,这是由于混乱。我希望我能写一些更接近这一点的东西,我沉迷于一些幻想的语法:

(* NOT VALID SYNTAX -- DO NOT COPY !!! *)

open Core.Std

let tally s =

  let get m k =
        match find m k with
        | None   -> 0
        | Some n -> n ,

      upd m k = add m k (1 + get m k) ,

      re = regexp "[^a-zA-Z0-9]+" ,
      ws = map (split re s) lowercase 

  in fold_left ws empty upd
我在上面所做的更改主要分为三组:

摆脱重复让。。。在中,将所有绑定合并为一个,-分隔的序列;AFAIK,这不是有效的OCaml; 去掉了函数调用中的~foo:-型噪声; 去掉了前缀Str、List等。
我可以使用有效的OCaml语法实现类似的效果吗?

可读性很难实现,这在很大程度上取决于读者的能力和对代码的熟悉程度。我将只关注语法转换,但如果这是您真正想要的,您也许可以以更紧凑的形式重构代码

要删除模块限定符,只需事先打开它们:

open Str 
open Map
open List
您必须按此顺序打开它们,以确保您在其中使用的列表值仍然可以访问,并且不会被映射值覆盖范围

对于带标签的参数,如果在函数签名顺序中为每个函数调用提供函数的所有参数,则可以省略标签

要减少let…in构造的数量,您有几个选项:

使用一组rec定义:

let tally s =
  let rec get m k =
     match find m k with
     | None   -> 0
     | Some n -> n

  and upd m k = add  m k (1 + get m k)

  and re = regexp "[^a-zA-Z0-9]+" 
  and ws = map lowercase (split re s)

in fold_left ws empty upd
let tally s =
  let module M = struct 
    let get m k =
      match find m k with
      | None   -> 0
      | Some n -> n

    let upd m k = add m k (1 + get m k)

    let re = regexp "[^a-zA-Z0-9]+" 
    let ws = map lowercase (split re s)

end in fold_left ws empty M.upd
一次创建多个定义:

let tally s =
  let get, upd, ws =
    let  re = regexp "[^a-zA-Z0-9]+"  in
    fun m k ->
     match find m k with
     | None   -> 0
     | Some n -> n,
  fun g m k -> add  m k (1 + g m k),
  map lowercase (split re s)

in fold_left ws empty (upd get)
使用模块对定义进行分组:

let tally s =
  let rec get m k =
     match find m k with
     | None   -> 0
     | Some n -> n

  and upd m k = add  m k (1 + get m k)

  and re = regexp "[^a-zA-Z0-9]+" 
  and ws = map lowercase (split re s)

in fold_left ws empty upd
let tally s =
  let module M = struct 
    let get m k =
      match find m k with
      | None   -> 0
      | Some n -> n

    let upd m k = add m k (1 + get m k)

    let re = regexp "[^a-zA-Z0-9]+" 
    let ws = map lowercase (split re s)

end in fold_left ws empty M.upd
后者让人想起Sml语法,也许更适合编译器进行适当的优化,但它只去除in关键字


请注意,由于我不熟悉核心Api,我可能编写了不正确的代码。

可读性很难实现,这在很大程度上取决于读者的能力和对代码的熟悉程度。我将只关注语法转换,但如果这是您真正想要的,您也许可以以更紧凑的形式重构代码

要删除模块限定符,只需事先打开它们:

open Str 
open Map
open List
您必须按此顺序打开它们,以确保您在其中使用的列表值仍然可以访问,并且不会被映射值覆盖范围

对于带标签的参数,如果在函数签名顺序中为每个函数调用提供函数的所有参数,则可以省略标签

要减少let…in构造的数量,您有几个选项:

使用一组rec定义:

let tally s =
  let rec get m k =
     match find m k with
     | None   -> 0
     | Some n -> n

  and upd m k = add  m k (1 + get m k)

  and re = regexp "[^a-zA-Z0-9]+" 
  and ws = map lowercase (split re s)

in fold_left ws empty upd
let tally s =
  let module M = struct 
    let get m k =
      match find m k with
      | None   -> 0
      | Some n -> n

    let upd m k = add m k (1 + get m k)

    let re = regexp "[^a-zA-Z0-9]+" 
    let ws = map lowercase (split re s)

end in fold_left ws empty M.upd
一次创建多个定义:

let tally s =
  let get, upd, ws =
    let  re = regexp "[^a-zA-Z0-9]+"  in
    fun m k ->
     match find m k with
     | None   -> 0
     | Some n -> n,
  fun g m k -> add  m k (1 + g m k),
  map lowercase (split re s)

in fold_left ws empty (upd get)
使用模块对定义进行分组:

let tally s =
  let rec get m k =
     match find m k with
     | None   -> 0
     | Some n -> n

  and upd m k = add  m k (1 + get m k)

  and re = regexp "[^a-zA-Z0-9]+" 
  and ws = map lowercase (split re s)

in fold_left ws empty upd
let tally s =
  let module M = struct 
    let get m k =
      match find m k with
      | None   -> 0
      | Some n -> n

    let upd m k = add m k (1 + get m k)

    let re = regexp "[^a-zA-Z0-9]+" 
    let ws = map lowercase (split re s)

end in fold_left ws empty M.upd
后者让人想起Sml语法,也许更适合编译器进行适当的优化,但它只去除in关键字


请注意,由于我不熟悉核心Api,我可能编写了不正确的代码。

如果对同一个值进行了一系列计算,那么在OCaml中有一个|>操作符,它从左侧获取一个值,并应用于右侧的函数。这可以帮助你摆脱让和进来。关于带标签的参数,您可以通过回到普通的标准库来摆脱它们,并使代码更小,但可读性更低。无论如何,有一小片带标签的参数,你总是可以写f~key~数据而不是f~key:key~数据:数据。最后,模块名可以通过本地打开语法let open List in。。。或者通过本地将其转换为较小的名称,让模块L=列表进入

无论如何,我想向您展示一个代码,它包含更少的混乱,我认为:

open Core.Std
open Re2.Std
open Re2.Infix
module Words = String.Map

let tally s =
  Re2.split ~/"\\PL" s |>
  List.map ~f:(fun s -> String.uppercase s, ()) |>
  Words.of_alist_multi |>
  Words.map ~f:List.length

如果对同一个值进行了一系列计算,那么在OCaml中有一个|>操作符,它从左侧获取一个值,并应用于右侧的函数。这可以帮助你摆脱让和进来。关于带标签的参数,您可以通过回到普通的标准库来摆脱它们,并使代码更小,但可读性更低。无论如何,有一小片带标签的参数,你总是可以写f~key~数据而不是f~key:key~数据:数据。最后,模块名可以通过本地打开语法let open List in。。。或者通过本地将其转换为较小的名称,让模块L=列表进入

无论如何,我想向您展示一个代码,它包含更少的混乱,我认为:

open Core.Std
open Re2.Std
open Re2.Infix
module Words = String.Map

let tally s =
  Re2.split ~/"\\PL" s |>
  List.map ~f:(fun s -> String.uppercase s, ()) |>
  Words.of_alist_multi |>
  Words.map ~f:List.length

对于第一个选项,您实际上不需要使绑定递归,也就是说,让x=2和y=3仍然有效,而且更干净。@ivg实际上您是对的:我可以像在第二个选项中一样解开定义,并完全删除rec绑定!不过,我仍然有两个let作用域,一个用于regexp,另一个用于rest
l可以工作,而且更干净。@ivg实际上你说得很对:我可以像在第二个选项中一样解开定义,并完全删除rec绑定!尽管如此,我仍然有两个let作用域,一个用于regexp,另一个用于rest.fwiw,当我第一次开始使用ocaml时,我对let..in的链也很恼火。这似乎是常见的习语。我现在在心里把它过滤掉。|>管道样式为您保存了let..in,但通常它需要显式使用标记的参数,以使它们按正确的顺序排列|>.fwiw,当我第一次开始使用ocaml时,我也对let..in的链感到恼火。这似乎是常见的习语。我现在在心里把它过滤掉。|>管道样式为您节省了let..in,但通常需要显式使用标记的参数以使它们按正确的顺序排列。|>仅供参考,有一个Map.change是更新映射中条目的更自然的选择:让update m k=String.Map.change m k function None->Some 1 | Some n->Some n+1@ivg:谢谢!顺便说一句,为什么有些人。。。一些n+1,而不仅仅是1 |。。。n+1?我以前见过这样的函数,即只返回一些,但我看不出它们的原理。更改键f是一个瑞士刀函数。它的语义如下:如果key不存在,则用None调用f,否则用一些v调用f,其中v是绑定到key的值。如果函数f返回一些u,则键将反弹到u,如果它不返回,则键将被删除(如果它存在)。@ivg:好的,找到了。谢谢仅供参考,有一个Map.change是更新地图中条目的更自然的选择:让update mk=String.Map.change mk function None->Some 1 | Some n->Some n+1@ivg:谢谢!顺便说一句,为什么有些人。。。一些n+1,而不仅仅是1 |。。。n+1?我以前见过这样的函数,即只返回一些,但我看不出它们的原理。更改键f是一个瑞士刀函数。它的语义如下:如果key不存在,则用None调用f,否则用一些v调用f,其中v是绑定到key的值。如果函数f返回一些u,则键将反弹到u,如果它不返回,则键将被删除(如果它存在)。@ivg:好的,找到了。谢谢