如何对 Haskell 中的类型执行 scatter/gather 操作?
How can I perform a scatter/gather operation on types in Haskell?
我有一棵包含不同类型节点的树。这些使用数据类型标记:
data Wrapping = A Int
| B String
我想写两个函数:
scatter :: Wrapping -> a
gather :: a -> Output
我的想法是我可以使用 (scatter.gather) :: Wrapping -> Output。 scatter 和 gather 函数当然会有几个不同的变体(每个 scatter 变体都有一个独特的 Wrappingn 数据类型,但中间类型的集合总是相同的),我希望能够干净地组合它们。
我遇到的问题是类型参数 a 并不是真正自由的,它是一组小的显式类型(这里是 {Int,String})。如果我尝试将到目前为止的内容编码为 Haskell 类型 classes,那么我会得到:
{-# LANGUAGE FlexibleInstances #-}
data Wrapping = A Int | B String
class Fanin a where
gather :: a -> String
instance Fanin Int where
gather x = show x
instance Fanin String where
gather x = x
class Fanout a where
scatter :: Fanout a => Wrapping -> a
instance Fanout Int where
scatter (A n) = n
instance Fanout String where
scatter (B x) = x
combined = gather.scatter
这两个 classes 类型检查很好,但显然最后一行会抛出错误,因为 ghc 知道类型参数在每种情况下都匹配,只有在我定义的两个情况下。我尝试了从另一个扩展一个 class 的各种组合:
class Fanin a => Fanout a where ...
class Fanout a => Fanin a where ...
最后我查看了 GADT 和存在类型来解决这个问题,但我在黑暗中跌跌撞撞。我找不到向 GHC 表达合法合格类型签名的方法,我在其中尝试了以下组合:
{-# LANGUAGE RankNTypes #-}
class (forall a. Fanout a) => Fanin a where
class (forall a. Fanin a) => Fanout a where
问题:如何向 GHC 表达我想将 a
限制为集合中的两种类型?
我觉得解决方案在于我看过的一种技术,但我太迷茫了,看不出它是什么...
如果我对你的理解正确,你需要如下内容:
module Main ( main ) where
-- Different kinds of wrapper data types
data WrapperA = A Int | B String
data WrapperB = C Int | D Float
-- A single intermediate data type (with phantom type)
data Intermediate a = E Int | F String
-- Generic scatter and gather functions
class Wrapped a where
scatter :: Wrapped a => a -> Intermediate a
gather :: Wrapped a => Intermediate a -> String
-- Specific scatter and gather implementations
instance Wrapped WrapperA where
scatter (A i) = E i
scatter (B s) = F s
gather (E i) = show i
gather (F s) = s
instance Wrapped WrapperB where
scatter (C i) = E i
scatter (D f) = F $ show f
gather (E i) = show i
gather (F s) = s ++ " was a float"
-- Beautiful composability
combined :: Wrapped a => a -> String
combined = gather . scatter
wrapperAexample1 = A 10
wrapperAexample2 = B "testing"
wrapperBexample1 = C 11
wrapperBexample2 = D 12.4
main :: IO ()
main = do print $ combined wrapperAexample1
print $ combined wrapperAexample2
print $ combined wrapperBexample1
print $ combined wrapperBexample2
主要问题似乎是你有一个中间类型,它可以有不同种类的内容,但这对于不同的包装器来说是不变的。不过,根据包装器的种类,您希望 gather
函数的行为有所不同。
为此,我会定义 Intermediate
类型来指定可以在中间阶段保存的值的种类,并给它一个幻像类型参数(以记住它起源于哪种包装器从)。然后,您可以定义一个 class 来保存 scatter
和 gather
函数,并为不同类型的包装器定义不同的函数。
上面的代码对我来说编译没有错误,并给出以下输出:
"10"
"testing"
"11"
"12.4 was a float"
如您所见,WrapperB
/D Float
输入的处理方式与 WrapperA
/B String
不同(它被标记为浮点值,即使在它已被转换为字符串)。这是因为在 Intermediate
表示中,记住原点是 WrapperB
:一个是 Intermediate WrapperA
类型,另一个是 Intermediate WrapperB
.
另一方面,如果您实际上不希望 gather 函数对不同的包装器有不同的行为,您可以简单地将其从 class 中取出并取出 phantom 类型。让ghc知道中间阶段的类型可以是Int
或String
的最简单方法在我看来仍然定义了类似Intermediate
类型的东西,而不是只使用a
.
The idea is that I can use (scatter.gather) :: Wrapping -> Output.
There will of course be several different variations on both the
scatter and the gather function (with each scatter variant having a
unique Wrappingn datatype, but the set of intermediate types will
always be the same) and I want to be able to cleanly compose them.
如果我没理解错的话,你想要不同的 Wrapping
类型,但中间 a
类型一直是 Either Int String
。我们可以在 classes:
中反映这些信息
data Wrapping = A Int
| B String
class Fanout wrap where
scatter :: wrap -> Either Int String
instance Fanout Wrapping where
scatter (A n) = Left n
scatter (B str) = Right str
class Fanin output where
gather :: Either Int String -> output
instance Fanin String where
gather = either show id
combined :: Wrapping -> String
combined = gather . scatter
此外,根据我从问题中收集到的信息,这个用例似乎并不特别适合输入 classes。特别是,我们可以去掉 Fanin
,然后 combined = either show id . scatter
在我看来比之前的定义更好看。
仅当单个 Either Int String -> a
或 a -> Either Int String
函数对每个 a
有意义并且您希望强制执行时,类型 class 解决方案才有意义这个。
我有一棵包含不同类型节点的树。这些使用数据类型标记:
data Wrapping = A Int
| B String
我想写两个函数:
scatter :: Wrapping -> a
gather :: a -> Output
我的想法是我可以使用 (scatter.gather) :: Wrapping -> Output。 scatter 和 gather 函数当然会有几个不同的变体(每个 scatter 变体都有一个独特的 Wrappingn 数据类型,但中间类型的集合总是相同的),我希望能够干净地组合它们。
我遇到的问题是类型参数 a 并不是真正自由的,它是一组小的显式类型(这里是 {Int,String})。如果我尝试将到目前为止的内容编码为 Haskell 类型 classes,那么我会得到:
{-# LANGUAGE FlexibleInstances #-}
data Wrapping = A Int | B String
class Fanin a where
gather :: a -> String
instance Fanin Int where
gather x = show x
instance Fanin String where
gather x = x
class Fanout a where
scatter :: Fanout a => Wrapping -> a
instance Fanout Int where
scatter (A n) = n
instance Fanout String where
scatter (B x) = x
combined = gather.scatter
这两个 classes 类型检查很好,但显然最后一行会抛出错误,因为 ghc 知道类型参数在每种情况下都匹配,只有在我定义的两个情况下。我尝试了从另一个扩展一个 class 的各种组合:
class Fanin a => Fanout a where ...
class Fanout a => Fanin a where ...
最后我查看了 GADT 和存在类型来解决这个问题,但我在黑暗中跌跌撞撞。我找不到向 GHC 表达合法合格类型签名的方法,我在其中尝试了以下组合:
{-# LANGUAGE RankNTypes #-}
class (forall a. Fanout a) => Fanin a where
class (forall a. Fanin a) => Fanout a where
问题:如何向 GHC 表达我想将 a
限制为集合中的两种类型?
我觉得解决方案在于我看过的一种技术,但我太迷茫了,看不出它是什么...
如果我对你的理解正确,你需要如下内容:
module Main ( main ) where
-- Different kinds of wrapper data types
data WrapperA = A Int | B String
data WrapperB = C Int | D Float
-- A single intermediate data type (with phantom type)
data Intermediate a = E Int | F String
-- Generic scatter and gather functions
class Wrapped a where
scatter :: Wrapped a => a -> Intermediate a
gather :: Wrapped a => Intermediate a -> String
-- Specific scatter and gather implementations
instance Wrapped WrapperA where
scatter (A i) = E i
scatter (B s) = F s
gather (E i) = show i
gather (F s) = s
instance Wrapped WrapperB where
scatter (C i) = E i
scatter (D f) = F $ show f
gather (E i) = show i
gather (F s) = s ++ " was a float"
-- Beautiful composability
combined :: Wrapped a => a -> String
combined = gather . scatter
wrapperAexample1 = A 10
wrapperAexample2 = B "testing"
wrapperBexample1 = C 11
wrapperBexample2 = D 12.4
main :: IO ()
main = do print $ combined wrapperAexample1
print $ combined wrapperAexample2
print $ combined wrapperBexample1
print $ combined wrapperBexample2
主要问题似乎是你有一个中间类型,它可以有不同种类的内容,但这对于不同的包装器来说是不变的。不过,根据包装器的种类,您希望 gather
函数的行为有所不同。
为此,我会定义 Intermediate
类型来指定可以在中间阶段保存的值的种类,并给它一个幻像类型参数(以记住它起源于哪种包装器从)。然后,您可以定义一个 class 来保存 scatter
和 gather
函数,并为不同类型的包装器定义不同的函数。
上面的代码对我来说编译没有错误,并给出以下输出:
"10"
"testing"
"11"
"12.4 was a float"
如您所见,WrapperB
/D Float
输入的处理方式与 WrapperA
/B String
不同(它被标记为浮点值,即使在它已被转换为字符串)。这是因为在 Intermediate
表示中,记住原点是 WrapperB
:一个是 Intermediate WrapperA
类型,另一个是 Intermediate WrapperB
.
另一方面,如果您实际上不希望 gather 函数对不同的包装器有不同的行为,您可以简单地将其从 class 中取出并取出 phantom 类型。让ghc知道中间阶段的类型可以是Int
或String
的最简单方法在我看来仍然定义了类似Intermediate
类型的东西,而不是只使用a
.
The idea is that I can use (scatter.gather) :: Wrapping -> Output. There will of course be several different variations on both the scatter and the gather function (with each scatter variant having a unique Wrappingn datatype, but the set of intermediate types will always be the same) and I want to be able to cleanly compose them.
如果我没理解错的话,你想要不同的 Wrapping
类型,但中间 a
类型一直是 Either Int String
。我们可以在 classes:
data Wrapping = A Int
| B String
class Fanout wrap where
scatter :: wrap -> Either Int String
instance Fanout Wrapping where
scatter (A n) = Left n
scatter (B str) = Right str
class Fanin output where
gather :: Either Int String -> output
instance Fanin String where
gather = either show id
combined :: Wrapping -> String
combined = gather . scatter
此外,根据我从问题中收集到的信息,这个用例似乎并不特别适合输入 classes。特别是,我们可以去掉 Fanin
,然后 combined = either show id . scatter
在我看来比之前的定义更好看。
仅当单个 Either Int String -> a
或 a -> Either Int String
函数对每个 a
有意义并且您希望强制执行时,类型 class 解决方案才有意义这个。