d - 拆分/拆分的编译问题

标签 d phobos

这是简单的代码:

import std.algorithm;
import std.array;
import std.file;

void main(string[] args)
{
    auto t = args[1].readText()
        .splitter('\n')
        .split("---")
    ;
}

看起来应该可以,但是无法编译。 DMD 2.068.2失败,并显示以下错误:
Error: template std.algorithm.iteration.splitter cannot deduce function from
argument types !()(Result, string), candidates are:
...
Error: template instance std.array.split!(Result, string) error instantiating

如果我在.array之前插入.split,它将进行编译。

我想念什么吗?还是一个错误?我试图在Bug跟踪器中进行简短的搜索,但未找到任何内容。

最佳答案

底线:通常可以通过在有问题的功能之前粘贴一个.array调用来解决此类问题。这为它提供了具有足够功能来运行算法的缓冲区。

以下是该库背后的推理以及您也可以用来实现此目的的其他一些想法:

无法编译的原因与std.algorithm和range背后的理念有关:它们尽可能便宜,可以将成本决策推向最高水平。

在std.algorithm(以及大多数写得很好的范围和范围消耗算法)中,模板约束将拒绝任何无法免费提供所需内容的输入。同样,转换范围(如滤波器,分离器等)将仅返回它们可以以最低成本提供的那些功能。

通过在编译时拒绝它们,它们迫使程序员在最高级别上做出有关如何支付这些费用的决定。您可能会重写该函数以使其工作方式不同,可以使用各种技术自己缓冲该函数以预先支付费用,或者您可以找到其他可行的方法。

因此,这就是您的代码所发生的情况:readText返回一个数组,该数组几乎是功能齐全的范围。 (由于它返回的是由UTF-8构成的string,因此就Phobos而言,它实际上并未提供随机访问权限(尽管令人困惑,语言本身对此有所不同,请在D论坛中搜索“autodecode”争议,您想了解更多),因为在可变长度的utf-8字符列表中找到Unicode代码点需要全部扫描。全部扫描并不是最小的花费,因此Phobos绝不会尝试它,除非您特别要求。)

无论如何,readText返回的范围具有很多功能,包括splitter需要的可扩展性。为什么splitter需要保存?考虑一下它所 promise 的结果:从最后一个分割点开始一直到下一个分割点的一系列字符串。当为最通用的范围编写此代码时,实现可能看起来像什么?

遵循以下原则:首先,save您的起始位置,以便稍后返回。然后,使用popFront,逐步进行查找,直到找到分割点。完成后,将保存的范围返回到分割点。然后,popFront越过分割点并重复该过程,直到消耗完整个东西(while(!input.empty))。

因此,由于splitter的实现需要具有对save起点的能力,因此它至少需要一个正向范围(这只是一个可扩展的范围。Andrei现在觉得这样的命名有点傻,因为名称太多了,但是在在他写std.algorithm的时候,他仍然坚信给他们起全部名字)。

并非所有范围都是正向范围!保存数组就像从当前位置返回一个切片一样容易。许多数值算法也是如此,保存它们只是意味着保留当前状态的副本。如果要转换的范围是可保存的,则大多数转换范围都是可保存的-同样,它们所需要做的就是返回当前状态。

......实际上,我认为您的例子应该是可理解的。实际上,确实存在一个需要谓词并进行编译的重载!

http://dlang.org/phobos/std_algorithm_iteration.html#.splitter.3

    import std.algorithm;
    import std.array;
    import std.stdio;

    void main(string[] args)
    {
            auto t = "foo\n---\nbar"
                    .splitter('\n')
                    .filter!(e => e.length)
                    .splitter!(a => a == "---")
            ;
            writeln(t);
    }

输出:[["foo"], ["bar"]]
是的,它在等同于特定事物的行上进行编译和拆分。另一个重载.splitter("---")无法编译,因为该重载需要切片功能(或狭窄的字符串,Phobos拒绝对其进行一般切片……但知道它实际上仍然可以,所以该功能是特殊情况。整个图书馆。)

但是,为什么它需要切片而不只是保存?老实说,我不知道。也许我也错过了一些东西,但是确实有效的重载的存在对我来说意味着我对算法的理解是正确的。可以通过这种方式完成。我确实相信切片会便宜一些,但是保存版本也足够便宜(您要统计过去弹出的几个项目才能到达拆分器,然后返回saved.take(that_count) ....也许这就是那里的原因。 :您将遍历项目两次,一次是在算法内部,另一次是在外部,而库认为这样做成本很高,因此无法扩展一个级别。(谓词版本通过让您的函数进行扫描来回避这一点,因此Phobos认为这是可行的。不再是问题,您已经知道自己的功能在做什么。)

我可以看到其中的逻辑。不过,我可以双向进行,因为实际上再次运行它的决定仍然在外面,但是我不明白为什么如果没有三思而后行可能就不希望这样做了。

最后,为什么splitter不为其输出提供索引或切片? filter为什么也不提供它?为什么map提供它?

好吧,这又与低成本哲学有关。 map可以提供它(假设它的输入可以提供),因为map实际上并没有改变元素的数量:输出中的第一个元素也是输入中的第一个元素,只是在结果上运行了一些功能。最后是同上,其他之间也一样。
filter改变了这一点。滤除[1,2,3]的奇数只会得到[2]:长度不同,现在在开头而不是中间找到2。但是,直到您真正应用过滤器,您才能知道它的位置-您必须在不缓冲结果的情况下跳来跳去。
splitter与filter类似。它改变了元素的位置,并且算法直到它实际遍历元素时才知道它在哪里拆分。因此,它可以在您迭代时告诉您,但不能在迭代之前出现,因此索引编制将是O(n)速度-计算上过于昂贵。索引应该是非常便宜的。

无论如何,现在我们了解了为什么存在该原理-让您,最终程序员可以对诸如缓存(需要更多的内存而不是可用的内存)或额外的迭代(需要更多的CPU时间而不是免费的)等昂贵的事情做出决定。算法),并通过考虑其实现了解splitter为什么需要它,我们可以研究满足该算法的方法:我们需要使用占用更多CPU周期的版本,并使用我们的自定义代码进行编写比较功能(请参见上面的示例),或以某种方式提供切片。最直接的方法是将结果缓存在数组中。
import std.algorithm;
import std.array;
import std.file;

void main(string[] args)
{
    auto t = args[1].readText()
        .splitter('\n')
        .array // add an explicit buffering call, understanding this will cost us some memory and cpu time
        .split("---")
    ;
}

您也可以在本地或自己缓冲它,以减少分配成本,但是无论如何,成本都必须在某处支付,Phobos偏爱您的程序员,因为程序员了解您的程序需求,并且愿意。支付或不支付这些费用,而不是代您在不告知您的情况下做出决定。

关于d - 拆分/拆分的编译问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33380674/

相关文章:

d - 如何在d中使用libgit的git_repository_open

phobos(D 的标准库)是否包含字符串类型的 "endsWith"?

extension-methods - D 中的扩展方法?

pointers - 为 Phobos 的二进制堆比较指向结构的指针

d - 对 std.algorithm.map 的结果使用 std.array.replace

assembly - 为什么由D编译的main()在64位计算机上具有32位返回值?

types - 将 int 值赋给 char

compiler-construction - 使用 dmd 2.063 构建项目期间临时生成的符号数量是否有限制?

class - D、设置类属性,声明符没有标识符

d - Phobos 库函数用于将 uint 转换为二进制字符串