【问题标题】:Generalizing functions in HaskellHaskell 中的泛化函数
【发布时间】:2016-09-30 05:44:53
【问题描述】:

这两个功能几乎相同:

dig :: MappingClassifierM FileSummary IO Partitioner -> Conduit (Cluster Partitioner FileSummary) IO (Cluster Partitioner FileSummary)
dig classifier =
  await >>= \case
    Nothing -> return ()
    Just ( Cluster clusterKey clusterValue ) -> do
      categories <- liftIO $ classify classifier clusterValue
      if (length clusterValue == length categories)
        then do
          yield $ Cluster clusterKey clusterValue
          dig classifier
        else do
          mapM_ (yield . cluster) categories
          dig classifier
      where
        cluster (key, val) = Cluster (key : clusterKey) val
        classify = classifyM

dig' :: BinaryClassifierM FileSummary IO -> Conduit (Cluster Partitioner FileSummary) IO (Cluster Partitioner FileSummary)
dig' classifier =
  await >>= \case
    Nothing -> return ()
    Just ( Cluster clusterKey clusterValue ) -> do
      categories <- liftIO $ classify classifier clusterValue
      if (length clusterValue == length categories)
        then do
          yield $ Cluster clusterKey clusterValue
          dig' classifier
        else do
          mapM_ (yield . cluster) categories
          dig' classifier
      where
        cluster = Cluster (Content : clusterKey)
        classify = classifyBinary

唯一的区别在于 where 子句中定义的函数。

以下限制适用:

  • classify 函数取决于作为第一个参数传递的分类器的“类型”。
  • cluster 函数取决于分类器的实际实现。

我想概括这两个函数,以便创建一个处理这两个实现以避免重复的单个函数。

我不知道我的方向是否正确。根据我迄今为止对 Haskell 的有限了解,我认为我必须创建一个类“分类器”,其中 BinaryClassifier 和 MappingClassifierM 将是其实例,但是当我尝试实现它时遇到了几个编译错误.

所以,我的问题是:有经验的 Haskell 程序员如何概括这两个函数以避免重复?

对于其他上下文,以下是我试图概括的两种不同情况的相关类型签名:

type MappingClassifierM a m k = a -> m k
classifyM :: (Monad m, Ord k) => MappingClassifierM a m k -> [a] -> m [(k, [a])]
dig :: MappingClassifierM FileSummary IO Partitioner -> Conduit (Cluster Partitioner FileSummary) IO (Cluster Partitioner FileSummary)

type BinaryClassifierM a m = a -> a -> m Bool
classifyBinary :: Monad m => BinaryClassifierM a m -> [a] -> m [[a]]
dig' :: BinaryClassifierM FileSummary IO -> Conduit (Cluster Partitioner FileSummary) IO (Cluster Partitioner FileSummary)

【问题讨论】:

  • 只需将dig 设为高阶函数,并将clusterclassify 函数作为参数传入即可。
  • 变量categories 的类型在digdig' 之间似乎不同 - 对吗?
  • 一个你可能喜欢的小把戏:\case Nothing -&gt; return (); Just p -&gt; e也可以写成traverse_ (\p -&gt; e)
  • @ErikR,你是对的,类别的类型不同。在每种情况下,categories 的类型都是 classify 函数返回的类型。差异在cluster 函数中处理

标签: haskell functional-programming


【解决方案1】:

我的方法是让编译器为我做大部分的思考。首先,我把通用代码做成了自己的函数

dig'' classifier =
  await >>= \case
    Nothing -> return ()
    Just ( Cluster clusterKey clusterValue ) -> do
      categories <- liftIO $ classify classifier clusterValue
      if (length clusterValue == length categories)
        then do
          yield $ Cluster clusterKey clusterValue
          dig'' classifier
        else do
          mapM_ (yield . cluster) categories
          dig'' classifier

这会导致几个问题。首先,编译器抱怨clusterclassify 是未定义的,所以我们需要将它们作为参数添加到函数中。您还会注意到分类器从未在分类函数之外使用,因此我们将它们组合成一个值。此外,由于我们更改了函数的参数,我们的递归调用也发生了变化,所以我们需要处理这些。

dig'' cluster =
  await >>= \case
    Nothing -> return ()
    Just ( Cluster clusterKey clusterValue ) -> do
      categories <- liftIO $ classify clusterValue
      if (length clusterValue == length categories)
        then do
          yield $ Cluster clusterKey clusterValue
          dig'' cluster classify
        else do
          mapM_ (yield . cluster) categories
          dig'' cluster classify

在这里我注意到 where 子句中的 cluster 需要 clusterKey,因此需要以某种方式作为参数传入。

dig'' cluster =
  await >>= \case
    Nothing -> return ()
    Just ( Cluster clusterKey clusterValue ) -> do
      categories <- liftIO $ classify clusterValue
      if (length clusterValue == length categories)
        then do
          yield $ Cluster clusterKey clusterValue
          dig'' cluster classify
        else do
          mapM_ (yield . cluster clusterKey) categories
          dig'' cluster classify

最后,使用类型漏洞让 ghc 为我找出数据类型。

dig'' :: _
dig'' cluster =
  await >>= \case
    Nothing -> return ()
    Just ( Cluster clusterKey clusterValue ) -> do
      categories <- liftIO $ classify clusterValue
      if (length clusterValue == length categories)
        then do
          yield $ Cluster clusterKey clusterValue
          dig'' cluster classify
        else do
          mapM_ (yield . cluster clusterKey) categories
          dig'' cluster classify

我的值是

(Foldable t, MonadIO m) =>
 ([a] -> a1 -> Cluster a b)
 -> ([b] -> IO (t a1)) -> Conduit (Cluster a b) m (Cluster a b)

但您的结果可能会有所不同,因为我在很大程度上构成了所有数据类型。例如,我怀疑 [a][b] 对于您的代码会有所不同。

现在,回到原来的功能。在这里,我们的优势在于已经知道结果的类型。

dig :: MappingClassifierM FileSummary IO Partitioner -> Conduit (Cluster Partitioner FileSummary) IO (Cluster Partitioner FileSummary)
dig classifier = dig'' ????

现在我们只需要dig'' 的两个参数。第一个参数只是填写cluster定义,所以我们得到

(\clusterKey (key, val) -> Cluster (key : clusterKey) val)

第二个参数是分类,我们在分类器中滚动到它,所以它很简单 (classifyM 分类器)

因此,最终的定义是

dig :: MappingClassifierM FileSummary IO Partitioner -> Conduit (Cluster Partitioner FileSummary) IO (Cluster Partitioner FileSummary)
dig classifier = dig'' (\clusterKey (key, val) -> Cluster (key : clusterKey) val) (classifyM classifier)

同样,你也可以找到

dig' :: BinaryClassifierM FileSummary IO -> Conduit (Cluster Partitioner FileSummary) IO (Cluster Partitioner FileSummary)
dig' classifier = dig'' (\clusterKey -> Cluster (Content : clusterKey)) (classifyBinary classifier)

【讨论】:

  • 你觉得我能想出的解决方案怎么样?
【解决方案2】:

我能够使用允许某些语言扩展的类找到替代解决方案:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleContexts #-}

class Classifier classifier a m c | classifier -> m c where
  classify :: (Monad m) => classifier -> [a] -> m [c]
  cluster :: classifier -> [Partitioner] -> c -> Cluster Partitioner a

instance Classifier (MappingClassifierM FileSummary IO Partitioner) FileSummary IO (P
  classify = classifyM
  cluster _ x (key, val) = Cluster (key : x) val

instance Classifier (BinaryClassifierM FileSummary IO) FileSummary IO [FileSummary] w
  classify = classifyBinary
  cluster _ clusterKey = Cluster (Content : clusterKey)

dig :: (Classifier classifier FileSummary IO c) => classifier -> Conduit (Cluster Par
dig classifier =
  await >>= \case
    Nothing -> return ()
    Just ( Cluster clusterKey clusterValue ) -> do
      categories <- liftIO $ classify classifier clusterValue

      when (length clusterValue == length categories) $
        yield $ Cluster clusterKey clusterValue

      when (length clusterValue /= length categories) $
        mapM_ (yield . (cluster classifier clusterKey)) categories

      dig classifier

【讨论】:

  • 我曾考虑过创建一个类型类,但我经常这样做,而且我一直在努力改掉这个习惯,所以我没有为我的解决方案。我喜欢您将dig classifier 移出条件。我希望我记得自己做那件事。我不喜欢使用when x 后跟when (not x),而不是只使用if 语句,但这可能是个人喜好问题。我唯一的其他评论是您已将分类器包含在集群的类型定义中,但您的集群实例都没有使用它。以后需要吗?
  • @user640078 我不想在集群的类型定义中包含分类器,但是如果我不这样做的话,我就会遇到编译器问题:Could not deduce (Classifier classifier0 FileSummary m0 c) arising from a use of ‘cluster’ from the context (Classifier classifier FileSummary IO c). The type variables ‘classifier0’, ‘m0’ are ambiguous。有没有其他方法可以让编译器知道选择什么实例?
  • 仅供参考,当使用这样的基金时,您通常应该将“确定”参数放在最后而不是放在首位。除了是惯用的,它还可以让您的班级使用广义的新类型派生。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多