2014-10-18 3 views
4

Существует очень распространенная проблема, с которой очень легко попасть в haskell. Code-фрагмент, который описывает его так:Типовые поля с именами в haskell ADTs

data JobDescription = JobOne { n :: Int } 
        | JobTwo 
        | JobThree { n :: Int } 
    deriving (Show, Eq) 

taskOneWorker :: JobDescription -> IO() 
taskOneWorker t = do 
    putStrLn $ "n: " ++ (show $ n t) 

main :: IO() 
main = do 
    -- this runs ok: 
    taskOneWorker (JobOne 10) 

    -- this fails at runtime: 
    -- taskOneWorker JobTwo 

    -- this works, but we didn't want it to: 
    -- taskOneWorker (JobThree 10) 

Я описал эту проблему, и это возможные решения в этой статье: https://www.fpcomplete.com/user/k_bx/playing-with-datakinds

Здесь, на StackOverflow, я хотел бы спросить вас - каковы наилучшие решения этой проблемы, используя какие инструменты и какую документацию?

+2

IMO, единственная проблема заключается в том, что Haskell позволяет объявлять полевые аксесуары вообще в многопользовательском ADT. Я считаю, что злоупотребление синтаксисом записей, просто не делайте этого, и проблем нет. – leftaroundabout

+0

@leftaroundabout конечно. Проблема с моим производственным кодом, который у меня есть, заключается в том, что полевые аксессуры уже существуют и используются повсеместно. Поэтому я хочу продолжить использование полевых аксессуаров (чтобы не делать сумасшедшего рефакторинга), но все равно получить безопасность по типу. –

+1

Просто определите новую функцию 'nOne :: JobDescription -> Int', которая работает только для' JobOne'. –

ответ

-1

да; используя линзы, подобные вам, и абстрагирование «n» в какой-то «JobData» кажется мне не слишком плохим (возможно, даже хорошим).

+0

Ну, это позволяет вам избежать сбоев во время выполнения с минимальными усилиями (без необходимости в DataKinds), но все же было бы неплохо ограничить параметры с помощью типов, когда это имеет смысл. –

1

Предлагаю использовать Prism. Prism_JobOne представляет значение Int, которое находится внутри конструктора JobOne. В taskOneWorker мы просматриваем его с помощью ^?. Либо значение t действительно является конструктором JobOne, и его аргумент возвращается в Just, или это не конструктор JobOne, и мы возвращаем Nothing.

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 

data JobDescription = JobOne { n :: Int } 
        | JobTwo 
        | JobThree { n :: Int } 
    deriving (Show, Eq) 
$(makePrisms ''JobDescription) 

taskOneWorker :: JobDescription -> IO() 
taskOneWorker t = do 
    case t ^? _JobOne of 
    Just n -> putStrLn $ "n: " ++ show n 
    Nothing -> putStrLn "Not a JobOne" 
       -- Or 'return()' if you want to ignore these cases 

main :: IO() 
main = do 
    taskOneWorker (JobOne 10) 
    taskOneWorker JobTwo 
    taskOneWorker (JobThree 10) 

-- *Main> main 
-- n: 10 
-- Not a JobOne 
-- Not a JobOne 
+1

Это кажется мне плохим вариантом, в случае, если taskOneWorker явно считается работающим именно с JobOne (а не с JobTwo или JobThree), это приводит к тому, что taskOneWorker имеет больше шаблонов и меньше безопасности по типу, чем решение из ссылки в исходном сообщении. –