2013-03-31 3 views
9

Я довольно новый программист Haskell, и я пытаюсь понять, как получить некоторые значения в алгебраическом типе данных.Инициализация типа алгебраических данных из списка

У меня есть тип записи данных:

data OrbitElements = OrbitElements { epoch :: Double, 
            ecc :: Double, 
            distPeri :: Double, 
            incl :: Double, 
            longAscNode :: Double, 
            argPeri :: Double, 
            timePeri :: Double, 
            meanMotion :: Double, 
            meanAnomaly :: Double, 
            trueAnomaly :: Double, 
            semiMajorAxis :: Double, 
            distApo :: Double, 
            period :: Double 
            } 

Я вытягивать в некоторой информации из текстового файла, который заканчивается в списке двойников. Есть ли простой способ инициализировать этот тип данных со списком? Я мог бы просто называть каждого сеттера индивидуально, но это кажется ужасно неэффективным, когда у меня уже есть все значения в списке.

let d = [2456382.5,6.786842103348031e-3,0.7184187640759256,3.394660181513041,76.64395338801751,55.2296201483587,2456457.141012543,1.602144936476915,240.4142797010899,239.7408018186761,0.7233278761603762,0.7282369882448266,224.6987721295883] 
let o = OrbitElements 
let epoch o = d !! 0 
let ecc o = d !! 1 
-- and so on 

Что мне не хватает?

ответ

16

Самый прямой путь, чтобы просто сделать это вручную:

fromList :: [Double] -> Maybe OrbitElements 
fromList [ _epoch 
     , _ecc 
     , _distPeri 
     , _incl 
     , _longAscNode 
     , _argPeri 
     , _timePeri 
     , _meanMotion 
     , _meanAnomaly 
     , _trueAnomaly 
     , _semiMajorAxis 
     , _distApo 
     , _period 
     ] 
    = Just $ OrbitElements 
      _epoch 
      _ecc 
      _distPeri 
      _incl 
      _longAscNode 
      _argPeri 
      _timePeri 
      _meanMotion 
      _meanAnomaly 
      _trueAnomaly 
      _semiMajorAxis 
      _distApo 
      _period 
fromList _ = Nothing 

Однако, есть немного сексуальнее путь, который должен разобрать их поэлементно, который менее подвержен ошибкам и более описательный о том, что мы пытаемся сделать:

Сначала мы определяем два синтаксических анализатора, один из которых запрашивает новый элемент из списка (или сбой, если список пуст), а второй из них соответствует концу списка (или не удается, если список не пуст):

import Control.Applicative 
import Control.Monad 
import Control.Monad.Trans.State 

getElem :: StateT [Double] Maybe Double 
getElem = do 
    s <- get 
    case s of 
     [] -> mzero 
     x:xs -> do 
      put xs 
      return x 

endOfList :: StateT [Double] Maybe() 
endOfList = do 
    s <- get 
    case s of 
     [] -> return() 
     _ -> mzero 

Теперь мы можем определить fromList в Applicative стиле:

fromList' :: [Double] -> Maybe OrbitElements 
fromList' = evalStateT $ OrbitElements 
    <$> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <*> getElem 
    <* endOfList 
+1

Спасибо, это подтвердили мои подозрения, и любой ответ, который начинается «Во-первых, мы определяем два парсера ... »отлично подойдет в моей книге. :) –

+4

Опция« вручную »становится более привлекательной в сочетании с [' -XRecordWildCards'] (http://www.haskell.org/ghc/docs/7.4 .2/html/users_guide/syntax-extns.html # record-wildcards): 'fromList [epoch, ecc, distPeri, включая longAscNode, argPeri, timePeri, meanMotion, meanAnomaly, trueAnomaly, semiMajorAxis, distApo, period] = Just OrbitElements {..} '. –

5

Вам не хватает того, что Haskell статически типизирован. Нет, у Haskell нет такой конструкции.

Предположим, что язык имел некоторый способ заполнить значения конструкторов из списка. Вот несколько вопросов, которые нужно подумать о:

  • Что произойдет, если в списке содержится больше или меньше предметов, чем требуется?
  • Как бы вы инициализировали запись, поля которой не были равномерно напечатаны?
2

Алгебраические типы данных должны быть инициализированы сразу, а не поле за раз, как вы это делаете. Правильный способ сделать это:

let d = ... 
let o = OrbitElements {epoch = d !! 0 
         ecc = d !! 1, 
         distPeri = d !! 2, 
         incl = d !! 3, 
         longAscNode = d !! 4, 
         argPeri = d !! 5, 
         timePeri = d !! 6, 
         meanMotion = d !! 7, 
         meanAnomaly = d !! 8, 
         trueAnomaly = d !! 9, 
         semiMajorAxis = d !! 10, 
         distApo = d !! 11, 
         period = d !! 12} 

Обратите внимание, что, как вы делаете это на самом деле не устанавливая никаких значений в o. let epoch o = d !! 0 определяет функцию с именем epoch, которая маскирует определение epoch как поле (вы всегда должны компилировать с включенными предупреждениями, чтобы компилятор поймал такие вещи), и эта новая функция принимает любое значение o (а не только ранее заданный OrbitElements) и возвращает d !! 0, ничего не делая с o. Если вы действительно хотели установить или изменить поле epocho, правильный способ сделать это будет let o' = o {epoch = d !! 0}, который возвращает новый объект OrbitElements с измененным полем epoch.

11

somwhat некрасиво решение ...: -o

Сделайте свой тип выведем Read:

data OrbitElements = OrbitElements { ... } 
         deriving (Read) 

Затем вы можете определить fromList по

fromList :: [Double] -> OrbitElements 
fromList ds = read $ "OrbitElement " ++ (concat $ Data.List.intersperse " " $ map show ds) 
+1

Это действительно умно! –

+2

Некоторые глупые ноты стиля: 'concat' с' intersperse' - это то же самое, что 'intercalate'. 'intercalate" "' совпадает с 'unwords'. Поэтому вы можете переписать вторую часть своего определения как 'unwords $ map show ds'. –

+1

Таким образом, 'fromList ds = read $" OrbitElement "++ (unwords $ map show ds)'. Возможно, нужно написать 'Data.List.unwords'. – md2perpe

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