【问题标题】:Erlang style - case vs function pattern matchingErlang 风格 - 案例与函数模式匹配
【发布时间】:2023-03-26 09:46:01
【问题描述】:

我已经到了编写相当多 Erlang 代码的阶段,并且我可以看到一些风格(坏的或好的)潜入了我一直在编写它的方式中。我想对这个特殊的习语发表一些意见 - 将 case 样式语句转换为函数模式匹配是否更好(更具可读性/更快/无论如何)?

例如

比较(一个人为的例子)

case {Size > 100000, Type} of
    {true, ets } ->
         %% Do something to convert to dets
         something;
    {false, dets} ->
         %% do something to convert to ets
         somethingelse;
    _ ->
         ignoreit
end;

...
maybeChangeStorage(Size, Type)
...

maybeChangeStorage(Size, ets) when Size > 10000 ->
   something;
maybeChangeStorage(Size, dets) when Size < 10000 ->
   somethingelse;
maybeChangeStorage(_,_) ->
   ignoreit.

在大多数情况下我更喜欢后者,但我会对其他意见感兴趣。

【问题讨论】:

  • maybeChangeStorage(Size, dets) 当 Size 应该是 Size

标签: coding-style erlang


【解决方案1】:

Learn you some Erlang for great good 在何时选择 case 以及何时使用 function 上有一个 small section。提到了两件事:

  1. 它们在 VM 中的表示方式相同,因此两种解决方案的性能没有差异。

  2. 如果您需要对多个参数使用防护,使用函数可能会更好。

总而言之,多半是风格和品味的问题。

【讨论】:

    【解决方案2】:

    如果在你的函数中你要做的第一件事是打开一个 case 子句,最好将这个顶级子句转换为函数模式匹配。

    【讨论】:

      【解决方案3】:

      (作为答案来获取代码的格式...!)

      我在进行一些更改时确实发现的一件事是 这种方法可以改变默认短路。例如。

      case A > 10 of 
            true -> 
                   case B > 10 of 
                        true -> dummy1; 
                        false -> dummy2 
                   end; 
            false -> dummy3 
      end 
      

      如果您像这样调用它,则必须始终执行 B > 10

      doTest(A > 10, B > 10) 
      

      doTest(true, true) -> dummy1; 
      doTest(true, false) -> dummy2; 
      doTest(false, _) -> dummy3. 
      

      这有时不是你想要的!

      【讨论】:

        【解决方案4】:

        第二种是首选方式,尤其是如果您可以将子句保留在一行中:

        maybeCngStor(Sz, ets)  when Sz > 10000 -> something;
        maybeCngStor(Sz, dets) when Sz < 10000 -> somethingelse;
        maybeCngStor(_,_)                      -> ignoreit.
        

        使其非常易于阅读和推理。始终选择将来最容易阅读的样式。通常你会发现一组子句,其中一个是 10 行,其余的只有一行 - 将长的一个分解为一个函数:

        maybeCngStor(Sz, ets)  when Sz > 10000 -> something;
        maybeCngStor(Sz, dets) when Sz < 10000 -> somethingelse();
        maybeCngStor(_,_)                      -> ignoreit.
        
        somethingelse() ->
           (...)
           Return.
        

        诸如排列子句以对齐它们并使用短变量名之类的小事情很重要 - 但不要陷入将所有内容都更改为 P、Q、R 的陷阱。

        如果您经常使用记录,一个好技巧是将记录匹配到短变量:

        #record{foo = F, bar = B, baz = Bz} = Parameter
        

        这为您提供了简短的变量名称,当您在明年圣诞节从 10,000 英尺跳伞到函数中寻找错误时,这些名称是有意义的。 F 显然是一个 Foo 等等等等...

        【讨论】:

        • 我真的很喜欢大多数情况下的功能匹配。虽然我的循环通常是这样的:先写case语句。稍后重构为函数。我倾向于在代码出现时编写代码,然后进行大量重构。我越接近一条线,我就越快乐。
        【解决方案5】:

        您可以通过以下方式使这些示例更加相似:

        case Type of
           ets  when Size > 10000 -> ...;
           dets when Size < 10000 -> ...;
           _ -> ...
        end.
        

        这对我来说似乎更清楚。将其拆分为单独的函数的优点是您可以为其命名,该名称充当文档并出现在堆栈跟踪中。如果该 sn-p 是更大功能的一部分,我会将其分开,否则就可以了。

        值得考虑的一点是,函数编写的错误情况将接受 ets/dets 以外的类型参数。除非这确实是您想要的,否则值得使该条款更具限制性。

        【讨论】:

        • 使用 _ 作为 catch all 子句对我来说总是有一种难闻的气味——我通常把它排除在外,让模式匹配把它扔掉。但在这种特殊情况下,如果不匹配,我什么也不想做。我喜欢将守卫放在案例条款中的另一种方法......
        【解决方案6】:

        对我来说,第一种风格更清晰,可能更快。但它需要测试才能准确地说出来。 在第二种情况下,如果 type!=ets 则 "Size > 10000" 和 "Size

        【讨论】:

        • 是的,我从内存中编写了片段,因此逻辑可能有点偏差。我喜欢第一种风格的一件事是元组的神奇构造来进行模式匹配——我以前用过 {Test1, Test2, Test3, Test4} 之类的东西,然后有一整套 case 子句对于我真正感兴趣的特定组合。它击败了一整套嵌套的 if 语句!
        • 担心测试变量大小的成本是微优化。编写尽可能清晰的代码,并且当您根据测试表明此构造的此特定实例太慢时,然后才更改它。
        猜你喜欢
        • 2011-09-29
        • 1970-01-01
        • 2013-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-27
        • 2014-08-12
        • 1970-01-01
        相关资源
        最近更新 更多