Някои потенциални и трудности при използването на лещи в 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
Бихте ли задали по-конкретен въпрос, като например да дадете пример само за 1 проблемна функция вместо огромен блок код? Опитайте се да обясните какво точно не ви харесва в него и какво предпочитате да видите.   -  person bheklilr    schedule 02.05.2014
comment
Мисля, че ако включите само един пример за тип, с който имате проблеми, това трябва да е достатъчно. Всички функции са доста сходни   -  person bennofs    schedule 02.05.2014
comment
Основният проблем е сигнатурата на типа, необходима за компилиране на кода. Не мога да напиша подпис на типа като GHCi и ако стартирам кода от нулата често (без лещи), GHCi ми помага да разбера подписа на типа на моя код.   -  person Alberto Capitani    schedule 02.05.2014


Отговори (2)


Като семейство в традицията на ML, 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