Haskell 中的复杂临时多态性
Complex ad-hoc polymorphism in Haskell
我正在尝试使用类型 class 来模拟临时多态性并解决涉及更高种类类型的一般情况,但到目前为止还没有找到正确的解决方案。
我正在寻找的是定义类似于以下内容的内容:
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
infixl 0 >>>
-- | Type class that allows applying a value of type @fn@ to some @m a@
class Apply m a fn b | a fn -> b where
(>>>) :: m a -> fn -> m b
-- to later use it in following manner:
(Just False) >>> True -- same as True <$ ma
(Just True) >>> id -- same as id <$> ma
Nothing >>> pure Bool -- same as Nothing >>= const $ pure Bool
(Just "foo") >>> (\a -> return a) -- same as (Just "foo") >>= (\a -> return a)
到目前为止,我已经尝试了多种选择,其中 none 有效。
只是一个直接的解决方案显然失败了:
instance (Functor m) => Apply m a b b where
(>>>) m b = b <$ m
instance (Monad m) => Apply m a (m b) b where
(>>>) m mb = m >>= const mb
instance (Functor m) => Apply m a (a -> b) b where
(>>>) m fn = fmap fn m
instance (Monad m, a' ~ a) => Apply m a (a' -> m b) b where
(>>>) m fn = m >>= fn
因为有大量与第一个实例相关的基金冲突(全部),很高兴涵盖所有案例(duh)。
我也想不出合适的类型族方法:
class Apply' (fnType :: FnType) m a fn b | a fn -> b where
(>>>) :: m a -> fn -> m b
instance (Functor m) => Apply' Const m a b b where
(>>>) m b = b <$ m
instance (Monad m) => Apply' ConstM m a (m b) b where
(>>>) m mb = m >>= const mb
instance (Functor m, a ~ a') => Apply' Fn m a (a' -> b) b where
(>>>) m mb = m >>= const mb
instance (Functor m, a ~ a') => Apply' Fn m a (a' -> m b) b where
(>>>) m fn = m >>= fn
data FnType = Const | ConstM | Fn | FnM
type family ApplyT a where
ApplyT (m a) = ConstM
ApplyT (a -> m b) = FnM
ApplyT (a -> b) = Fn
ApplyT _ = Const
这里我有几乎相同的问题,其中第一个实例通过fundep与所有实例冲突。
我想要实现的最终结果有点类似于有时在 Scala 中使用的臭名昭著的 magnet pattern。
更新:
为了进一步阐明这种类型的必要性 class,这里有一个比较简单的例子:
-- | Monad to operate on
data Endpoint m a = Endpoint { runEndpoint :: Maybe (m a) } deriving (Functor, Applicative, Monad)
到目前为止,没有必要在适当的位置提及运算符 >>>
,因为用户可能会使用 <$ | <$> | >>=
的标准集。 (实际上,不确定 >>=
,因为无法根据 Monad
定义 Endpoint
)
现在让它更复杂一点:
infixr 6 :::
-- | Let's introduce HList GADT
data HList xs where
HNil :: HList '[]
(:::) :: a -> HList as -> HList (a ': as)
-- Endpoint where a ~ HList
endpoint :: Endpoint IO (HList '[Bool, Int]) = pure $ True ::: 5 ::: HNil
-- Some random function
fn :: Bool -> Int -> String
fn b i = show b ++ show i
fn <$> endpoint -- doesn't work, as fn is a function of a -> b -> c, not HList -> c
此外,假设函数 fn
也可能因此定义为 m String
。这就是为什么我正在寻找一种方法来向 API 用户隐藏这种复杂性。
值得一提的是,我已经有一个类型 class 可以将 a -> b -> c
转换为 HList '[a, b] -> c
如果目标是对 HList
进行抽象,那就去做吧。不要通过在每个参数中引入一个可能的 monad 包装器来混淆事情,结果是 quite complicated indeed。而是使用所有常用工具在功能级别进行包装和提升。所以:
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
data HList a where
HNil :: HList '[]
(:::) :: x -> HList xs -> HList (x : xs)
class ApplyArgs args i o | args i -> o, args o -> i where
apply :: i -> HList args -> o
instance i ~ o => ApplyArgs '[] i o where
apply i _ = i
instance (x ~ y, ApplyArgs xs i o) => ApplyArgs (x:xs) (y -> i) o where
apply f (x ::: xs) = apply (f x) xs
我正在尝试使用类型 class 来模拟临时多态性并解决涉及更高种类类型的一般情况,但到目前为止还没有找到正确的解决方案。
我正在寻找的是定义类似于以下内容的内容:
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
infixl 0 >>>
-- | Type class that allows applying a value of type @fn@ to some @m a@
class Apply m a fn b | a fn -> b where
(>>>) :: m a -> fn -> m b
-- to later use it in following manner:
(Just False) >>> True -- same as True <$ ma
(Just True) >>> id -- same as id <$> ma
Nothing >>> pure Bool -- same as Nothing >>= const $ pure Bool
(Just "foo") >>> (\a -> return a) -- same as (Just "foo") >>= (\a -> return a)
到目前为止,我已经尝试了多种选择,其中 none 有效。 只是一个直接的解决方案显然失败了:
instance (Functor m) => Apply m a b b where
(>>>) m b = b <$ m
instance (Monad m) => Apply m a (m b) b where
(>>>) m mb = m >>= const mb
instance (Functor m) => Apply m a (a -> b) b where
(>>>) m fn = fmap fn m
instance (Monad m, a' ~ a) => Apply m a (a' -> m b) b where
(>>>) m fn = m >>= fn
因为有大量与第一个实例相关的基金冲突(全部),很高兴涵盖所有案例(duh)。
我也想不出合适的类型族方法:
class Apply' (fnType :: FnType) m a fn b | a fn -> b where
(>>>) :: m a -> fn -> m b
instance (Functor m) => Apply' Const m a b b where
(>>>) m b = b <$ m
instance (Monad m) => Apply' ConstM m a (m b) b where
(>>>) m mb = m >>= const mb
instance (Functor m, a ~ a') => Apply' Fn m a (a' -> b) b where
(>>>) m mb = m >>= const mb
instance (Functor m, a ~ a') => Apply' Fn m a (a' -> m b) b where
(>>>) m fn = m >>= fn
data FnType = Const | ConstM | Fn | FnM
type family ApplyT a where
ApplyT (m a) = ConstM
ApplyT (a -> m b) = FnM
ApplyT (a -> b) = Fn
ApplyT _ = Const
这里我有几乎相同的问题,其中第一个实例通过fundep与所有实例冲突。
我想要实现的最终结果有点类似于有时在 Scala 中使用的臭名昭著的 magnet pattern。
更新:
为了进一步阐明这种类型的必要性 class,这里有一个比较简单的例子:
-- | Monad to operate on
data Endpoint m a = Endpoint { runEndpoint :: Maybe (m a) } deriving (Functor, Applicative, Monad)
到目前为止,没有必要在适当的位置提及运算符 >>>
,因为用户可能会使用 <$ | <$> | >>=
的标准集。 (实际上,不确定 >>=
,因为无法根据 Monad
定义 Endpoint
)
现在让它更复杂一点:
infixr 6 :::
-- | Let's introduce HList GADT
data HList xs where
HNil :: HList '[]
(:::) :: a -> HList as -> HList (a ': as)
-- Endpoint where a ~ HList
endpoint :: Endpoint IO (HList '[Bool, Int]) = pure $ True ::: 5 ::: HNil
-- Some random function
fn :: Bool -> Int -> String
fn b i = show b ++ show i
fn <$> endpoint -- doesn't work, as fn is a function of a -> b -> c, not HList -> c
此外,假设函数 fn
也可能因此定义为 m String
。这就是为什么我正在寻找一种方法来向 API 用户隐藏这种复杂性。
值得一提的是,我已经有一个类型 class 可以将 a -> b -> c
转换为 HList '[a, b] -> c
如果目标是对 HList
进行抽象,那就去做吧。不要通过在每个参数中引入一个可能的 monad 包装器来混淆事情,结果是 quite complicated indeed。而是使用所有常用工具在功能级别进行包装和提升。所以:
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
data HList a where
HNil :: HList '[]
(:::) :: x -> HList xs -> HList (x : xs)
class ApplyArgs args i o | args i -> o, args o -> i where
apply :: i -> HList args -> o
instance i ~ o => ApplyArgs '[] i o where
apply i _ = i
instance (x ~ y, ApplyArgs xs i o) => ApplyArgs (x:xs) (y -> i) o where
apply f (x ::: xs) = apply (f x) xs