Некоторые возможности и трудности использования линз в MonadState

Далее следует серия примеров/упражнений по линзам (автор Эдвард Кметт) в MonadState, основанных на решении Петра Пудлака на мой предыдущий вопрос.

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

{-# LANGUAGE TemplateHaskell, RankNTypes #-}

import Control.Lens
import Control.Monad.State

---------- Example by Petr Pudlak   ----------
-- | An example of a universal function that modifies any lens.
-- It reads a string and appends it to the existing value.
modif :: Lens' a String -> StateT a IO ()
modif l = do
    s <- lift getLine
    l %= (++ s)

-----------------------------------------------

Следующие сигнатуры типа комментария созданы GHCi. Другие - адаптации из Питера. Лично я изо всех сил пытаюсь понять, чем производятся GHCi, и мне интересно: почему GHCi не производит упрощенные?

-------------------------------------------

-- modif2
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (Int -> p a b) -> Setting p s s a b -> t IO ()
modif2 :: (Int -> Int -> Int) -> Lens' a Int -> StateT a IO ()     
modif2 f l = do
    s<- lift getLine
    l %= f (read s :: Int)

---------------------------------------

-- modif3
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (String -> p a b) -> Setting p s s a b -> t IO ()
modif3 :: (String -> Int -> Int) -> Lens' a Int -> StateT a IO ()     
modif3 f l = do
    s <- lift getLine
    l %= f s
-- :t modif3 (\n -> (+) (read n :: Int)) == Lens' a Int -> StateT a IO ()

---------------------------------------

-- modif4 
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (t1 -> p a b) -> (String -> t1) -> Setting p s s a b -> t IO ()
modif4 :: (Bool -> Bool -> Bool) -> (String -> Bool) -> Lens' a Bool -> StateT a IO ()
modif4 f f2 l = do
    s <- lift getLine
    l %= f (f2 s)
-- :t modif4 (&&) (\s -> read s :: Bool) == Lens' a Bool -> StateT a IO ()

---------------------------------------
-- modif5
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (t1 -> p a b) -> (String -> t1) -> Setting p s s a b -> t IO ()
modif5 :: (b -> b -> b) -> (String -> b) -> Lens' a b -> StateT a IO ()
modif5 f f2 l = do
    s<- lift getLine
    l %= f (f2 s)
-- :t modif5 (&&) (\s -> read s :: Bool) == Lens' a Bool -> StateT a IO ()

---------------------------------------

-- modif6
  -- :: (Profunctor p, MonadState s m) =>
     -- (t -> p a b) -> (t1 -> t) -> t1 -> Setting p s s a b -> m ()
modif6 :: (b -> b -> b) -> (c -> b) -> c -> Lens' a b -> StateT a IO ()
modif6 f f2 x l = do
    l %= f (f2 x)
-- :t modif6 (&&) (\s -> read s :: Bool) "True" ==  MonadState s m => Setting (->) s s Bool Bool -> m ()
-- :t modif6 (&&) (\s -> read s :: Bool) "True" 

---------------------------------------

-- modif7
  -- :: (Profunctor p, MonadState s IO) =>
     -- (t -> p a b) -> (String -> t) -> Setting p s s a b -> IO ()
modif7 :: (b -> b -> b) -> (String -> b) -> Lens' a b -> StateT a IO ()
modif7 f f2 l = do
    s <- lift getLine
    l %= f (f2 s)
-- :t modif7 (&&) (\s -> read s :: Bool) == 
-- :t modif7 (+) (\s -> read s :: Int) == 

---------------------------------------

p7a :: StateT Int IO ()
p7a = do
  get
  modif7 (+) (\s -> read s :: Int) id

test7a = execStateT p7a 10  -- if input 30 then result 40

---------------------------------------

p7b :: StateT Bool IO ()
p7b = do
  get
  modif7 (||) (\s -> read s :: Bool) id

test7b = execStateT p7b False  -- if input "True" then result "True"

---------------------------------------

data Test = Test { _first :: Int
                 , _second :: Bool
                 }
    deriving Show

$(makeLenses ''Test)

dataTest :: Test
dataTest = Test  { _first = 1, _second = False }

monadTest :: StateT Test IO String
monadTest = do
  get
  lift . putStrLn $ "1) modify \"first\" (Int requested)"
  lift . putStrLn $ "2) modify \"second\" (Bool requested)"
  answ <- lift getLine
  case answ of
    "1" -> do lift . putStr $ "> Write an Int: "
              modif7 (+) (\s -> read s :: Int) first
    "2" -> do lift . putStr $ "> Write a Bool: "
              modif7 (||) (\s -> read s :: Bool) second
    _   -> error "Wrong choice!"
  return answ

testMonadTest :: IO Test  
testMonadTest = execStateT monadTest dataTest

person Alberto Capitani    schedule 02.05.2014    source источник
comment
Не могли бы вы задать более конкретный вопрос, например, привести пример только одной проблемной функции вместо огромного блока кода? Попробуйте объяснить, что именно вам в нем не нравится и что вы бы предпочли увидеть.   -  person bheklilr    schedule 02.05.2014
comment
Я думаю, что если вы просто включите один пример типа, с которым у вас есть проблемы, этого должно быть достаточно. Функции все очень похожи   -  person bennofs    schedule 02.05.2014
comment
Основная проблема — сигнатура типа, необходимая для компиляции кода. Я не могу написать подпись типа, как GHCi, и если я часто начинаю код с нуля (без линз), GHCi помогает мне понять подпись типа моего кода.   -  person Alberto Capitani    schedule 02.05.2014


Ответы (2)


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

почему GHCI не производит эти упрощенные?

Вместо этого он определяет более общие типы. Например, вы упоминаете, что GHC вычисляет следующий тип кода:

modif2 :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
  (Int -> p a b) -> Setting p s s a b -> t IO ()

Это очень общий тип, потому что каждый раз, когда я использую modif2, я могу выбирать разные профункторы p, преобразователи монад t и состояния s. Так что modif2 можно использовать повторно. Вы предпочитаете этот тип подписи:

modif2 :: (Int -> Int -> Int) -> Lens' a Int -> StateT a IO ()     

Я согласен, что это более читабельно, но и менее обобщенно: здесь вы решили, что p должно быть ->, а t должно быть StateT, и как пользователь modif2 я не мог это изменить.

Есть надежда, что в будущем дела поправятся?

Я уверен, что Haskell продолжит указывать наиболее общие типы в результате вывода типов. Я мог представить, что в дополнение к самому общему типу, ghci или сторонний инструмент может показать вам примеры экземпляров. В таком случае было бы неплохо как-то объявить, что -> является типичным профунктором. О каких-то работах в этом направлении мне не известно, так что особой надежды нет, т.к.

person Toxaris    schedule 02.05.2014

Давайте посмотрим на ваш первый пример:

modif :: Lens' a String -> StateT a IO ()
modif l = do
  s <- lift getLine
  l %= (++ s)

Этот тип прост, но у него также есть недостаток: вы можете использовать свою функцию, только передавая Lens. Вы не можете использовать свою функцию, когда у вас есть Iso вместо Traversal, даже хотя это имело бы смысл! Учитывая более общий тип, который выводит GHCi, вы могли бы, например, написать следующее:

modif _Just :: StateT (Maybe String) IO ()

который добавит прочитанное значение только в том случае, если это состояние было Just или

modif traverse :: StateT [String] IO ()

который добавит прочитанное значение ко всем элементам в списке. Это невозможно с приведенным вами простым типом, потому что _Just и traverse это не линзы, а только Traversals.

person bennofs    schedule 02.05.2014