Значения по умолчанию в типах данных Haskell
когда вы определяете класс на объектно-ориентированном языке, он обычно устанавливает значения по умолчанию для переменных-членов. Есть ли какой-либо механизм в Haskell, чтобы сделать то же самое в типах записей? И следующий вопрос: если мы с самого начала не знаем всех значений для конструктора данных, но получаем их из взаимодействия IO, можем ли мы построить тип, используя что-то вроде шаблона builder из OOP?
спасибо заранее
3 ответов
общей идиомой является определение значения по умолчанию.
data A = A { foo :: Int , bar :: String }
defaultA :: A
defaultA = A{foo = 0, bar = ""}
это может быть затем (чисто)" обновлено " позже с реальными значениями.
doSomething :: Bool -> A
doSomething True = defaultA{foo = 32}
doSomething False = defaultA{bar = "hello!"}
псевдокод пример:
data Options = O{ textColor :: Bool, textSize :: Int, ... }
defaultOptions :: Options
defaultOptions = O{...}
doStuff :: Options -> IO ()
doStuff opt = ...
main :: IO ()
main = do
...
-- B&W, but use default text size
doStuff defaultOptions{ color = False }
если нет разумных значений по умолчанию, вы можете обернуть значения полей в Maybe
.
если вы чувствуете себя предприимчивым, вы даже можете использовать более продвинутый подход статически отделять" промежуточные " значения опций, в которых может не хватать нескольких полей, от "доработанные", которые должны иметь все поля. (Я бы не рекомендовал это новичкам Haskell.)
есть ли какой-либо механизм в Haskell, чтобы сделать то же самое в типах записей?
что вы можете сделать, это скрыть конструктор и предоставить функцию в качестве конструктора вместо.
скажем, например, у нас есть список, который мы хотим обновить, вместе с рядом изменений, то мы можем определить его как:
data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
deriving Show
теперь мы можем определить функцию, которая инициализирует BuildList
с начальной список:
revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
и скрывая конструктор в module
экспорт, таким образом, мы скрываем возможность инициализации его с другой ревизией, чем ревизия 0
. Таким образом, модуль может выглядеть так:
module Foo(RevisionList(), revisionList)
data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
что-то вроде шаблона от ООП?
мы можем, например, использовать State
монада за это. Например:
module Foo(RevisionList(), revisionList,
increvision, RevisionListBuilder, prefixList)
import Control.Monad.State.Lazy
type RevisionListBuilder a = State (RevisionList a)
increvision :: RevisionListBuilder a ()
increvision = do
rl <- get
put (rl { revision = 1 + revision rl})
prefixList :: a -> RevisionListBuilder a ()
prefixList x = do
rl <- get
put (rl { theList = x : theList rl })
increvision
мы get
the RevisionList
до сих пор, выполнять обновления, а put
в новый результат вернулся.
теперь другой модуль может импортировать наши Foo
, и используйте строитель как:
some_building :: RevisionListBuilder Int ()
some_building = do
prefixList 4
prefixList 1
и теперь мы можем "сделать"RevisionList
в редакции 2
с окончательным списком [1,4,2,5]
С:
import Control.Monad.State.Lazy(execState)
some_rev_list :: RevisionList Int
some_rev_list = execState some_building (revisionList [2,5])
так это будет выглядеть примерно так:
Foo.hs
:
module Foo(RevisionList(), revisionList,
increvision, RevisionListBuilder, prefixList)
data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
deriving Show
type RevisionListBuilder a = State (RevisionList a)
revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
increvision :: RevisionListBuilder a ()
increvision = do
rl <- get
put (rl { revision = 1 + revision rl})
prefixList :: a -> RevisionListBuilder a ()
prefixList x = do
rl <- get
put (rl { theList = x : theList rl })
increvision
Bar.hs
:
import Foo
import Control.Monad.State.Lazy(execState)
some_building :: RevisionListBuilder Int ()
some_building = do
prefixList 4
prefixList 1
some_rev_list :: RevisionList Int
some_rev_list = execState some_building (revisionList [2,5])
Итак, теперь мы построили some_rev_list
С "здание" some_building
:
Foo Bar> some_rev_list
RevisionList {theList = [1,4,2,5], revision = 2}
здесь уже есть хорошие ответы, поэтому этот ответ предназначен только как дополнение к прекрасным ответам от чи и Виллем Ван Онсем.
в основных объектно-ориентированных языках, таких как Java и C#, это не то, что объект по умолчанию неинициализированное; скорее, объект по умолчанию обычно инициализируется значениями по умолчанию для их типов, и просто случается, что для ссылочных типов значение по умолчанию равно null ссылка.
Haskell не имеет нулевых ссылок, поэтому записи не могут быть инициализированы с помощью нулей. Самым прямым переводом объектов будут записи, в которых каждый отдельный элемент является Maybe
. Это не особенно полезно, однако, но это подчеркивает, как трудно защитить инварианты в ООП.
шаблон Builder не решает эту проблему вообще. Любой конструктор должен начинаться с начального объекта Builder, и этот объект должен иметь значений по умолчанию.
для более подробной информации и множества примеров я написал серия статей об этом. Серия статей специально посвящена шаблону построителя тестовых данных, но вы должны видеть, как он обобщается на шаблон Fluent Builder в целом.