Haskell中无点函数的先决条件是什么?

时间:2016-12-01 18:23:10

标签: function haskell pointfree

我一直认为无点函数的先决条件是将函数参数放到定义的末尾。 E.g。

-- This can be made pointfree quite easily:    
let lengths x = map length x    
let lengths' = map length

-- However this cannot:
let lengthz x = length `map` x
-- let lengthz' = length `map` (parse error)

我最初遇到过这个阅读this question。我们有这个例子:

agreeLen :: (Eq a) => [a] -> [a] -> Int
agreeLen x y = length $ takeWhile id $ zipWith (==) x y
-- This may look like it can easily be made pointfree, however it cannot
-- agreeLen' :: (Eq a) => [a] -> [a] -> Int
-- agreeLen' = length $ takeWhile id $ zipWith (==) (zipWith is applied to too few arguments)

那么为什么我的第一个例子可以成为无点的,但其他两个不可以?

4 个答案:

答案 0 :(得分:6)

您的第一个可以以无点样式编写,但您必须根据map作为中缀函数的使用进行调整。

let lengthz = (length `map`) -- write the partial application as a section

agreeLen的问题是zipWith不是一个参数的函数,而是两个参数的函数。在将结果传递给zipWith之前,需要将takeWhile id应用于这两个参数。以无点样式编写它的不容易的方法是

-- via http://pointfree.io
agreeLen = ((length . takeWhile id) .) . zipWith (==)

简单地说,zipWith (==)应用于第一个参数xagreeLen以生成一个新函数(一个获取列表并返回压缩列表的函数)。这个新函数作为(length . takeWhile id) .的参数给出,它产生一个新的组合函数,它将第二个参数带到agreeLen并产生所需的Int值。

(@ duplode可能derives this比我将要尝试的更干净。)

当初始函数需要超过2个参数时,很快失控的一个技巧是明确地解决它,执行合成,然后重新调整结果。

agreeLen = curry $ length . takeWhile id . (uncurry $ zipWith (==))

答案 1 :(得分:6)

-- However this cannot:
let lengthz x = length `map` x
-- let lengthz' = length `map` (parse error)

\x -> length `map` x免费书面点只是map length。中缀反引号只是语法糖。 (正如chepner所指出的,如果你真的想要它,你可以使用一个部分,即(length `map`)。)

agreeLen :: (Eq a) => [a] -> [a] -> Int
agreeLen x y = length $ takeWhile id $ zipWith (==) x y
-- This may look like it can easily be made pointfree, however it cannot

这里的关键词是“容易”。如果你足够努力的话,几乎任何事情都可以毫无关键。在这种情况下,如果我们使用y而不是agreeLen来编写(.),则省略($)参数很容易:

agreeLen x y = (length . takeWhile id . zipWith (==) x) y
agreeLen x = length . takeWhile id . zipWith (==) x

对于x,我们可以通过将(.)与其他函数组合成zipWith (==) x来处理它,而只是使用函数修改值的另一种情况: / p>

agreeLen x = (.) (length . takeWhile id) (zipWith (==) x)
agreeLen x = ((length . takeWhile id) .) (zipWith (==) x) -- cosmetical change
agreeLen x = (((length . takeWhile id) .) . zipWith (==)) x
agreeLen = ((length . takeWhile id) .) . zipWith (==)

这不是你在实践中真正想做的事情,但肯定是可能的。

答案 2 :(得分:3)

您始终可以使用多态S(<*>)和K(const)组合器将类型化的lambda表达式转换为类型化的组合子表达式。 A Note on Typed Combinators and Typed Lambda Terms显示了一个证明,它也是一个抽象算法,用于将lambda术语转换为(无点)组合术语:

  
      
  1. | X <子>α | x α = I α→α
  2.   
  3. | X <子>α | X α = K β→(α→β) X β如果x α∉Xβ
  4.   
  5. | X <子>α | X α→β = X α→β如果x α∉Xα→β
  6.   
  7. | X <子>α | X β→γ Y β = S α→(β→γ)→α→β→(α→γ)(| x < sub>α | X β→γ)(| x α | Y β)否则
  8.   

这可以很容易地转换成Haskell表示法:

(\(x :: a) -> (x :: a)) = id

-- If X does not mention x
(\(x :: a) -> (X :: a)) = const X

-- If X does not mention x
(\(x :: a) -> (X :: a -> b)) = X

(\(x :: a) -> (X :: b -> c) (Y :: b))
  = (\(x :: a) -> X) <*> (\(x :: a) -> Y)

因此,您总是可以编写一个无点定义,但是如果没有更高级别的组合器,它可能会更大,更丑陋,您可能需要引入新类型的包装和放大器。展开或RankNTypes处理因传递多态函数而产生的不可预测的多态性。

但是,在真正的Haskell代码中,当你可以“获取定义结尾的参数”时,主要是想使用无点样式,也就是说,当你可以将f x = g x转换为{{ 1}}。如果你有一个简单的“管道”函数,你可以传递一个中间值而不复制或删除它,这种方法效果最好。例如,这是一个直方图函数:

f = g

答案 3 :(得分:3)

如果我们将这种形式的函数定义称为无点

f = e

其中f只是一个函数名,而 e 是一个表达式, 我们至少可以使这些函数无点

  1. 右侧只是一个功能应用程序。
  2. 不要求对其参数进行模式匹配,
  3. &#34;复杂&#34;中没有提到参数。子表达式,例如列表推导,case,let或lambda抽象。
  4. 也就是说,如果参数只是简单的名称,如果同时满足其他两个条件,我们可以使它无点。

    对于需要一些模式匹配的参数,如果我们可以使用某些函数(例如maybeeitherfstsnd)来消除构造函数,我们通常可以重写这个函数让我们得到一个可以无点编写的函数:

     f (a,b) = (b,a)
     f' ab   = (snd ab, fst ab)
     f'' = (pure (,) <*> snd) <*> fst
    

    此外,右侧的if个表达式可以重写为bool

    的应用

    这是一个从表达式 e 中删除某些变量 v 的算法,这样生成的表达式 x 不包含< em> v ,当应用于 v 时相当于 e

    • 如果 e 不包含 v ,则结果为pure e
    • 如果 e v ,则结果为id
    • 如果 e 某些表达 f 应用于 v f 未提及 v < / em>结果是 f
    • 否则, e 必须是应用程序 gh ,其中至少有一个 g h 提及 v 。结果将是 g&#39; &LT * GT; h&#39; 其中 g&#39; 是从 g h&#消除 v 时产生的表达式39; 是从 h 消除 v 时产生的表达式

    以下是如何使函数定义无点,以满足上述要求

     f v1 v2 v3 ... vn = e
    
    • e&#39; e 中消除 vn
    • 记下新功能

      F&#39; v1 v2 v3 ... v(n-1)= e&#39;

    • 如果没有留下任何参数,您就完成了

    • 否则,请 f&#39; 无点

    注意,有不同的方法可以使用(.) flip之类的函数来消除表达式的变量,这通常会导致更短的代码,但是,上面是最基本的方法,因为它只是需要S,K和I组合器(在Haskell中写成<*> pureid