使用'如果'点免费

时间:2015-12-17 19:17:17

标签: haskell functional-programming pointfree

我在Haskell有一个任务(不,这不是我的功课,我是为了考试而学习)。

任务是:

  

写入无点函数numocc,用于计算给定列表中元素的出现次数。例如:numocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]] = [1, 2, 0]

这是我的代码:

addif :: Eq a => a -> Int -> a -> Int
addif x acc y = if x == y then acc+1 else acc

count :: Eq a => a -> [a] -> Int
count = flip foldl 0 . addif

numocc :: Eq a => a -> [[a]] -> [Int]
numocc = map . count

numocccount是免费的',但他们使用的功能addif并非如此。

我不知道如何才能使addif函数无点。有没有办法让if语句无点?也许有一个技巧不使用if

6 个答案:

答案 0 :(得分:10)

我会使用以下事实:您可以使用Bool轻松地将Int转换为fromEnum

addif x acc y = acc + fromEnum (x == y)

现在你可以开始应用通常的技巧让它无点了

-- Go prefix and use $
addif x acc y = (+) acc $ fromEnum $ (==) x y
-- Swap $ for . when dropping the last argument
addif x acc = (+) acc . fromEnum . (==) x

等等。我不会忘记让它免费的所有乐趣,特别是当有工具为你做的时候。

或者,您可以编写类似

的函数
count x = sum . map (fromEnum . (==) x)

这几乎是免费的,并且有一些技巧可以让你更接近,虽然它们很快变得非常讨厌:

count = fmap fmap fmap sum map . fmap fmap fmap fromEnum (==)

在这里,我认为使用fmap代替(.)实际上看起来更好,尽管您可以将每个fmap替换为(.),它将是完全相同的代码。基本上,(fmap fmap fmap)一起组成一个参数和一个两个参数函数,如果你给它命名为.:,你可以将其写成

count = (sum .: map) . (fromEnum .: (==))

细分:

> :t fmap fmap fmap sum map
Num a => (a -> b) -> [a] -> b

所以它需要一个从b到数字a的函数,一个b的列表,并返回一个a,但不是太糟糕。

> :t fmap fmap fmap fromEnum (==)
Eq a => a -> a -> Int

此类型可以写成Eq a => a -> (a -> Int),这是一个需要注意的重要事项。这使得此函数的返回类型与fmap fmap fmap sum map的{​​{1}}输入匹配,因此我们可以将它们组合起来以获得类型为b ~ Int的函数。

答案 1 :(得分:8)

为什么不

numocc x 
  = map (length . filter (== x))
  = map ((length .) (filter (== x)) )
  = map (((length .) . filter) (== x))
  = map (((length .) . filter) ((==) x))
  = map (((length .) . filter . (==)) x)
  = (map . ((length .) . filter . (==))) x
  = (map . (length .) . filter . (==)) x

然后是琐碎的eta收缩。

答案 2 :(得分:6)

一个技巧是导入众多if functions中的一个,例如Data.Bool.bool 1 0(也可在Data.Bool.Extras中找到)。

一个更神秘的技巧是使用Foreign.Marshal.Utils.fromBool,它完全符合你的需要。或者同样的事情,减少奥术:fromEnum(感谢@bheklilr)。

但我认为最简单的伎俩就是避免自己计算,只需在length之后应用标准filter函数。

答案 3 :(得分:1)

使用Enum Bool实例,可以构建一个无点替换,以便在更一般的情况下使用它:

chk :: Bool -> (a,a) -> a
chk = ([snd,fst]!!) . fromEnum

使用chk我们可以定义addIf的不同版本:

addIf' :: Eq a => a -> a -> Int -> Int
addIf' = curry (flip chk ((+1),id) . uncurry (==))

现在我们可以简单地替换chk中的addIf'

addIf :: Eq a => a -> a -> Int -> Int
addIf = curry (flip (([snd,fst]!!) . fromEnum) ((+1),id) . uncurry (==))

答案 4 :(得分:0)

我认为你正在寻找Data.Bool的{​​{3}},它自4.7.0.0(2014-04-08)以来就存在。

incif :: (Eq a, Enum counter) => a -> a -> counter -> counter
incif = ((bool id succ) .) . (==)

在将表达式传递给.之前,附加==允许bool获取两个参数。

由于参数的顺序不同,您需要使用incif,如下所示:

(flip . incif)

(将整合到 incif留给读者作为练习。[翻译:这不是微不足道的,我还不知道如何。;])

答案 5 :(得分:0)

请记住,在Haskell列表推导中,如果条件可以在结果子句中使用或在结尾处使用。但是,最重要的是,没有if的警卫可以用来过滤结果。我正在使用拉链对。该对中的第二个是列表编号。它保持不变,同时将列表的元素与常量(k)进行比较。 您的结果[1,2,0]不包括列表编号1,2或3,因为从结果列表中的总和位置可以看出这一点。此处的结果不会在每个列表中添加实例,而是为每个列表列出它们。

nocc k ls = [ z | (y,z) <- zip ls [1..length ls], x <- y, k == x]
nocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]

[1,2,2] - 在列表1中读为[1,2,0]或1,在列表2中读取为2,在列表3中读为0