有没有办法定义“pairmap
”,如下所示:
pairmap f (x,y) = (f x, f y)
以下是有效的:
pairmap (+2) (1::Int, 2::Float)
pairmap succ (1::Int, 'a')
pairmap Just ('a', True)
等
当然,在第一种情况下,两个元素必须是Num
类,而在第二种情况下,都是类Enum
。然而,在第三种情况下,不需要限制。
回答(但可以改进)
以下代码(ideone)解决了这个问题,但请注意我的函数必须包装在一个数据类型中,该数据类型既包含输入和输出类型之间的关系,也包含输入类型的任何约束。这有效,但有一些样板。如果我能用更少的样板来实现这一目标会很好,所以任何答案都会受到赞赏(虽然这个解决方案对我来说相当合适)。
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
import GHC.Exts (Constraint)
class Function f where
type Constraints f a :: Constraint
type instance Constraints f a = ()
type Result f a
type instance Result f a = a
applyFunc :: (Constraints f a) => f -> a -> Result f a
pairmap ::
(Function f, Constraints f a, Constraints f b) =>
f -> (a, b) -> (Result f a, Result f b)
pairmap f (x,y) = (applyFunc f x, applyFunc f y)
data NumFunc where
NumFunc :: (forall a. Num a => a -> a) -> NumFunc
instance Function NumFunc where
type Constraints NumFunc a = (Num a)
applyFunc (NumFunc f) = f
data EnumFunc where
EnumFunc :: (forall a. Enum a => a -> a) -> EnumFunc
instance Function EnumFunc where
type Constraints EnumFunc a = (Enum a)
applyFunc (EnumFunc f) = f
data MaybeFunc where
MaybeFunc :: (forall a. a -> Maybe a) -> MaybeFunc
instance Function MaybeFunc where
type Result MaybeFunc a = Maybe a
applyFunc (MaybeFunc f) = f
y1 = pairmap (NumFunc (+2)) (1::Int, 2::Float)
y2 = pairmap (EnumFunc succ) (1::Int, 'a')
y3 = pairmap (MaybeFunc Just) ('a', True)
main = do
print y1
print y2
print y3
回答2
我认为这更好,更灵活(ideone),但同样,任何减少样板欢迎的改进:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
import GHC.Exts (Constraint)
data Func (c :: (* -> * -> Constraint)) where
Func :: (forall a b. c a b => a -> b) -> Func c
class (c a, a ~ b) => BasicConstraint c a b
instance (c a, a ~ b) => BasicConstraint c a b
numFunc = Func @(BasicConstraint Num)
enumFunc = Func @(BasicConstraint Enum)
class (c a, t a ~ b) => NewtypeConstraint c t a b
instance (c a, t a ~ b) => NewtypeConstraint c t a b
class EmptyConstraint a
instance EmptyConstraint a
maybeFunc = Func @(NewtypeConstraint EmptyConstraint Maybe)
applyFunc :: Func c -> (forall a b. c a b => a -> b)
applyFunc (Func f) = f
pairmap :: (c a a', c b b') => Func c -> (a, b) -> (a', b')
pairmap f (x,y) = (applyFunc f x, applyFunc f y)
main = do
print $ pairmap (numFunc (+2)) (1::Int, 2::Float)
print $ pairmap (enumFunc succ) (1::Int, 'a')
print $ pairmap (maybeFunc Just) ('a', True)
答案 0 :(得分:7)
前两个示例比第三个示例更简单。
{-# LANGUAGE RankNTypes, ConstraintKinds, KindSignatures, AllowAmbiguousTypes, TypeApplications #-}
import GHC.Exts (Constraint)
pairmap :: forall (c :: * -> Constraint) d e. (c d, c e) =>
(forall a. (c a) => a -> a) -> (d,e) -> (d,e)
pairmap f (x,y) = (f x, f y)
使用此解决方案的警告是,您需要显式实例化您正在使用的约束:
ghci> pairmap @Num (+1) (1 :: Int, 1.0 :: Float)
(2,2.0)
至于第三种,这是半解决方案。如果第二种类型总是一个参数化的类型(如f a
),那么你可以做同样的事情(尽管你的第一个例子停止工作 - 你可以通过将它们包装在{{}来使它们工作1}})。
Identity
再次,在GHCi
pairmap' :: forall (c :: * -> Constraint) f d e. (c d, c e) =>
(forall a. (c a) => a -> f a) -> (d,e) -> (f d,f e)
pairmap' f (x,y) = (f x, f y)