在Haskell中邻接两个泛型Either
我正在寻找一种方法,将两个在Haskell中邻接两个泛型Either,haskell,Haskell,我正在寻找一种方法,将两个要么a b和要么c d连接在一起,并将要么a(要么b(要么c d))作为最终结果。但是如果b~或者或者/或者d~或者,它也应该能够递归地“平坦”两个eithers 我尝试用fundep定义类型类: 类与AB c | AB->c相邻,其中 邻接:a->b->c 但无法为该类提供任何有意义的实例。我觉得可以通过类型族来实现,但我还不够精通 本质上,我试图从Scala中复制shapeless,我写了另一个答案,但它是不正确的,因为它也没有递归地展平嵌套的。希望这个能起作用
要么a b
和要么c d
连接在一起,并将要么a(要么b(要么c d))
作为最终结果。但是如果b~或者或者/或者d~或者,它也应该能够递归地“平坦”两个eithers
我尝试用fundep定义类型类:
类与AB c | AB->c相邻,其中
邻接:a->b->c
但无法为该类提供任何有意义的实例。我觉得可以通过类型族来实现,但我还不够精通
本质上,我试图从Scala中复制shapeless,我写了另一个答案,但它是不正确的,因为它也没有递归地展平嵌套的。希望这个能起作用
一些必要的扩展:
{-# LANGUAGE DataKinds, MultiParamTypeClasses, FunctionalDependencies,
UndecidableInstances, FlexibleInstances, ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-} -- To enable supplying types with @
{-# LANGUAGE AllowAmbiguousTypes #-} -- Not strictly necessary, just to avoid Proxy
课程本身:
class Flatten input result | input -> result where
flatten :: input -> result
-- Branch can be used as a kind thanks to DataKinds
data Branch = RebalanceNeeded
| RebalanceNotNeeded
| Atomic
type family WhichBranch t :: Branch where
WhichBranch (Either (Either _ _) _) = RebalanceNeeded
WhichBranch (Either _ _) = RebalanceNotNeeded
WhichBranch _ = Atomic
class Flatten' (branch :: Branch) input result | branch input -> result where
flatten' :: input -> result
-- We always delegate on the auxiliary class
instance Flatten' (WhichBranch input) input result => Flatten input result where
flatten = flatten' @(WhichBranch input)
-- The left branch is itself another either. We need to rebalance and keep flattening.
instance Flatten (Either x (Either y z)) r
=> Flatten' RebalanceNeeded (Either (Either x y) z) r where
flatten' e = case e of
Left (Left x) -> flatten @(Either x (Either y z)) (Left x)
Left (Right y) -> flatten @(Either x (Either y z)) (Right (Left y))
Right z -> flatten @(Either x (Either y z)) (Right (Right z))
-- The left branch is not itself an either. We only flatten the right branch.
instance (Flatten y y') => Flatten' RebalanceNotNeeded (Either x y) (Either x y') where
flatten' e = case e of
Left x -> Left x
Right y -> Right (flatten @y y)
instance Flatten' Atomic x x where
flatten' = id
该解决方案利用分支类型族来检查最左边的类型。结果被输入到一个辅助类型类flant'
,该类处理额外的信息。这是一种避免恼人的“重叠实例”错误的解决方法
另一种选择是简单地将{-#OVERLAPPABLE#-}
和{-#OVERLAPPING#-}
杂注放在实例上,在没有辅助类和类型族的情况下工作
使用示例:
ghci> :t flatten (undefined :: Either (Either Bool Float) (Either (Either Char Word) Int))
Either Bool (Either Float (Either Char (Either Word Int)))
编辑:代替多参数类型分类,编码这些单向转换的另一种方法是:
其优点是,现在我们可以显式要求计算结果类型:
ghci> :kind! (Flattened (Either (Either Bool Float) (Either (Either Char Word) Int)))
Either Bool (Either Float (Either Char (Either Word Int)))
你说的“平坦”是什么意思?说,我有ab
和cd
其中b~或者ef
和d~或者ghg
,最后它应该是或者a(或者f(或者c(或者ghg))
而不是或者a(或者ef))(或者c(或者ghh))
所以你想让它“正确递归”吗?不确定这里的语法术语是否适用,但是的,即使是“非递归”的情况对我来说也不直接。关于这两个参数,我们有4个案例:左/左、左/右、右/左、右/右。在这4种情况下,您希望输出是什么?相关:“避免与封闭类型族重叠实例”谢谢,这太棒了!另外,类型附近的@
的含义是什么?@SergeyKolbasov这是一个类型应用程序,它不是严格需要的,另一种方法是通过添加带有普通类型注释的数据.Proxy
参数来解决类型歧义。
ghci> :kind! (Flattened (Either (Either Bool Float) (Either (Either Char Word) Int)))
Either Bool (Either Float (Either Char (Either Word Int)))