Как использовать MultiPiece

Я совершенно новичок в Yesod (и не очень опытен в haskell), и я пытаюсь создать свой первый обработчик. Я создал свое приложение, используя параметры по умолчанию (я использую версию Yesod 0.9.4.1 и выбираю postgresql при создании), и теперь я пытаюсь получить некоторые данные из таблицы с помощью selectList. Я определил новую таблицу (назовем ее Foo) в файле конфигурации моделей:

    Foo
        xStart Int
        yStart Int

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

/foos/#Int/#Int/*FooId FoosReturnR GET

и обработчик:

    module Handler.FoosReturn where

    import Import

    selectWindowSize :: Int 
    selectWindowSize = 10000

    getFoosReturnR :: Int -> Int -> [FooId] -> Handler RepPlain
    getFoosReturnR x y withoutIds = do
        foos <- runDB $ selectList [FooId /<-. withoutIds, 
               FooXStart <. x + selectWindowSize,
               FooXStart >=. x - selectWindowSize, 
               FooYStart <. y + selectWindowSize,
               FooYStart >=. y - selectWindowSize] [] 
        return $ RepPlain $ toContent $ show foos

Я импортировал обработчик в Application.hs и добавил его в файл Cabal, и теперь, когда я пытаюсь его запустить, я получаю сообщение об ошибке, говорящее, что FooId не является экземпляром MultiPiece, но когда я пытаюсь сделать его экземпляром, возникает ошибка о том, что FooId является синонимом типа и не может быть экземпляром MultiPiece - как решить эту проблему?


РЕДАКТИРОВАТЬ: Даниэль: ну, на самом деле я не знаю, что такое FooId - это часть магии Yesod, которую я до сих пор не совсем понимаю - она ​​генерируется автоматически из определения таблицы - но это какое-то число.

Поскольку я не знаю, как использовать MultiPiece, я переключился на более простое решение и изменил:

маршрут: /foos/#Int/#Int/#String FoosReturnR GET

обработчик: [добавлено также ведение журнала]

    module Handler.FoosReturn where

    import Import
    import Data.List.Split
    import qualified Data.Text.Lazy as TL

    selectWindowSize :: Int 
    selectWindowSize = 10000

    getFoosReturnR :: Int -> Int -> String -> Handler RepPlain
    getFoosReturnR x y withoutIds = do
        app <- getYesod
        liftIO $ logLazyText (getLogger app) ("getFoosReturnR('" `TL.append` (TL.pack $ (show x) ++ "', '" ++ (show y) ++ "', '" ++ withoutIds ++ "') "))
        foos <- runDB $ selectList [FooId /<-. (map (\a -> read a :: FooId) $ splitOn "," withoutIds), 
               FooXStart <. x + selectWindowSize,
               FooXStart >=. x - selectWindowSize, 
               FooYStart <. y + selectWindowSize,
               FooYStart >=. y - selectWindowSize] [] 
        return $ RepPlain $ toContent $ show foos

и теперь он компилируется, но когда я перехожу к: http://localhost:3000/sectors/1/1/1,2 Я получаю страницу, содержащую только: Internal Server Error Prelude.read: no parse

Ну, я не совсем понимаю, что такое FooId - как создать такой список FooId из списка строк, содержащих числа?

И, конечно же, наиболее востребовано решение, как сделать FooId экземпляром MultiPiece.


РЕДАКТИРОВАТЬ:

Даниэль и svachalek, спасибо за ваши сообщения - я попробовал ваше (Даниэля) решение, но затем я получил ошибки, говорящие о том, что ожидается [FooId] (как в объявлении функции обработчика), но был задан тип FooId, и это привело меня к следующему решению :

    data FooIds = FooIds [FooId] deriving (Show, Read, Eq)

    instance MultiPiece FooIds where
        toMultiPiece (FooIds fooList) = map (Data.Text.pack . show) fooList
        fromMultiPiece texts = 
            if length (filter isNothing listOfMaybeFooId) > 0
                then Nothing
                else Just $ FooIds $ map fromJust listOfMaybeFooId
            where 
                listOfMaybeFooId = map constructMaybeFooId texts
                constructMaybeFooId :: Text -> Maybe FooId
                constructMaybeFooId x = case reads (Data.Text.unpack x) :: [(FooId,String)] of
                        [(foo,_)] -> Just foo
                        _         -> Nothing

конечно я изменил маршрут на: /foos/#Int/#Int/*FooIds FoosReturnR GET

и обработчик:

    getFoosReturnR :: Int -> Int -> FooIds -> Handler RepPlain
    getFoosReturnR coordX coordY (FooIds withoutIds) = do

и теперь я не получаю никаких ошибок ни во время компиляции, ни во время выполнения, и единственная неудовлетворительная вещь заключается в том, что в результате я всегда получаю Not Found, даже если я задаю параметры, которые должны дать мне какие-то результаты - так что теперь мне нужно выяснить как определить какой именно SQL был отправлен в базу данных


РЕДАКТИРОВАТЬ:

Теперь я вижу, что «Не найдено» связано с проблемой и что указанное выше редактирование не является решением — когда я перехожу к localhost:3000/foos/4930000/3360000, я получаю результаты (но тогда FooIds пуст) - но когда я добавляю что-то вроде: localhost: 3000/sectors/4930000/3360000/1, я всегда получаю "Не найдено" - так что это все еще не работает.


person godfryd    schedule 02.02.2012    source источник
comment
Вы можете писать экземпляры для синонимов типов с языковым расширением TypeSynonymInstances (также может потребоваться FlexibleInstances). Что такое FooId синоним?   -  person Daniel Fischer    schedule 02.02.2012
comment
Даниэль: Я ответил в редакции. Не могли бы вы опубликовать код, как сделать FooId экземпляром MultiPiece?   -  person godfryd    schedule 02.02.2012


Ответы (3)


Хотел бы я помочь, но yesod как-то связан с веб-приложениями, насколько я знаю, поэтому я никогда не смотрел на него. Так что я могу просто попробовать нанести удар в воздухе, может, я во что-нибудь попал.

Хайу ведет к

class MultiPiece s where
    fromMultiPiece :: [Text] -> Maybe s
    toMultiPiece :: s -> [Text]

в Yesod.Dispatch. Поскольку у FooId, похоже, есть экземпляр Read и, возможно, экземпляр Show, вы можете попробовать

{-# LANGUAGE TypeSynonymInstances #-}
-- maybe also FlexibleInstances

instance MultiPiece FooId where
    toMultiPiece foo = [Text.pack $ show foo]
    fromMultiPiece texts =
        case reads (unpack $ Text.concat texts) :: [(FooId,String)] of
          [(foo,_)] -> Just foo
          _         -> Nothing

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

person Daniel Fischer    schedule 02.02.2012

Проблема решена:)

Вы можете либо использовать мою реализацию из одного из последних правок вопроса, либо перейти к URL-адресу, например: http://localhost:3000/foos/4930000/3360000/Key {unKey = PersistInt64 3}/Key {unKey = PersistInt64 4} Тип ключа выводится из Read, но не очень дружелюбным (и ожидаемым) способом:)

Или измените реализацию fromMultiPiece на:

    instance MultiPiece FooIds where
        toMultiPiece (FooIds fooList) = map (Data.Text.pack . show) fooList
        fromMultiPiece texts = 
            if length (filter isNothing listOfMaybeFooId) > 0
                then Nothing
                else Just $ FooIds $ map fromJust listOfMaybeFooId
            where 
                listOfMaybeFooId = map constructMaybeFooId texts
                constructMaybeFooId :: Text -> Maybe FooId
                constructMaybeFooId x = case TR.decimal x of 
                        Left err -> Nothing 
                        Right (el,_) -> Just $ Key (PersistInt64 el)

и используйте такие URL-адреса, как: http://localhost:3000/foos/4930000/3360000/1/2

Большое спасибо Дэвиду МакБрайду из группы Google Yesod Web Framework.


РЕДАКТИРОВАТЬ: приведенное выше решение имело только один недостаток - использование типа PersistInt64 - не рекомендуется использовать такие детали реализации, но его можно исправить, используя функции fromPersistValue и toPersistValue из Database.Persist следующим образом:

    instance MultiPiece FooIds where
        toMultiPiece (FooIds fooList) = map (persistValuetoText . unKey) fooList
            where
                persistValuetoText x = case fromPersistValue x of
                    Left _ -> Data.Text.pack "" 
                    Right val -> Data.Text.pack $ show (val::Int) 
        fromMultiPiece texts = 
            if length (filter isNothing listOfMaybeFooId) > 0
                then Nothing
                else Just $ FooIds $ map fromJust listOfMaybeFooId
            where 
                listOfMaybeFooId = map constructMaybeFooId texts
                constructMaybeFooId :: Text -> Maybe FooId
                constructMaybeFooId x = case TR.decimal x of 
                        Left _ -> Nothing 
                        Right (el,_) -> Just $ Key (toPersistValue (el :: Int))

Еще раз большое спасибо Дэвиду МакБрайду и за это!

person godfryd    schedule 03.02.2012

Я также новичок в Yesod, и я сдался и добавил -XTypeSynonymInstances к параметрам ghc в моем файле .cabal, и до сих пор это значительно облегчало мне жизнь. Я не уверен, что это самый элегантный ответ на эту конкретную проблему, но в противном случае я предсказываю, что вы будете довольно часто сталкиваться с ошибкой экземпляра псевдонима. P.S. попробуйте id = (Ключ (PersistInt 64 n))

person svachalek    schedule 02.02.2012