隐藏Haskell中的约束

隐藏Haskell中的约束,haskell,Haskell,如果你使用的函数有很多约束条件 f :: (C_0, ..., C_n) => .... 而f用于g的定义,那么g也需要这些约束。但是,这些约束可能指的是g不应该知道的事情(因为这可能会泄露有关如何实现f的信息)。假设这是一件明智的事情,那么隐藏C_0的明智方式是什么。。。C_n 我试过这样的方法(请不要拔头发): 但是,虽然这样做有效,但它会导致可简化的类约束警告,敦促我使用单本地绑定,或者用C_0,…,C_n替换CanApplyF 在Haskell中有没有实现隐藏约束的方法?老实说,

如果你使用的函数有很多约束条件

f :: (C_0, ..., C_n) => ....
f
用于
g
的定义,那么
g
也需要这些约束。但是,这些约束可能指的是
g
不应该知道的事情(因为这可能会泄露有关如何实现
f
的信息)。假设这是一件明智的事情,那么隐藏
C_0的明智方式是什么。。。C_n

我试过这样的方法(请不要拔头发):

但是,虽然这样做有效,但它会导致可简化的类约束警告,敦促我使用单本地绑定,或者用
C_0,…,C_n
替换
CanApplyF


在Haskell中有没有实现隐藏约束的方法?

老实说,我认为您所做的应该是正确的方法。实际上,
-wsimpilifiable类约束
有点可疑,尽管有时确实有用。尽管如此,如果对约束的了解实际上是一种安全风险,那么我敢说这可能是没有希望的。(也是,但这是另一个讨论。)

可能的解决办法:

  • 使
    CanApplyF
    不是一个新类,而只是一个约束同义词

    这应该是可行的,但无论如何它都不是一个严格的封装。找出约束
    CanApplyF
    真正包含的内容并不重要,事实上(与其他类型同义词一样),它们可能会意外地出现在错误消息中

  • 添加一个虚拟方法,这样从技术上讲,
    CanApplyF
    就不等同于它的超类的组合

    class (C_0, ..., C_n) => CanApplyF where
      onlyFUsesThis :: ()
    
    f = onlyFUsesThis `seq` ...
    
    (这不是那样工作的,因为实例是模糊的。您需要使用
    -xallowambigioustypes
    -XTypeApplications
    使其工作,或者引入一些
    代理来工作。)


我认为这与面向对象编程中的依赖注入类似。在OOP中,了解依赖项的依赖项不是一个好主意。最好只知道您的直接依赖项(或者更准确地说,它们的接口)

考虑一下这个代码

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RankNTypes #-}
import Prelude hiding (empty,insert,lookup,toList)
import Data.Foldable (foldl')
import qualified Data.Map.Strict as M

data Mappy m a = Mappy
  { empty :: forall b. m a b,
    insert :: forall b. a -> b -> m a b -> m a b,
    lookup :: forall b. a -> m a b -> Maybe b,
    toList :: forall b. m a b -> [(a, b)]
  }

histogram :: Mappy m a -> [a] -> [(a, Int)]
histogram (Mappy {empty, insert, lookup, toList}) = toList . foldl' step empty
  where
    step acc k = case lookup k acc of
      Nothing -> insert k 1 acc
      Just count -> insert k (succ count) acc
直方图
不知道它在内部使用的贴图关键点上所需的任何约束

将其付诸实施:

mappy :: Ord a => Mappy M.Map a
mappy =
  Mappy
    { empty = M.empty,
      insert = M.insert,
      lookup = M.lookup,
      toList = M.toList
    }

main :: IO ()
main = print $ histogram mappy "aabbbccc" 
-- [('a',2),('b',3),('c',3)]
也许我们可以使用typeclass,而不是记录,一个拥有所有类型所需的“surface”操作作为方法的类:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Kind

-- a dictionary
class Mappy a where
    type Container a :: Type -> Type
    empty :: forall b. Container a b
    insert :: a -> b -> Container a b -> Container a b
    lookup :: a -> Container a b -> Maybe b
    toList :: Container a b -> [(a, b)]

histogram :: forall a. Mappy a => [a] -> [(a, Int)]
histogram xs = toList $ foldl' step (empty @a @Int) xs
  where
    step acc k = case lookup k acc of
      Nothing -> insert k 1 acc
      Just count -> insert k (succ count) acc

这些解决方案隐藏了
直方图
中的
Eq
Ord
等约束,但让客户工作得更多:现在他必须组装记录字典,或者声明新的typeclass实例。

如果函数的参数不知道它想要什么,人们将如何使用该函数?@bereal
CanApplyF
的实例将列在Haddocks中。用户只是不知道为什么这些是允许的类型,而其他类型不是。我希望用户仍然必须有一个约束来说明
f
所期望的内容,但这样做不会泄露
f
实现中的任何信息。因此,
CanApplyF
是一个如何隐藏约束的示例,尽管可能很糟糕。为了给出一个附加示例,假设
f
f
使用的类型的子字段中具有
Ord
约束。不仅
f
会泄露
Ord
约束,还会泄露它所使用的数据的子字段。@damianadales把整个
f
作为参数传递给
g
怎么样?我不太清楚类型类会泄露哪些重要的实现细节,但是,如果有一个可以与函数一起使用的类型的详尽列表,则隐藏泛型实现并仅导出具有具体类型的包装器。“了解依赖项的依赖项不是一个好主意。最好只了解直接依赖项(或者更准确地说,它们的接口)。”
mappy :: Ord a => Mappy M.Map a
mappy =
  Mappy
    { empty = M.empty,
      insert = M.insert,
      lookup = M.lookup,
      toList = M.toList
    }

main :: IO ()
main = print $ histogram mappy "aabbbccc" 
-- [('a',2),('b',3),('c',3)]
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Kind

-- a dictionary
class Mappy a where
    type Container a :: Type -> Type
    empty :: forall b. Container a b
    insert :: a -> b -> Container a b -> Container a b
    lookup :: a -> Container a b -> Maybe b
    toList :: Container a b -> [(a, b)]

histogram :: forall a. Mappy a => [a] -> [(a, Int)]
histogram xs = toList $ foldl' step (empty @a @Int) xs
  where
    step acc k = case lookup k acc of
      Nothing -> insert k 1 acc
      Just count -> insert k (succ count) acc