从(a-> b)到(MyType-> MyType)

Jan*_*lme 2 haskell

我定义了一个简单的代数(具体)数据类型,MyType:

data MyTpe = MyBool Bool | MyInt Int
Run Code Online (Sandbox Code Playgroud)

...我试图找到一种方法将"转换"任意函数(a-> b),其中a和b是Bool或Int,进入相关的(MyType-> MyType)函数.

这样做,它将(a-> b)转换为Maybe(MyType-> MyType)(参见下面的[1]):

import Data.Typeable

data MyTpe = MyBool Bool | MyInt Int deriving Show

liftMyType :: (Typeable a, Typeable b) => (a -> b) -> Maybe (MyTpe -> MyTpe)
liftMyType f =  case castIntInt f of
                  Just g    -> Just $ liftIntInt g
                  Nothing   -> 
                    case castIntBool f of
                     Just g    -> Just $ liftIntBool g
                     Nothing   -> 
                       case castBoolInt f of
                       Just g    -> Just $ liftBoolInt g
                       Nothing   -> 
                         case castBoolBool f of
                         Just g    -> Just $ liftBoolBool g
                         Nothing   -> Nothing

castIntInt :: (Typeable a, Typeable b) => (a -> b) -> Maybe (Int -> Int)
castIntInt f =  cast f :: Maybe (Int -> Int)

castIntBool :: (Typeable a, Typeable b) => (a -> b) -> Maybe (Int -> Bool)
castIntBool f =  cast f :: Maybe (Int -> Bool)

castBoolInt :: (Typeable a, Typeable b) => (a -> b) -> Maybe (Bool -> Int)
castBoolInt f =  cast f :: Maybe (Bool -> Int)

castBoolBool :: (Typeable a, Typeable b) => (a -> b) -> Maybe (Bool -> Bool)
castBoolBool f =  cast f :: Maybe (Bool -> Bool)

liftIntInt :: (Int -> Int) -> (MyTpe -> MyTpe)
liftIntInt f (MyInt x) = MyInt (f x)

liftIntBool :: (Int -> Bool) -> (MyTpe -> MyTpe)
liftIntBool f (MyInt x) = MyBool (f x)

liftBoolInt :: (Bool -> Int) -> (MyTpe -> MyTpe)
liftBoolInt f (MyBool x) = MyInt (f x)

liftBoolBool :: (Bool -> Bool) -> (MyTpe -> MyTpe)
liftBoolBool f (MyBool x) = MyBool (f x)
Run Code Online (Sandbox Code Playgroud)

然而,这非常难看并且不能很好地扩展:如果我想以这种方式扩展MyType怎么办?

data MyTpe = MyBool Bool | MyInt Int | MyString String
Run Code Online (Sandbox Code Playgroud)

...或者如果我还想将(a1 - > a2 - > b)转换为关联的(MyType-> MyType-> MyType)函数,其中a1,a2和b是Bool或Int?

我的问题:是否有一种简单,更优雅,更像Haskell的方式来处理这个问题?

[1]:未在所有 MyType元素上定义liftIntInt函数等(例如,没有为(MyBool x)元素定义liftIntInt).代码只是一个简化的案例,在现实生活中我正确地处理了这个问题.

J. *_*son 6

你正在寻找一种类型

goal :: (a -> b) -> (MyType -> MyType)
Run Code Online (Sandbox Code Playgroud)

对于一些"合适"的选择ab.这些"合适的"选择是静态已知的,因为静态已知定义MyType.

您正在寻找的是一个类型类.特别是,我们想要的是MultiParamTypeClassespragma

{-# LANGUAGE MultiParamTypeClasses #-}

class MapMyType a b where
  liftMyType :: (a -> b) -> (MyType -> MyType)
Run Code Online (Sandbox Code Playgroud)

所以现在完整的类型liftMyType

liftMyType :: MapMyType a b => (a -> b) -> (MyType -> MyType)
Run Code Online (Sandbox Code Playgroud)

我们可以使用类型类机械的各种实例存储liftMyType有它是可用的只有和什么时候ab可以解决的是在那里种liftMyType有人居住.

instance MapMyType Int  Int  where liftMyType f (MyInt x)  = MyInt  (f x)
instance MapMyType Int  Bool where liftMyType f (MyInt x)  = MyBool (f x)
instance MapMyType Bool Int  where liftMyType f (MyBool x) = MyInt  (f x)
instance MapMyType Bool Bool where liftMyType f (MyBool x) = MyBool (f x)

-- (as a side note, this is a dangerous function to instantiate since it
--  has incomplete pattern matching on its `MyType` typed argument)
Run Code Online (Sandbox Code Playgroud)

现在,值得一提的是,MultiParamTypeClasses经常使用像这样的损害推理.特别是,如果我们在寻找的代码片段liftMyType a b,我们必须能够推断出的类型ab自己(从提示例如,没有帮助传递"向下"的呼叫liftMyType),否则我们会得到一个模糊的实例编译失败.实际上,这使得这个特别糟糕的是,如果要么 直接推断出a或者b不能直接推断,我们就会得到编译失败.

在许多情况下,您可能希望通过FunctionalDependencies允许更多推断来在两个参数之间"流动"并使模糊错误不太常见来控制此问题.

但在这种情况下,我认为它是一种代码味道.虽然上面的代码是有效的(注释注释)但它有一种脆弱的解决方案.

  • 当然,完全多态的函数`f ::(a - > b) - >(MyType - > MyType)`不能存在.我很好奇为什么海报认为他们需要这样的功能...... (4认同)
  • 在某些翻译的实现中可能需要@ReinHenrichs (2认同)