我们可以枚举构造函数,而不是直接使用变量。因此,我们需要涵盖四种情况:
concatenation:: MyList a -> MyList a -> MyList a
concatenation Empty Empty = -- ...
concatenation Empty (Element b bs) = -- ...
concatenation (Element a as) Empty = -- ...
concatenation (Element a as) (Element b bs) = -- ...
现在我们可以着手解决这些问题。连接两个Empty 列表将返回一个空列表,因此Empty。
concatenation Empty Empty = Empty
如果第一个列表是Empty,而后者不是,那么我们返回第二个列表。
concatenation Empty (Element b bs) = Element b bs
如果第一个元素是一个列表,而后者不是,那么我们可以返回第一个列表:
concatenation (Element a as) Empty = Element a as
最后,如果两个列表都不为空,则结果列表从第一个列表的第一个元素开始,然后是第一个列表的尾部和第二个列表的串联:
concatenation (Element a as) (Element b bs) = Element a (concatenation as (Element b bs))
这给了我们以下代码:
concatenation:: MyList a -> MyList a -> MyList a
concatenation Empty Empty = Empty
concatenation Empty (Element b bs) = Element b bs
concatenation (Element a as) Empty = Element a as
concatenation (Element a as) (Element b bs) = Element a (concatenation as (Element b bs))
我们可以用 as patterns 来缩短实现:
concatenation:: MyList a -> MyList a -> MyList a
concatenation Empty Empty = Empty
concatenation Empty bl@(Element b bs) = bl
concatenation al@(Element a as) Empty = al
concatenation (Element a as) bl@(Element b bs) = Element a (concatenation as bl)
但这相当冗长,而且它在两个列表上执行模式匹配,这可能很昂贵:例如,如果第二个列表首先需要昂贵的计算,并且第一个列表足够长(甚至无限),那么我们将无论如何都不要对第二个列表感兴趣。
因此,我们可以查看是否可以压缩子句的数量。前两行可以组合在一起,来自:
concatenation Empty Empty = Empty
concatenation Empty (Element b bs) = Element b bs
到:
concatenation Empty bl = bl
因为不管第二个列表的值是多少,我们无论如何都会返回它。它还为我们节省了潜在的评估,这可以产生更有效的代码。
我们可以对最后两行做类似的事情吗?
concatenation (Element a as) Empty = Element a as
concatenation (Element a as) bl@(Element b bs) = Element a (concatenation as bl)
因此,两者都有一个Element 构造函数,并且a 作为第一个元素。由于as 和Empty 的串联最终是concatenation as Empty = as,因此我们也可以在前一种情况下执行递归调用,并使用:
concatenation (Element a as) bl = Element a (concatenation as bl)
这会导致:
concatenation :: MyList a -> MyList a -> MyList a
concatenation Empty bl = bl
concatenation (Element a as) bl = Element a (concatenation as bl)
我们可以在这里省略第二个参数:
concatenation :: MyList a -> MyList a -> MyList a
concatenation Empty = id
concatenation (Element a as) = Element a . concatenation as
请注意,最后一个归约本身并不是更有效:这可能导致将(可能很大)列表的整个副本构建到内存中,并且如果第二个列表结果为空,则需要进行大量计算。另一方面,如果计算第二个列表会导致陷入无限循环或昂贵的计算,而我们对此部分不感兴趣,那么我们可能会节省大量 CPU 周期。