Return 子类型集

Return Set of subtype

假设我的数据类型定义为

data Foo = Hello Int | World Int

并充当

isWorld :: Foo -> Bool
isWorld (World i) = True
isWorld (Hello i) = False

getWorlds :: Set Foo -> Set World
getWorlds = Set.filter isWorld

这不起作用:

Not in scope: type constructor or class `World'

这是有道理的,因为 World 只是一个函数,但我不知道如何在 Haskell 中对其建模。使用 data 正确吗?我希望能够仅为某些实例定义函数:

foo :: World -> Int
foo (World i) = i

正如预期的那样,这会引发相同的错误,因为 World 不是类型。有没有办法做到这一点,最好没有 GHC 扩展?

完成此操作的最简单方法是简单地为每个构造函数的值创建一个类型。

newtype Hello = Hello Int deriving (Eq,Ord)
newtype World = World Int deriving (Eq,Ord)
type Foo = Either Hello World

getWorlds :: Set Foo -> Set World
getWorlds = Set.fromList . rights . Set.toList       -- import Data.Either

问题是您想要一个包含两种可能事物的列表(编辑:或者更确切地说是一组,但适用相同的逻辑),而 Haskell 中的常规列表是同源的。但是如果你像这样定义你的数据,通过将一组单独的类型包装在另一种数据类型中:

newtype Hello = Hello Int
newtype World = World Int
data Foo = FooHello Hello
         | FooWorld World

您仍然可以:

  • 有一个列表 Foo
  • filterFoo使用Foo -> Bool类型的函数实现f Foo -> f World(作为例子)
  • 根据需要将 Foo 扩展到任意数量的构造函数

根据您尝试执行的操作,可能已经有一些库可以解决类似的问题。我至少可以想到 one 似乎相关。

在你的代码中 World 不是类型,它只是一个构造函数。

Haskell 中的惯用解决方案是使用 Either 创建类型的联合:

newtype Hello = Hello { getHello :: Int } deriving (Ord, Eq)
newtype World = World { getWorld :: Int } deriving (Ord, Eq)
type Foo = Either Hello World

isWorld :: Foo -> Bool
isWorld = Data.Either.isRight

getWorlds :: Set Foo -> Set World
getWorlds = Set.fromDistinctAscList . Data.Either.rights . Set.toAscList

foo :: World -> Int
foo = getWorld

它适用于两个以上的构造函数

newtype Cheese = Cheese { getCheese :: Int }
newtype Bread = Bread { getBread :: Int }
newtype Wine = Wine { getWine :: Int }

type Comestible = Either Cheese (Either Bread Wine)

但是如果你觉得parens令人反感,你可以使用Either中缀:

{-# LANGUAGE TypeOperators #-}
-- ...
type Comestible = Cheese `Either` Bread `Either` Wine
-- (NOTE: this parses as Either (Either Cheese Bread) Wine

如果太冗长,您可以声明自己的运算符:

type (+) = Either
infixr 5 +
type Comestible = Cheese + Bread + Wine

我在这里使用了 + 因为 Either a b 是一个 sum 类型,而 (a,b) 是一个 产品 类型。

您可以选择另外两个选项:

保持简单

并仅处理各个备选方案的 内容 。 IE。你可以写

getWorlds :: Set Foo -> Set Int
getWorlds = Set.mapMonotonic (\(World i)->i) $ Set.filter isWorld

只使用一种data类型...

但通过类型约束来限制可以采用哪些替代项:

{-# LANGUAGE GADTs, DataKinds, KindSignatures #-}

data Foo (a :: FooC) where
  Hello :: CanHello a => Int -> Foo a
  World :: CanWorld a => Int -> Foo a

data FooC = FooC | HelloC | WorldC

class CanHello (a :: FooC)
instance CanHello 'FooC
instance CanHello 'HelloC

class CanWorld (a :: FooC)
instance CanWorld 'FooC
instance CanWorld 'WorldC

此时,你有Foo三种不同的“味道”:

  • Foo' 'FooC 就像您的原始类型:它的值可以是 HelloWorld,因为 FooC 是两者的实例 类。
  • Foo' 'WorldC 就像您所说的 World 类型:它的构造函数 WorldFoo 相同,但它不允许 Hello 构造函数.
  • Foo' 'HelloC 允许 Hello 构造函数。

所以你现在可以给他们起名字了...

type Foo = Foo' 'FooC
type World = Foo' 'WorldC
type Hello = Foo' 'HelloC

然后写

getWorlds :: Set Foo -> Set World
getWorlds = Set.mapMonotonic (\(World i)->World i) $ Set.filter isWorld

函数\(World i) -> World i看起来多余,但实际上是必需的:你将数据包装在一个新的约束中,它实际上证明它总是World