【问题标题】:How do I attach optional attributes to values?如何将可选属性附加到值?
【发布时间】:2014-03-02 21:49:09
【问题描述】:

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

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

这是我想做的一个玩具示例:

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

等等

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

【问题讨论】:

  • 你给出一个稍微不那么玩具的例子,至少大致是你所针对的应用程序?
  • 这是一个音乐应用程序。 “事物”实际上是和弦,可选属性确实暗示了特定类型的转换(添加 7 度,删除根,分布在两个八度等)我不想进入太多细节,因为我不'不希望人们说“做音乐,使用 Haskore”等。这是一个学习练习,所以我用它来理解任何特定应用程序之外的原理。

标签: haskell types variant


【解决方案1】:

假设和弦是音符的集合。

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))
]

【讨论】:

    【解决方案2】:

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

    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
             ]
    

    【讨论】:

      【解决方案3】:

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

      您的示例属性似乎属于一组已知类别:颜色(例如 RED、BLUE)、大小(例如 LARGE、SMALL)以及我称之为“纹理”的东西(例如 DOTTED)。 Thing 不一定在每个类别中都有一个属性,但我假设它在 same 类别中不应有多个属性 - 对于单个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 关联起来。

      如果您确实希望允许同一类别中的多个属性(例如,具有 LARGE 和 SMALL 的 Thing),您可以使用另一种代数数据类型将所有类别类型组合在一起:

      data ThingAttribute = ColorAttribute Color
                          | SizeAttribute Size
                          | TextureAttribute Texture
      

      然后将Set ThingAttribute——或者如果你不介意相同属性的重复,只需一个[ThingAttribute]——与每个Thing相关联。

      【讨论】:

      • 谢谢。我想我会选择其他解决方案,但我从中学到了很多。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-12
      • 2022-01-12
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      相关资源
      最近更新 更多