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

时间:2014-11-09 23:09:03

标签: 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.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

我上面做的改变主要分为三组:

  1. 删除重复的let ... in,合并所有绑定(进入, - 分开的序列;这个,AFAIK,无效OCaml);
  2. 摆脱了函数调用中的~foo:类型噪音;
  3. 删除了前缀Str.List.
  4. 我可以使用有效的OCaml语法实现类似的效果吗?

2 个答案:

答案 0 :(得分:3)

可读性难以实现,它在很大程度上取决于读者的能力和对代码的熟悉程度。我将仅关注语法转换,但如果您正在寻找的话,您可以以更紧凑的形式重构代码。

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

open Str 
open Map
open List

您必须按顺序打开它们,以确保您在那里使用的List值仍然可以访问,而不是Map覆盖范围。

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

要减少let...in构造的数量,您有以下几种选择:

  1. 使用一组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
    
  2. 一次制作多个定义:

    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)
    
  3. 使用模块对定义进行分组:

    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
    
  4. 后者让人联想到Sml语法,或许更适合编译器进行适当的优化,但它只能删除in个关键字。

    请注意,由于我不熟悉Core Api,我可能写错了代码。

答案 1 :(得分:3)

如果您对相同的值有一系列计算,那么在OCaml中有一个|>运算符,它从左侧获取一个值,并应用于右侧的函数。这可以帮助你摆脱"摆脱" letin。关于标记的参数,然后您可以通过回退到一个vanilla标准库来摆脱它们,并使您的代码更小,但可读性更低。无论如何,有一小块带有标记参数的糖,你总是可以写f ~key ~data而不是f ~key:key ~data:data。最后,模块名称可以通过本地开放语法(let open List in ...)删除,也可以通过本地将其删除为较小的名称(let module L = List in)。

无论如何,我想向你展示一个包含较少杂乱的代码,我认为:

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