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
- filter
Foo
使用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
就像您的原始类型:它的值可以是 Hello
或 World
,因为 FooC
是两者的实例 类。
Foo' 'WorldC
就像您所说的 World
类型:它的构造函数 World
与 Foo
相同,但它不允许 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
。
假设我的数据类型定义为
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
- filter
Foo
使用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
就像您的原始类型:它的值可以是Hello
或World
,因为FooC
是两者的实例 类。Foo' 'WorldC
就像您所说的World
类型:它的构造函数World
与Foo
相同,但它不允许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
。