【发布时间】:2019-08-06 01:50:38
【问题描述】:
我正在尝试使用类型类来模拟临时多态性并解决涉及更高种类类型的通用案例,但到目前为止无法找出正确的解决方案。
我正在寻找的是定义类似于:
{-# 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)
到目前为止,我已经尝试了多种选择,但都没有奏效。 一个简单的解决方案显然失败了:
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。
更新:
为了进一步阐明对此类类型类的需求,这里有一个简单的示例:
-- | 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 用户隐藏这种复杂性。
值得一提的是,我已经有一个类型类可以将a -> b -> c 转换为HList '[a, b] -> c
【问题讨论】:
-
噢,来吧...为什么最近有这么多人在没有给出理由的情况下投反对票? IMO 这可能不是一个特别有用的问题(你为什么想要那个?),但它肯定是明确规定的,它确实显示出足够的研究努力。
-
¯\_(ツ)_/¯ 可能与提到 Scala 有关? :) 回答“我为什么要这样做”是为了在单个运算符
>>>下统一对我的 monad 的可能操作,否则我必须按情况介绍它们,这可能会产生很多 噪音 在最终的 API 外观中。 -
嗯,类型类的一个好的经验法则是:如果你不能在类上编写任何有用的多态,那么使用首先上课。在你的情况下,我看不出
>>>比直接使用<$、<$>和>>=有什么好处。 -
好的,例子。我有以下单子:
data Foo m a = Foo { underlying: m (Maybe a) }。至于a是一些原始的,你是完全正确的,通常的运营商很好。现在,让我们想象一下a :: HList '[_p0, _p1]。用户没有提供函数_p0 -> _p1 -> out,而是“注定”要处理HList '[_p0, _p1] -> out,这可能不是最好的体验。 -
我还不太相信,但也许这实际上是一个很好的用例。您为什么不详细说明一下并将其添加到问题中?