【问题标题】:controlling program flow without if-else / switch-case statements在没有 if-else / switch-case 语句的情况下控制程序流程
【发布时间】:2013-02-19 21:17:55
【问题描述】:

假设我有 1000 个函数,定义如下

void func dummy1(int a);
void func dummy2(int a, int aa);
void func dummy3(int a, int aa, int aaa);
.
.
.
void func dummy1000(int a, int aa, int aaa, ...);

我想编写一个函数,它接受一个整数 n (n

在我看来,如果不在运行时重新编译,这是无法实现的,因此像 java、c、c++ 这样的语言永远不会让这样的事情发生。

希望有办法做到这一点。如果是这样,我很好奇。

注意:这不是我永远不会使用的东西,我只是出于好奇而提出问题。

【问题讨论】:

  • 你以前看过反射吗?
  • 它的“n args”部分是偶然的,还是您希望找到解决方案的示例?如果是这样,请查看en.wikipedia.org/wiki/Stdarg.h
  • "N args" 部分不是偶然的,我故意将其包括在内。我现在已经检查了那个库,但是我认为如果你希望你的函数有一定数量的参数但在不使用控制语句的情况下调用它们并没有帮助。
  • 一个非常短的 shell 脚本会生成你似乎绝望的千头 case 语句。
  • 是的,毫无疑问会

标签: function programming-languages function-pointers program-flow


【解决方案1】:

在现代函数式语言中,您可以制作一个以列表作为参数的函数列表。这可以说可以解决您的问题,但也可以说是作弊,因为它并不完全是您的问题所暗示的静态类型实现。然而,在使用“手动”参数处理时,这几乎是 Python、Ruby 或 Perl 等动态语言所做的......

无论如何,Haskell 中有以下内容:它为nth 函数(从它的第一个参数fs)提供第二个参数(x)的n 副本列表,并返回结果。当然,您需要以某种方式将函数列表放在一起,但与 switch 语句不同,此列表可以作为一等参数重用。

selectApplyFunction :: [ [Int] -> a ] -> Int -> Int -> a
selectApplyFunction fs x n = (fs !! (n-1)) (replicate n x)

dummy1 [a] = 5 * a
dummy2 [a, b] = (a + 3) * b
dummy3 [a, b, c] = (a*b*c) / (a*b + b*c + c*a)
...
myFunctionList = [ dummy1, dummy2, dummy3, ... ]

-- (myfunction n) provides n copies of the number 42 to the n'th function
myFunction = selectApplyFunction myFunctionList 42

-- call the 666'th function with 666 copies of 42
result = myFunction 666

当然,如果n 大于函数的数量,或者如果函数无法处理给定的列表,则会出现异常。还要注意,它是糟糕的 Haskell 风格——主要是因为它滥用列表来(滥用)解决您的问题的方式......

【讨论】:

  • 毫无疑问,解释型语言会以某种方式完成这项工作,因为它们有机会在执行之前解释输入,如本例所示。我只是想知道如果语言无法解释运行时输入,这是否也是可能的。
  • 我认为存在一些误解:Haskell 是一种静态类型语言,其主要实现(GHC)是编译器。编译语言和解释语言同样能够解释运行时输入。
  • 我的次要观点是,我的 Haskell 解决方案是“手动”(使用静态类型列表)动态语言隐式实现的(作为其参数处理机制的一部分)。我的主要观点是我的解决方案可能已经回答了所提出的问题,但可能没有很好地满足你的好奇心——所以,你可能需要做更多的思考,更多的研究,并提出一个更好的问题 8^ )
  • 编译器应该准确地知道参数的数量,以便在调用函数之前生成将参数压入堆栈的指令。当调用哪个函数以及所需的参数数量直接取决于用户的输入时,应使用流控制指令编写代码,这会导致汇编中的许多跳转指令。但是,在解释语言中,解释器可以在执行时解释命令以生成“一些新指令”,在这种情况下,您不需要源代码中的任何控制流语句,就像在您的示例中一样。
  • 不,我的问题最初是如何在没有任何动态重新编译的情况下实现这一点,就像反射和您的解决方案所做的那样,我不满意。
【解决方案2】:

不,你错了。大多数现代语言都支持某种形式的反射,它允许您按名称调用函数并将参数传递给它。

【讨论】:

  • 反射是否允许我从可以在运行时初始化和分配的字符串中传递参数?
【解决方案3】:

您可以使用大多数现代语言创建函数数组。 在伪代码中,

var dummy = new Array();
dummy[1] = function(int a);
dummy[2] = function(int a, int aa);
...

var result = dummy[whateveryoucall](1,2,3,...,whateveryoucall);

【讨论】:

  • 这个方案如何处理不同数量的函数参数?
【解决方案4】:

在函数式语言中,您可以这样做,但在强类型语言(如 Haskell)中,函数必须具有相同的类型:

funs = [reverse, tail, init]   -- 3 functions of type [a]->[a]

run fn arg = (funs !! fn) $ args  -- applies function at index fn to args

【讨论】:

    【解决方案5】:

    在面向对象的语言中,您可以同时使用function objects 和反射来实现您想要的。通过将适当的 POJO(调用 C 结构)传递给函数对象来解决参数数量可变的问题。

    interface Functor<A,B> {
        public B compute(A input);
    }
    
    class SumInput {
        private int x, y;
        // getters and setters
    }
    
    class Sum implements Functor<SumInput, Integer> {
    
        @Override
        public Integer compute(SumInput input) {
           return input.getX() + input.getY();
        }
    
    }
    

    现在假设您有大量这些“函子”。您将它们收集在一个配置文件中(可能是一个 XML 文件,其中包含有关每个函子、使用场景、说明等的元数据)并将列表返回给用户。
    用户选择其中之一。通过使用反射,您可以看到所需的输入和预期的输出。用户填写输入,然后使用反射实例化仿函数类 (newInstance()),调用 compute() 函数并获取输出。
    添加新的仿函数时,只需更改配置文件中的仿函数列表即可。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-27
      • 1970-01-01
      • 2018-10-25
      • 2022-08-23
      • 1970-01-01
      • 2015-08-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多