2016-05-14 5 views
2

От http://learnyouahaskell.com/making-our-own-types-and-typeclassesИзбежание примитивной одержимости в Haskell

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

В реальном приложении, используя примитивы, такие как строки и Int для имени и возраста будут constitue примитивной одержимости, код запаха. (Очевидно, также дата родился предпочтительнее 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?

ответ

8

Сделайте Name аннотация и предоставите умный конструктор. Это означает, что вы не экспортируете конструктор данных Name и обеспечиваете Maybe -returning конструктора вместо:

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 
+0

не могли бы вы рассказать о различиях между тем же, использовать тип из Data.Word или неэффективное предложение? Может быть, Liquid Haskell? – fred

+0

Предоставление умного конструктора для 'Age' - это некоторая работа; вам нужно создать новый тип и две функции; но он эффективен и прост в использовании. 'Data.Word' - это просто целое число без знака, например' uint' в C#. На самом деле мало пользы от использования представления 'Zero' /' PlusOne'. Я очень мало знаю о Liquid Haskell, но в F *, который также имеет типы уточнения, вы должны написать 'type Age = i: int {i> = 0}', и проверка типа отклонит вашу программу, если она не равна 100 % уверен, что возраст, который вы проходите, больше или равен нулю. – rightfold

+0

Я знаю, что это просто глупый пример, но многие люди делают ошибку реального мира, устанавливая ограничение длины для имен, которые слишком коротки, чтобы обрабатывать все имена, которые есть на самом деле. Некоторые ограничения разумны, чтобы избежать DOS-атак и т. Д., Но я был бы очень подозрительно относиться к ограничению длины имени под 256 символами или около того, и если бы я разрабатывал систему, я бы хотел обратиться к эксперту, чтобы убедиться, что этого достаточно , – dfeuer

2

Это может быть код запах в некоторых языках, но это обычно не считается одним в Haskell. Вы должны выбрать конкретное представление имени и даты рождения где-то, и объявление типа данных Person, вероятно, является лучшим местом для этого. В Haskell обычным способом сохранения другого кода в зависимости от представления имени было бы сделать абзац Person. Вместо того, чтобы выставлять конструктор Person, выставляйте функции для создания, изменения и проверки значений Person.

+0

По моему опыту, практически все разработчики и кодовые базы на всех языках предпочитают примитивы для всего и используют имена переменных и встроенные проверки, а не классы/типы, чтобы указывать и проверять, что это такое. Они сказали бы, что мой пример был одержимостью класса/типа и называет это запахом кода. – fred

+0

@fred, это действительно зависит от контекста и того, как программа растет. Преждевременная абстракция и преждевременное обобщение могут тратить много времени, если ваши первоначальные предположения ошибочны. «Лицо» похоже на довольно разумную базовую абстракцию, но опять же, это зависит от программы. – dfeuer

+0

Смешно.Haskell имеют типы фантомов, выразительный полиморфизм, интеллектуальные конструкторы, подвергая типы без конструкторов. Все, что нужно для эффективной борьбы с примитивной одержимостью! Примитивная навязчивая идея - когда в представлении памяти различаются каким-либо образом от законных данных домена + поведения. (Например, абсолютный размер и относительный размер будут представлены теми же значениями в памяти компьютера, однако путаница с другими будет иметь ужасные последствия. Добавить фантомный тип. Hakell укажет на эти ошибки) –

Смежные вопросы