对不同类型的对的函数

时间:2017-02-24 23:53:26

标签: haskell

有没有办法定义“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)

1 个答案:

答案 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)