首页 > 解决方案 > Haskell 中的复杂临时多态性

问题描述

我正在尝试使用类型类来模拟临时多态性并解决涉及更高种类类型的通用案例,但到目前为止无法找出正确的解决方案。

我正在寻找的是定义类似于:

{-# 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 中使用的臭名昭著的磁铁模式。

更新:

为了进一步阐明对这种类型类的需求,这里有一个简单的例子:

-- | Monad to operate on
data Endpoint m a = Endpoint { runEndpoint :: Maybe (m a) } deriving (Functor, Applicative, Monad)

到目前为止,还没有太大的必要>>>在适当的位置提及操作符,因为用户可能会使用标准集来<$ | <$> | >>=代替。(实际上,不确定,>>=因为没有办法用 来Endpoint定义Monad

现在让它更复杂一点:

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 -> cHList '[a, b] -> c

标签: haskelltypeclass

解决方案


如果目标是对HLists 进行抽象,那么就这样做。不要通过在每个参数中引入可能的 monad 包装器来混淆事情,事实证明它确实非常复杂。而是使用所有常用工具在功能级别进行包装和提升。所以:

{-# 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

推荐阅读