haskell 规范化掉嵌套在类型中的不必要的“Compose”和“Identity”

iyr7buue  于 2023-01-05  发布在  其他
关注(0)|答案(1)|浏览(133)

我想定义一个类型,它包含一个容器和容器的类型,以及一些操作,比如对容器执行外积,Map容器及其值,以及各种标准类(FunctorApplicativeMonadMonadTrans等)提供的所有内容。
这是类型的定义和我提到的两个操作:

-- metacontainer
newtype MC c a = MC { runMetaContainer :: c a } deriving Show

-- outer product
cross :: (Functor f1, Functor f2) => (a -> b -> c) -> MC f1 a -> MC f2 b
  -> MC (Compose f1 f2) c
cross f (MC c1) (MC c2) = MC $ Compose $ (\x -> f x <$> c2) <$> c1

-- map the container
hoist :: (c1 a -> c2 b) -> MC c1 a -> MC c2 b
hoist f (MC c) = MC $ f c

这种方法是有效的,但是当我cross两个MC时,得到的容器是由一些嵌套的 * 不必要的 * 容器组成的,比如ComposeIdentity
请看这个例子:

let list = MC [1,2,3]          :: MC [] Int
let justStr = MC $ Just "hey"  :: MC Maybe String
let n = MC $ Identity 5        :: MC Identity Int

let dblLst = cross (*) list n  :: MC (Compose [] Identity) Int
let justStrList = cross (\x str -> str ++ " " ++ show x)
  dblLst justStr               :: MC (Compose (Compose [] Identity) Maybe) String

因为justStrList的类型中有那些不必要的Compose s和Identity s,所以我不能简单地做类似于...

hoist (\lstMbStr -> lstMbStr & filter isJust & map fromJust) justStrList
-- error
--     • Couldn't match type ‘Compose (Compose [] Identity) Maybe’
--                      with ‘[]’
--       Expected: MyType [] (Maybe b0)
--         Actual: MyType (Compose (Compose [] Identity) Maybe) String

有什么方法可以将容器"标准化"吗?
在本例中,我想将(Compose (Compose [] Identity) Maybe) String转换为等效的[Maybe String]
我试过用夏丽瑶的方法,但是对我不起作用。
这是我的全部代码:

import Data.Maybe ( catMaybes )
import Data.Functor.Identity ( Identity(..) )
import Data.Functor.Compose ( Compose, Compose(..) )
import Data.Coerce (coerce, Coercible)

-- metacontainer
newtype MC c a = MC { runMetaContainer :: c a }

-- outer product
cross :: (Functor f1, Functor f2) => (a -> b -> c) -> MC f1 a -> MC f2 b
  -> MC (Compose f1 f2) c
cross f (MC c1) (MC c2) = MC $ Compose $ (\x -> f x <$> c2) <$> c1

-- map the container
hoist :: (c1 a -> c2 b) -> MC c1 a -> MC c2 b
hoist f (MC c) = MC $ f c

coerceMC :: Coercible (c a) (d a) => MC c a -> MC d a
coerceMC = coerce

main :: IO ()
main = do
    let list = MC [1,2,3]
    let justStr = MC $ Just "hey"
    let n = MC $ Identity 5

    let dblLst = cross (*) list n
    let justStrList = cross (\x str -> str ++ " " ++ show x) dblLst justStr
    
    print $ hoist catMaybes (coerceMC justStrList)

这是我收到的错误信息

error:
    • Couldn't match type: [Char]
                     with: Maybe b0
      Expected: MC (Compose (Compose [] Identity) Maybe) (Maybe b0)
        Actual: MC (Compose (Compose [] Identity) Maybe) [Char]
    • In the first argument of ‘coerceMC’, namely ‘justStrList’
      In the second argument of ‘hoist’, namely ‘(coerceMC justStrList)’
      In the second argument of ‘($)’, namely
        ‘hoist catMaybes (coerceMC justStrList)’

如果我试图向GHC询问coerceMC justStrList的类型,它会说它是MC d0 [Char],其中'd0'是一个不明确的类型变量。
如果我尝试启用PartialTypeSignatures扩展,它会说它无法匹配类型的表示:[Identity (Maybe [Char])]与以下值的关系:d0 [Char].
我不知道如何表达我希望coerceMC justStrList拥有的类型,它应该是MC [Maybe] String,除非它是一个无效的类型,因为种类不匹配。
我尝试强制提升函数(hoist f (MC c) = MC $ f $ coerce c)内部的容器,因为我知道如何表达c的强制(从Compose (Compose [] Identity) Maybe String[Maybe String]),但它仍然不起作用。
我设法让它与hoist的定义一起工作:hoist f (MC c) = MC $ f (coerce c :: [Maybe String]),但我希望找到一个解决方案,如果f的类型存在,则可以从f的类型推断强制类型。

c9qzyr3d

c9qzyr3d1#

可以使用coerce在具有相同运行时表示形式的类型之间进行Map。

import Data.Coerce (coerce, Coercible)

-- Example specialization
example :: MC (Compose (Compose [] Identity) Maybe) a -> MC (Compose [] Maybe) a
example = coerce

coerce具有很强的多态性,通常需要类型注解。以下专门化可以修复一些类型参数,从而有所帮助:

coerceMC :: Coercible (c a) (d a) => MC c a -> MC d a
coerceMC = coerce

您的示例仍然需要更复杂一些,justStrList的类型是MC (Compose (Compose [] Identity) Maybe) String,它简化为MC (Compose [] Maybe) String,还剩下一些Compose,这实际上无法进一步简化。
实际上,最后一个Compose只能在hoist中,在应用给定函数之前被消除。找到一个足够好的hoist的泛化有点棘手。我会选择这个,它只需要一些围绕主函数的转换函数。

hoistBetween :: (c1 a -> x) -> (y -> c2 b) -> (x -> y) -> MC c1 a -> MC c2 b
hoistBetween f h g (MC c) = MC $ h (g (f c))

由于前面提到的类型推断问题,你可能不希望两个都是coerce,一个更特殊的组合子在一方应用coerce会有一个丑陋的、不对称的签名,所以hoistBetween就是了。
你现在可以这样写:

print (hoistBetween coerce id catMaybes justStrList :: MC [] String)

这仍然需要对结果进行类型签名,因为print是多态的,但是在更具体的示例中,您可能需要知道hoistBetween的两侧。
或者,这个hoistBetween还允许您只使用newtype构造函数和析构函数,避免了coerce的类型推断问题(需要一点开销)。

print (hoistBetween
             (fmap runIdentity . getCompose . getCompose)
             id
             catMaybes
             justStrList)

相关问题