从 Ocaml 中的嵌套条件中删除重复语句

Removing repeated statements from nested conditions in Ocaml

作为 class 中的一个练习,我们应该根据年龄和性别计算夜总会的入场费。 25 岁以下可享受 20% 的折扣,Females/NBs 可享受 50% 的折扣,乘法叠加

虽然我的代码有效,但它重复了两次性别检查,这是一种糟糕的形式,可能会在更复杂的应用程序中引起问题。我怎样才能避免重复?

(* OCaml Version *)
let entry_price age gender =
    if age < 18 
    then (failwith "Must be over 18 to enter")
    else let price = 12.0 in
      if age <= 25
      then let price = (price *. 0.8) in
        if gender == 'f' || gender == 'x'
        then (price *. 0.5)
        else prix
      else if gender == 'f' || gender == 'x'
        then (price *. 0.5)
        else price;;

这是一个不会自我重复的 Python 版本,感谢三元运算符:(也可以使用非嵌套的 ifs 完成,Ocaml 不允许

# Python version
def entry_price(age : int, gender : str):
    if age < 18:
        raise ArgumentError("Must be over 18 to enter")
    return ( 
        12.0
        *(0.8 if (age<25) else 1)
        *(0.5 if gender in ['f', 'x'] else 1)
    )

您几乎可以复制 python 版本:

let entry_price age gender =
  if age < 18 
  then failwith "Must be over 18 to enter"
  else
    12.0
    *. (if age < 25 then 0.8 else 1.0) 
    *. (if gender = 'f' || gender = 'x' then 0.5 else 1.0);;

if 表达式语法只有一点点不同,您需要使用 float-specific 乘法运算符 *.,而不是 int-specific *.

另外,挑个毛病,OCaml 中没有语句。语句是一个 imperative/procedural 构造,即。做这个,然后做那个。 OCaml 是一种 expression-based 语言,其中每个表达式的计算结果都是一个值。您仍然可以丢弃表达式的值,从而使它看起来像一条语句,但编译器会抱怨,除非您非常明确地丢弃它,因为这通常是用户错误。

@glennsl 提供了 Python 代码到 OCaml 的很好的直接翻译,但我们还有其他方法可以解决这个问题。

如果我们创建一个变体类型来描述年龄,我们可以编写一个 categorize_age 函数,它将数字年龄转换为我们可以进行模式匹配的描述性术语。它还为我们在未来提供了一个方便的地方来改变我们用来做出这些决定的年龄。

然后我们可以在agegender上使用match来考虑四种可能的结果:

  • 18 岁以下
  • 25 岁以下 'f''x' 性别
  • 25 岁或以上 'f''x' 性别
  • 任何其他 18 岁或以上的人
type age_cat = Under_age | Legal | Adult

let categorize_age a = 
  if a < 18 then Under_age
  else if a <= 25 then Legal
  else Adult
        
let entry_price age gender = 
  let price = 12.0 in
  match categorize_age age, gender with  
  | Under_age, _       -> failwith "Must be over 18 to enter"
  | Legal, ('f' | 'x') -> price *. 0.8 *. 0.5 (* or just 0.4  *)
  | Adult, ('f' | 'x') -> price *. 0.5 *. 0.5 (* or just 0.25 *)
  | _                  -> price

看这里的逻辑,我们其实不需要指定Adult, ('f' | 'x')因为我们知道我们已经匹配了gender'f'或者[=的情况17=],ageUnder_ageLegal。因此我们可以对模式使用通配符 __, ('f' | 'x').

最后一个模式是通配符,因为我们正在匹配尚未匹配的任何情况,并且这些值与结果无关,因此没有必要将名称绑定到它们。

let entry_price age gender = 
  let price = 12.0 in
  match categorize_age age, gender with  
  | Under_age, _       -> failwith "Must be over 18 to enter"
  | Legal, ('f' | 'x') -> price *. 0.8 *. 0.5 (* or just 0.4  *)
  | _,     ('f' | 'x') -> price *. 0.5 *. 0.5 (* or just 0.25 *)
  | _                  -> price