避免在Haskell中进行原始的痴迷

时间:2016-05-14 10:57:39

标签: haskell types primitive

来自http://learnyouahaskell.com/making-our-own-types-and-typeclasses

data Person = Person { name :: String  
                     , age :: Int  
                     } deriving (Show)   

在一个真实的应用程序中,使用像String和Int这样的原语作为名称和年龄将构成原始的痴迷,代码气味。 (显然,出生日期优于国际时代,但让我们忽略它)相反,人们更喜欢像

这样的东西
newtype Person = Person { name :: Name  
                        , age :: Age  
                        } deriving (Show)   

在OO语言中,这看起来像

class Person {
  Name name;
  Age age;
  Person(Name name, Age age){
    if (name == null || age == null)
      throw IllegalArgumentException();
    this.name = name;
    this.age = age;
  }
}

class Name extends String {
  Name(String name){
    if (name == null || name.isEmpty() || name.length() > 100)
      throw IllegalArgumentException();
    super(name);
  }
}

class Age extends Integer {
  Age(Integer age){
    if (age == null || age < 0)
      throw IllegalArgumentException();
    super(age);
  }
}

但在惯用的最佳实践Haskell中如何实现同样的目标?

2 个答案:

答案 0 :(得分:8)

使Name抽象并提供智能构造函数。这意味着您不导出Name数据构造函数,而是提供Maybe - 返回构造函数:

module Data.Name
( Name -- note: only export type, not data constructor
, fromString
, toString
) where

newtype Name = Name String

fromString :: String -> Maybe Name
fromString n | null n         = Nothing
             | length n > 100 = Nothing
             | otherwise      = Just (Name n)

toString :: Name -> String
toString (Name n) = n

现在无法在此模块之外构造错误长度的Name值。

对于Age,您可以执行相同的操作,或使用Data.Word中的类型,或使用以下效率低但保证非负的表示形式:

data Age = Zero | PlusOne Age

答案 1 :(得分:2)

这可能是某些语言中的代码嗅觉,但它通常不被认为是Haskell中的一种。您必须选择名称和出生日期某处的具体表示,Person的数据类型声明可能是最佳选择。在Haskell中,保持其他代码不依赖于名称表示的常用方法是使Person抽象。不要公开Person构造函数,而是公开用于创建,修改和检查Person值的函数。