我正在尝试代表以下字词:
a0 <> a1 <> ... <> an-1
其中ai
必须是可交换半群的元素。为此,可以选择如下的数据表示:
newtype SemigroupPolynomial a = SP Map a Integer
其中地图包含多项式的不同项及其计数。
通过这种方式,我们可以代表总和
3 + 3 + 6
as(假设OverloadedLists
):
SP [(3, 2), (6, 1)]
但我们也可以代表以下术语:
3 * 3 * 6
SemigroupPolynomial
可以是Semigroup
的实例:
instance ??? a => Semigroup (SemigroupPolynomial a) where
(MP p0) <> (MP p1) =
MP $ Map.filter (0/=) $ Map.unionWith (+) p0 p1
问题不在于我必须在???
中加入哪些限制因素:
<>
操作是可交换的和关联的。关于如何表示可交换的Monoids was already asked here的类似问题。但是,似乎约束(Abelian m, Monoidal m)
可能过于强大(我不需要零元素),它会阻止我使用它来表示产品。
答案 0 :(得分:1)
正如@leftroundabout所评论的那样,你在这里 需要约束。不要被#34;约束&#34;这个词所迷惑。在Haskell中,约束的主要目的不是以某种方式约束特定类型或操作的行为。相反,它将函数将接受的类型集约束到支持一组操作的那些类型。
当我写:
f
我并没有真正限制类型fmapTwice
充当仿函数并遵守仿函数所需的规则。相反,我限制f
函数仅适用于支持fmap
操作的类型data Foo a = Foo a | NoFoo deriving (Show)
instance Functor Foo where
fmap _ _ = NoFoo -- invalid functor violates: fmap id = id
。
没有什么可以阻止一些人写作:
> fmapTwice (*2) (Foo 10)
NoFoo
>
并将我的函数应用于此无效仿函数:
Functor
Haskell依赖程序员规则来确保声明具有import Data.Semigroup
import qualified Data.Map as Map
import Data.Map.Strict (Map)
data SemigroupPolynomial a = SP (Map a Integer) deriving (Show)
instance (Ord a) => Semigroup (SemigroupPolynomial a) where
(SP p0) <> (SP p1) =
SP $ Map.filter (0/=) $ Map.unionWith (+) p0 p1
实例的东西是一个表现良好的函子。
在您的示例中,实例:
Ord a
不需要a
以外的任何约束,以确保Map
可以用作SemigroupPolynomial
密钥。
现在,您可以确保只使用foldSP :: (a -> a -> a) -> SemigroupPolynomial a -> a
foldSP f (SP m) = foldr1 f $ concatMap (\(a, n) -> replicate (fromIntegral n) a)
(Map.assocs m)
main = do let sp = singleton 3 <> singleton 3 <> singleton 6
print sp
print $ foldSP (*) sp
print $ foldSP (+) sp
print $ foldSP (-) sp -- wrong, but it's your own damn fault
代表交换操作:
data CommutativeOp a = CO (a -> a -> a)
foldSP :: CommutativeOp a -> SemigroupPolynomial a -> a
foldSP (CO f) (SP m) = <same as above>
如果你想以某种方式在你的数据类型中引入交换性的要求,那么这样做的一种方式(它不涉及Haskell&#34;约束&#34;根本就是)写出如下内容: / p>
plusOp = CO (+)
timesOp = CO (*)
现在,只要你在写作时意识到:
(+)
您正在声明(*)
和foldSP
是可转换操作,这将确保main = do let sp = singleton 3 <> singleton 3 <> singleton 6
print $ foldSP plusOp sp
print $ foldSP timesOp sp
仅适用于此类操作:
a
如果您想以某种方式在类型SemigroupPolynomial a
上引入交换约束以确保a
是有效的表示,那么您不能为Int
执行此操作等于Int -> Int -> Int
,显然,因为它取决于使用哪个二进制操作newtype
进行折叠。
相反,您需要将操作嵌入到类型中,可能使用代表操作的Sum
,例如Product
中的Data.Semigroup
或class Commutative a
instance Commutative (Sum a)
instance Commutative (Product a)
instance (Ord a, Commutative b) => SemigroupPolynomial b where
...definition on (<>) as above...
。然后,您可以引入一个类型类(没有操作)来表示交换约束:
foldSP' :: (Monoid a) => SemigroupPolynomial a -> a
foldSP' (SP m) = mconcat $ concatMap (\(a, n) -> replicate (fromIntegral n) a)
(Map.assocs m)
现在fold操作将使用newtype中隐含的操作(这里,只使用monoid实例):
import Data.Semigroup
import qualified Data.Map as Map
import Data.Map.Strict (Map)
newtype SemigroupPolynomial a = SP (Map a Integer) deriving (Show)
class Commutative a
instance Commutative (Sum a)
instance Commutative (Product a)
instance (Ord a, Commutative a) => Semigroup (SemigroupPolynomial a) where
(SP p0) <> (SP p1) =
SP $ Map.filter (0/=) $ Map.unionWith (+) p0 p1
singleton :: a -> SemigroupPolynomial a
singleton x = SP $ Map.singleton x 1
foldSP' :: (Monoid a) => SemigroupPolynomial a -> a
foldSP' (SP m) = mconcat $ concatMap (\(a, n) -> replicate (fromIntegral n) a)
(Map.assocs m)
main = do let sp1 = singleton (Sum 3) <> singleton (Sum 3) <> singleton (Sum 6)
print sp1
print (foldSP' sp1)
let sp2 = singleton (Product 3) <> singleton (Product 3)
<> singleton (Product 6)
print sp2
print (foldSP' sp2)
也许这就是你想要的。如果是这样,完整的示例如下所示:
phpMyAdmin
答案 1 :(得分:1)
你所构建的是一个免费的幺半群,而这只是一个列表。您可以使用[(a, Integer)]
而不是地图,这对某些用例来说没问题。由于Haskell没有依赖类型,我怀疑在类型方面会有更好的东西。
问题不在于我必须加入哪些限制?这样:
&lt;&gt;操作是可交换的和联想的。
它可用于表示总和和产品,如上所示。
您应该创建Ring
类型类,或使用numeric-prelude的类型类。这将让用户知道您的数据类型是关联的,并且具有加法和乘法。根据我的知识,没有任何伟大的类型级方式来表明这些。您可以使用属性测试来确保一切都通过hspec或类似的库工作。
然而似乎约束(Abelian m,Monoidal m)可能太强(我不需要零元素),它会阻止我用它来代表产品。
没有与多项式“超过”半群相关联的代数结构,因此您可能必须自己实现。由于您说<>
是可交换的,因此您的元素将满足Abelian m
。这只是添加单位的问题。