如何将可选属性附加到值?

时间:2014-03-02 21:49:10

标签: haskell types variant

我想存储一些“东西”列表,这些东西可以附加一些可选的额外属性。每件事物都可以有一个或多个属性。不同的属性有不同的类型。

我希望能够在代码中简明扼要地创建这些内容的文字列表。但我很难看到如何通过类型系统,因为元组允许类型的混合但是固定长度,而列表是可变长度但只有一种类型。

这是我希望能够做到的一个玩具示例:

things = [
   Thing 1 RED,
   Thing 2 RED LARGE,
   Thing 3 BLUE SMALL,
   Thing 4 SMALL,
   Thing 5 BLUE DOTTED
]

这样做的正确方法是什么?

3 个答案:

答案 0 :(得分:7)

假设和弦是Notes的集合。

data Note = A | Bb | B | C | ...

但也有可选的注释集

data Ann = Seventh | Sus2 | Split | ...

我们可以将和弦建模为

data Chord = Chord { notes :: [Note]
                   , anns :: [Ann]
                   }

我们可以用这种方式建立一个完整的词汇

maj :: Note -> Chord
ann :: Ann -> Chord -> Chord
transpose :: Int -> Note -> Note
transposeChord :: Int -> Chord -> Chord

然后像这样构建我们的列表

chords = [
  ann Seventh (maj C)
, ann Split (ann Sus2 (maj A))
]

答案 1 :(得分:4)

基本上,您应该存储具有这些属性的和弦的结果属性,而不是将属性存储为给定。一个简单的(但不是很好的音乐)解决方案是,只存储最后的音高:

newtype Pitch = Pitch {midiNote :: Int}

a, as, bb, b, bs, c, cs, db, d, ds, eb, e, es, f, fs, gb, g, gs, ab :: Pitch
[ a, as,bb, b,bs, c,cs,db, d,ds,eb, e,es, f,fs,gb, g,gs,ab] = map Pitch
 [55,56,56,57,58,58,59,59,60,61,61,62,63,63,64,64,65,66,66]

type Chord = [Pitch]

minor :: Pitch -> Chord
minor (Pitch fund) = map (Pitch . (fund+)) [0, 3, 7]

seventh :: Pitch -> Chord
seventh (Pitch fund) = map (Pitch . (fund+)) [0, 4, 7, 10]

spread :: Chord -> Chord
spread = sort
 . zipWith (\octShift (Pitch note) -> Pitch $ note + 12 * octShift) $ cycle [0,1]

用作例如。

chords :: [Chord]
chords = [ minor e, seventh d, minor e, minor a, seventh b, spread $ minor e ]

更复杂的方法实际上可能以更具音乐意义的方式存储有关和弦的信息:

data Chord = Chord { fundamental :: Pitch
                   , gender :: Maybe ChordGender
                   , ExtraNotes :: [AddNote]
                   , OctaveShifts :: [Int]
                   }

data ChordGender = Major | Minor
data AddNote = AddNote { roughInterval :: Int, intervalIsMajor :: Bool }

major :: Pitch -> Chord
major fund = Chord fund (Just Major) [] []

sus4 :: Pitch -> Chord
sus4 fund = Chord fund Nothing [AddNote 4 False] []

spread :: Chord -> Chord
spread ch@(Chord _ _ _ shifts)
  = ch{shifts = cycle [0,1]}

这可以大致相同的方式使用,但更通用。

如果您不希望将属性作为前缀函数提供,则可以执行as the diagrams package, with

infixl 8 #
(#) :: a -> (a -> b) -> b
(#) = flip ($)

chords = [ c # major
         , g # sus4
         , g # major
         , a # minor
         , f # major # spread
         , g # sus4  # spread
         , g # major # spread
         , c # major # spread
         ]

答案 2 :(得分:3)

在Haskell中有多种方法可以实现heterogeneous collections,但是使用任意类型的任意值列表可能比您需要的更灵活,并且比它的价值更麻烦。你可能最好用一组富有表现力的值来创建一个类型,并使用同类的集合。

您的示例属性似乎属于一组已知类别:颜色(例如RED,BLUE),大小(例如LARGE,SMALL)以及我称之为“纹理”(例如DOTTED)。 Thing不一定在每个类别中都有一个属性,但我会假设它在相同的类别中不应该有多个属性 - 它没有意义单个Thing既可以是大也可以是小。

您可以将这些类别表示为代数数据类型:

data Color = Red
           | Blue

data Size = Large
          | Small

data Texture = Dotted

并将它们与数据结构相结合:

data ThingAttributes = ThingAttributes {
  thingColor :: Maybe Color,
  thingSize :: Maybe Size,
  thingTexture :: Maybe Texture
}

现在,您只需将ThingAttributes个值与每个Thing相关联。

如果想要允许同一类别中的多个属性(例如,Thing同时具有LARGE和SMALL),则可以使用其他代数数据类型来引入所有类别一起打字:

data ThingAttribute = ColorAttribute Color
                    | SizeAttribute Size
                    | TextureAttribute Texture

然后关联Set ThingAttribute - 或者只关注[ThingAttribute],如果您不介意同一属性的重复项,则每个Thing