返回子类型集

时间:2017-08-09 13:11:37

标签: haskell types ghc

假设我的数据类型定义为

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扩展?

4 个答案:

答案 0 :(得分:5)

实现此目的的最简单方法是简单地为每个构造函数的值创建一个类型。

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

答案 1 :(得分:4)

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

public class Main {

    public static void main(String[] args) {

        Config config = new Config();
        HazelcastInstance instance = Hazelcast.newHazelcastInstance(config);

        final MapConfig customerMapConf = config.getMapConfig("customers");
        final MapStoreConfig customerMapStoreConf = customerMapConf.getMapStoreConfig();
        customerMapStoreConf.setImplementation(new SimpleMapLoader());
        customerMapStoreConf.setEnabled(true);
        customerMapConf.setMapStoreConfig(customerMapStoreConf);
        config.addMapConfig(customerMapConf);
        MapIndexConfig indexConfig = new MapIndexConfig();
        indexConfig.setAttribute("name");
        indexConfig.setOrdered(false);
        customerMapConf.setMapIndexConfigs(Arrays.asList(indexConfig));
        customerMapStoreConf.setInitialLoadMode(InitialLoadMode.LAZY);
        Map<Integer, String> map = instance.getMap("customers");

       // System.out.println(map.get("1"));

        instance.shutdown();
    }

}

你仍然可以:

  • 有一个newtype Hello = Hello Int newtype World = World Int data Foo = FooHello Hello | FooWorld World
  • 列表
  • 使用Foo类型的函数过滤Foo以实现Foo -> Bool(作为示例)
  • 根据需要将Foo扩展到任意数量的构造函数

根据您的尝试,可能已经有一些库可以解决类似的问题。我认为至少one看似相关。

答案 2 :(得分:2)

在您的代码中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 infix:

{-# 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 类型,而<{1>}是产品类型。

答案 3 :(得分:1)

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

保持简单

并且只处理各个替代品的内容。即你可以写

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' 'WorldCWorld类型的含义相同:其构造函数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