【问题标题】:make function with 'if' point-free使用“if”来实现无点功能
【发布时间】:2016-03-24 09:20:39
【问题描述】:

我在 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的技巧?

【问题讨论】:

  • 你可以做数学问题,比如` let f x y = 1 - ceiling (fromIntegral (x-y) / fromIntegral y) :: Int`? (这不完全是你所需要的;))
  • 1 - ceiling (abs $ fromIntegral (x-y) / fromIntegral (max x y)) :: Int 应该这样做,如果我没有错过某个地方的一些讨厌的边境案例 - 也许你会想对此进行推理或运行一些快速检查;)(好吧,我错过了一些负面因素,所以你会必须更多abs in ...但原理应该很明显...真正的解决方案是微不足道的,留给读者:D
  • 一般情况下,您可以将if 语句替换为函数bool :: Bool -> a -> a -> a; bool False f _ = f; bool True _ t = t,在这种情况下,您始终可以形成一个“正常”表达式,可以使用常规方法使其无点。跨度>

标签: haskell functional-programming pointfree


【解决方案1】:

我认为您可以使用fromEnum 轻松地将Bool 转换为Int

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 的函数,一个bs 的列表,并返回一个a,还不错。

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

而且这个类型可以写成Eq a => a -> (a -> Int),这是需要注意的重要一点。这使得该函数的返回类型与fmap fmap fmap sum mapb ~ Int 的输入相匹配,因此我们可以组合它们以获得Eq a => a -> [a] -> Int 类型的函数。

【讨论】:

    【解决方案2】:

    为什么不

    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 收缩。

    【讨论】:

    • 我认为这最符合所描述的任务。
    • @AndrásKovács 后退方法... :) cf。 this recent Lisp entry 用于 map head . filter (not.null.drop 1) . group 的一些复杂等价物。
    【解决方案3】:

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

    一个更神秘的技巧是使用Foreign.Marshal.Utils.fromBool,这正是您需要的。或者同样的事情,不那么神秘:fromEnum(感谢@bheklilr)。

    但我认为最简单的技巧是简单地避免计算自己,只需在 filtering 之后应用标准的 length 函数即可。

    【讨论】:

    • 为什么不直接使用fromEnum 而不是Foreign.Marshal 函数呢?它做同样的事情并且已经存在于Prelude 中。
    • @bheklilr:呃,没有意识到 BoolEnum 的一个实例 - 你已经得到了我的支持 :-) fromBool 刚刚闪过我的脑海,我已经看到了它在过去的某个地方……
    【解决方案4】:

    使用BoolEnum 实例,如果可以在更一般的情况下使用,则可以构建一个无点替换:

    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 (==))
    

    【讨论】:

      【解决方案5】:

      我认为您正在寻找 Data.Boolbool,它自 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 留给读者作为练习。[翻译:这不是微不足道的,我还不知道如何。;])

      【讨论】:

        【解决方案6】:

        请记住,在 Haskell 列表推导中,if 条件可以用于结果子句或末尾。但是,最重要的是,没有 if 的守卫可用于过滤结果。我正在使用 zip 中的对。该对中的第二个是列表编号。当列表的元素与常数 (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,2,0] 或列表 1 中的 1,列表 2 中的 2 和列表 3 中的 0

        【讨论】:

          猜你喜欢
          • 2012-01-24
          • 2014-09-04
          • 2015-10-22
          • 1970-01-01
          • 2016-10-26
          • 2020-05-05
          • 2021-03-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多