Erlang:嵌套案例

Erlang : nested cases

我是 Erlang 的新手。我试图找出列表索引是否超出范围(在尝试之前)所以我想用类似

的东西做一个 if 子句
if lists:flatlength(A) < DestinationIndex ....

我发现这些函数结果不能用在 if 守卫中,所以我改用了 case。这导致嵌套的 case 语句

case Destination < 1 of
    true -> {ok,NumberOfJumps+1};
    false ->
    case lists:flatlength(A) < Destination of
        true -> 
            doSomething;
        false ->
            case lists:member(Destination,VisitedIndices) of
                true -> doSomething;
                false -> 
                    doSomethingElse
            end
    end
end.

我发现这在可读性和代码风格方面很糟糕。这是你在 erlang 中做类似事情的方式还是有更优雅的方式来做到这一点?

提前致谢

在您将以下内容视为神奇的福音之前,请注意此函数的输入方式几乎肯定是不合常理的。在达到这一点之前,您应该寻求限制案例的方式——对嵌套案例的需求本身通常是一种代码味道。有时这确实是不可避免的,但我强烈怀疑可以在代码的早期简化一些方面(尤其是对正在传递的数据结构及其含义进行更多思考)。

没有看到变量 A 的来源,我在这里将其作为参数。另外,在没有看到这个函数是如何输入的情况下,我正在构建一个函数头,因为如果没有函数的其余部分,很难确定地说什么。

综上所述,让我们稍微重构一下:

首先,我们想要摆脱我们知道可以进入守卫的一件事,那就是你的第一个 case 检查是否 Destination < 1。让我们考虑一下我们真的想调用一个公共函数的两个不同子句,而不是使用大小写:

foo(Destination, NumberOfJumps, _, _) when Destination < 1 ->
    {ok, NumerOfJumps + 1};
foo(Destination, _, VisitedIndices, A) ->
    case lists:flatlength(A) < Destination of
        true -> doSomething;
        false ->
            case lists:member(Destination,VisitedIndices) of
               true  -> doSomething;
               false -> doSomethingElse
            end
    end.

不太奇怪。但是那些仍然存在的嵌套案例......有些事情对他们来说很烦人。这是我怀疑可以在其他地方做一些事情来减少在代码中更早的路径选择的地方。但是让我们假装你无法控制那些东西。在这种情况下,布尔值赋值和 if 可以提高可读性:

foo(Destination, NumberOfJumps, _, _) when Destination < 1 ->
    {ok, NumberOfJumps + 1};
foo(Destination, _, VisitedIndices, A) ->
    ALength = lists:flatlength(A) < Destination,
    AMember = lists:member(Destionation, VisitedIncides),
    NextOp =
        if
            ALength     -> fun doSomething/0;
            AMember     -> fun doSomething/0;
            not AMember -> fun doSomethingElse/0
        end,
    NextOp().

这里我直接切入正题,通过将结果分配给一个变量来确保我们只执行一次可能代价高昂的操作——但这​​让我非常不舒服因为我本来就不该落到这种地步的。

无论如何,像这样的东西应该与之前的代码一样测试,并且在此期间可能更具可读性。但是你应该寻找其他地方来简化。特别是,这个VisitedIndices业务感觉有点可疑(为什么我们不知道Destination是否是会员?),变量A需要在我们到达这个之后展平功能很奇怪(为什么它还没有被压平?为什么有这么多?),NumberOfJumps感觉有点像累加器,但它的存在是神秘的。

您可能会问,是什么让我对这些变量感到奇怪?唯一一贯使用的是 Destination -- 其他的仅在 foo/4 的一个子句或另一个子句中使用,但不能同时使用。这让我认为这应该是执行链上游某处的不同执行路径,而不是在超级决策类型函数中全部结束。

编辑

通过更全面地描述手头的问题(参考下面评论中的讨论),考虑这是如何解决的:

-module(jump_calc).
-export([start/1]).

start(A) ->
    Value = jump_calc(A, length(A), 1, 0, []),
    io:format("Jumps: ~p~n", [Value]).

jump_calc(_, Length, Index, Count, _) when Index < 1; Index > Length ->
    Count;
jump_calc(Path, Length, Index, Count, Visited) ->
    NewIndex = Index + lists:nth(Index, Path),
    NewVisited = [Index | Visited],
    NewCount = Count + 1,
    case lists:member(NewIndex, NewVisited) of
        true  -> NewCount;
        false -> jump_calc(Path, Length, NewIndex, NewCount, NewVisited)
    end.

始终尝试尽可能多地预先加载处理,而不是一遍又一遍地执行相同的计算。考虑一下我们可以多么轻松地将每次迭代都挡在守卫后面,以及我们 甚至因此不必编写多少条件性内容。函数匹配是一个强大的工具——一旦掌握了它,您就会真正开始享受 Erlang。