【问题标题】:Functor Design Pattern in HaskellHaskell 中的函子设计模式
【发布时间】:2011-12-09 03:24:46
【问题描述】:

对于没有为这个问题提出一个好的标题,我深表歉意。我在表达我的需要时遇到了一些麻烦。我在 Haskell 中有一个简单的问题,我想知道解决它的最佳方法是什么。

假设我有一个数字列表:[-3,2,1,2]。我想返回具有最高绝对值的值。也就是说,我想返回-3。所以我想要:

f = maximum . map abs

当然,问题是返回计算值 (3) 而不是原始值 (-3)。

我可以想办法做到这一点,也许将原始列表映射到 (originalValue, calculatdValue) 的元组,找到其 snd 由我的函数返回的元组(最大值),然后返回该元组的 fst。

但是对于像这样的简单问题,这似乎是很多“管道”,我想知道是否有一些我缺少的抽象可以解决这个问题。也就是说,我一直都在做这个一般程序,我想要一些巧妙的方法:

  1. 我想要一份物品清单。
  2. 我想将它们映射到某个值(比如说绝对值)
  3. 然后我想根据一些标准选择一个(假设我想要最大值或最小值)。
  4. 但是我想返回 original 值。 (如果列表是[-3,2,1,2] 并且我想返回具有最高绝对值的值,那么我将返回-3)。

有这个库函数吗?有没有一个函子或单子?

我想我想要一个带有签名的函数:

f :: ([b] -> b) -> (a -> b) -> [a] -> a

f maximum abs [-3,2,1,2]

这对我来说感觉很“敷衍”,或者可能是“单调的”。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    使用带比较函数的 maximumBy。然后,您可以传递一些比较您想要的方式的函数。

    maximumBy (compare `on` abs)
    

    【讨论】:

    • 或等效:maximumBy (comparing abs)
    • 我更喜欢使用on,因为它适用于其他事物,例如 nubBy ((==) on abs)。
    • 感谢您的解决方案!有一个maximumBy和一个minimumBy。有 findFirstBy 吗?我猜有很多函数将 a 转换为 b(abs),然后有很多函数将 a [b] 转换为 b(最大值)。而且我正在尝试找到一种无需烹饪即可制作它们的方法。
    • @Ara:Data.List 模块中的elemIndex 函数产生满足给定谓词的列表中第一个元素的索引。如果没有匹配的元素,则返回Nothing。这可能是您在谈论findFirstBy 时所想的。
    【解决方案2】:

    停止...胡扯时间!

    所以你有一个东西列表[a]。而您只想得到其中一个a。您还希望以某种特殊方式(不是它们的自然顺序)比较此列表中的元素,以确定哪个先出现。这是棘手的部分,但您应该能够看到我所描述的是a -> a -> Ordering 形式的函数。

    把它们放在一起:

    (a -> a -> Ordering) -> [a] -> a

    还有hoogle itmaximumByminimumBy 是第一个热门 :) 当您学会使用 Hoogle 时,它​​可能是一项强大的资产。 (有关在这种情况下如何使用maximumBy 的详细信息,请参阅augustss 的答案)

    【讨论】:

      【解决方案3】:

      另一种方法,如果转换有点贵:

      maximumWith :: (Ord b) => (a -> b) -> [a] -> a
      maximumWith f = snd . maximumBy (compare `on` fst) . map (f &&& id)
      

      这种类型类似于GHC.ExtssortWith,这为我们提供了另一种方法:

      maximumWith :: (Ord b) => (a -> b) -> [a] -> a
      maximumWith f = head . sortWith (Down . f)
      

      我们可以类似地定义一个 minimumWith:

      minimumWith :: (Ord b) => (a -> b) -> [a] -> a
      minimumWith f = head . sortWith f
      

      查看sortWith 的源代码发现它是由sortBy 实现的,因此它缺少maximumWith 的第一个定义所具有的缓存。

      这显然需要一些基准测试:

      module Main where
      import Control.Arrow ((&&&))
      import Data.List (sortBy)
      import Data.Function (on)
      import GHC.Exts (sortWith)
      import Criterion.Main
      
      sortWith :: (Ord b) => (a -> b) -> [a] -> [a]
      sortWith f = map snd . sortBy (compare `on` fst) . map (f &&& id)
      
      badFib :: Int -> Int
      badFib 0 = 1
      badFib 1 = 1
      badFib n = badFib (n - 1) + badFib (n - 2)
      
      main = defaultMain [ bench "GHC.Exts.sortWith" $ nf (GHC.Exts.sortWith badFib) [0..20]
                         , bench "Main.sortWith" $ nf (Main.sortWith badFib) [0..20]
                         ]
      

      我的笔记本电脑上的结果:

      benchmarking GHC.Exts.sortWith
      collecting 100 samples, 12 iterations each, in estimated 1.504415 s
      bootstrapping with 100000 resamples
      mean: 1.264608 ms, lb 1.260519 ms, ub 1.270248 ms, ci 0.950
      std dev: 24.42169 us, lb 19.21734 us, ub 31.50275 us, ci 0.950
      found 8 outliers among 100 samples (8.0%)
        5 (5.0%) high mild
        3 (3.0%) high severe
      variance introduced by outliers: 0.996%
      variance is unaffected by outliers
      
      benchmarking Main.sortWith
      collecting 100 samples, 50 iterations each, in estimated 1.516733 s
      bootstrapping with 100000 resamples
      mean: 305.9089 us, lb 304.0602 us, ub 310.9257 us, ci 0.950
      std dev: 14.41005 us, lb 6.680240 us, ub 30.26940 us, ci 0.950
      found 18 outliers among 100 samples (18.0%)
        9 (9.0%) high mild
        9 (9.0%) high severe
      variance introduced by outliers: 0.999%
      variance is unaffected by outliers
      

      【讨论】:

        【解决方案4】:

        如果您总是尝试通过投影对某些内容进行排序和比较,而不仅仅是在特定用途(在这种情况下请参阅 augustss 的答案),请使用新类型包装器:

        newtype AbsInt = AbsInt Int
        
        instance Eq AbsInt where
            AbsInt x == AbsInt y = abs x == abs y
        
        instance Ord AbsInt where
            compare (AbsInt x) (AbsInt y) = compare x y
        

        现在,例如:

        maximum [AbsInt 1, AbsInt 10, AbsInt (-50)] = AbsInt (-50)
        

        大概你会使用AbsInt 作为你的研究对象,所以你不会到处写AbsInts。

        AbsInt 上需要的操作越多,需要的样板就越多。但是,如果您只想“通过”某些实例,GHC 有一个扩展名GeneralizedNewtypeDeriving 允许这样做;例如:

        {-# LANGUAGE GeneralizedNewtypeDeriving #-}
        
        newtype AbsInt = AbsInt Int
            deriving (Num)
        

        现在AbsInt 在算术方面的行为类似于Int,但(鉴于上述实例)在比较方面是绝对值。另请注意,Num 实例使您能够使用文字,因此:

        (maximum [1,2,-3] :: AbsInt) = AbsInt (-3)
        

        【讨论】:

          【解决方案5】:

          我相信以下内容应该可行。

          foldl abs_max (head xs) xs
           where abs_max x y = if (abs x) > (abs y) then x else y
          

          超越手头的任务,您可以通过抽象出比较函数并稍后传递它来概括它。

          【讨论】:

            【解决方案6】:

            这是我做的。这有点咩,因为它需要 (Eq b)

            selectOn :: (Eq b) => ([b] -> b) -> (a -> b) -> [a] -> a
            selectOn reducer f list = head $ filter (\x -> f(x) == k ) list
              where k = reducer $ map f list
            

            然后:

            selectOn maximum abs [1,2,-3]
            

            或者:

            selectOn sum id [-3, 0, 3]
            

            我想我可以概括比较 on 并得到完全相同的效果。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-03-05
              • 1970-01-01
              • 2016-04-26
              • 1970-01-01
              • 2014-02-25
              • 1970-01-01
              • 2022-11-27
              • 1970-01-01
              相关资源
              最近更新 更多