xmonad `tags ‹- спрашивает (workspaces .config)` магия, как мне это разобрать?

Тыкаю модуль XMonad.Actions.WindowMenu из xmonad-contrib, пытаюсь сделать его настраиваемым.

И мне трудно понять следующий бит:

В исходный код представляет собой конструкцию типа:

windowMenu :: X ()
windowMenu = withFocused $ \w -> do
    tags <- asks (workspaces . config)
    -- ...

Как лучше понять, что там происходит? Другими словами, в каком контексте оценивается все tags <- asks (workspaces . config)?

Причина, по которой я спрашиваю, заключается в том, что когда я пытаюсь реорганизовать это в другой функции:

defaultActs :: [(String, X ())]
defaultActs = do
    tags <- asks (workspaces . config)
    [ ("A: " ++ tag, return ()) | tag <- tags]

вылетает с ошибкой:

    • No instance for (MonadReader XConf [])
        arising from a use of ‘asks’
    • In a stmt of a 'do' block: tags <- asks (workspaces . 
      In the expression:
        do tags <- asks (workspaces . config)
           [("A: " ++ tag, return ()) | tag <- tags]
      In an equation for ‘defaultActs’:
          defaultActs
            = do tags <- asks (workspaces . config)
                 [("A: " ++ tag, return ()) | tag <- tags]

Отредактировано, чтобы добавить:

Я понимаю типы этого утверждения:

ghci> :t asks (workspaces . config)
asks (workspaces . config) :: MonadReader XConf m => m [String]
ghci> :t withFocused
withFocused :: (Window -> X ()) -> X ()

но причина, по которой он ломается, (до сих пор) остается загадкой.


person Wejn    schedule 02.04.2020    source источник


Ответы (4)


Проще всего просто заставить оба windowMenu и defaultActions работать в монаде X, у которой уже есть соответствующий экземпляр MonadReader. Так:

defaultActions :: X [(String, X ())]
defaultActions = do
    tags <- asks (workspaces . config)
    return ([ ("Cancel menu", return ())
            , ("Close"      , kill)
            , ("Maximize"   , withFocused $ \w -> sendMessage $ maximizeRestore w)
            , ("Minimize"   , withFocused $ \w -> minimizeWindow w)
            ] ++
            [ ("Move to " ++ tag, windows $ W.shift tag) | tag <- tags ])

windowMenu :: X ()
windowMenu = withFocused $ \w -> do
    acts <- defaultActions
    Rectangle x y wh ht <- getSize w
    Rectangle sx sy swh sht <- gets $ screenRect . W.screenDetail . W.current . windowset
    let originFractX = (fi x - fi sx + fi wh / 2) / fi swh
        originFractY = (fi y - fi sy + fi ht / 2) / fi sht
        gsConfig = (buildDefaultGSConfig colorizer)
                    { gs_originFractX = originFractX
                    , gs_originFractY = originFractY }
    runSelectedAction gsConfig acts

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

windowMenu' :: [(String, X ())] -> X ()
windowMenu' acts = withFocused $ \w -> do
    Rectangle x y wh ht <- getSize w
    Rectangle sx sy swh sht <- gets $ screenRect . W.screenDetail . W.current . windowset
    let originFractX = (fi x - fi sx + fi wh / 2) / fi swh
        originFractY = (fi y - fi sy + fi ht / 2) / fi sht
        gsConfig = (buildDefaultGSConfig colorizer)
                    { gs_originFractX = originFractX
                    , gs_originFractY = originFractY }
    runSelectedAction gsConfig acts

В таком мире вы можете запустить defaultActions и передать его результат в windowMenu' с помощью (>>=) (или более do нотации):

windowMenu :: X ()
windowMenu = defaultActions >>= windowMenu'
-- OR
windowMenu = do
    acts <- defaultActions
    windowMenu' acts
person Daniel Wagner    schedule 03.04.2020

Цитирую комментарий:

Да, windowMenu имеет тип X (), а мой defaultActs относится к другому типу. Чего я не понимаю, так это того, как запуск лямбды через withFocused предоставляет ей доступ к некоторому дополнительному контексту.

Вы можете использовать asks для получения полей конфигурации таким образом, поскольку существует экземпляр MonadReader XConf X для монада X. MonadReader обычно используется для предоставления такого доступа к конфигурации.


Причина, по которой я спрашиваю, заключается в том, что когда я пытаюсь реорганизовать это в другой функции:

defaultActs :: [(String, X ())]
    defaultActs = do
    tags <- asks (workspaces . config)
    [ ("A: " ++ tag, return ()) | tag <- tags]

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

defaultActs :: X [String]
defaultActs = do
    tags <- asks (workspaces . config)
    return [ "A: " ++ tag | tag <- tags]

То есть получить теги из конфигурации, создать модифицированный список и вернуть его в контексте монады X.

person duplode    schedule 02.04.2020
comment
Спасибо, это был толчок в правильном направлении: stackoverflow.com/a/61013887/1177128. - person Wejn; 03.04.2020

Хорошо, благодаря ответу duplode я смог понять это:

defaultActions :: XConf -> [(String, X ())]
defaultActions = do
    tags <- asks (workspaces . config)
    return ([ ("Cancel menu", return ())
            , ("Close"      , kill)
            , ("Maximize"   , withFocused $ \w -> sendMessage $ maximizeRestore w)
            , ("Minimize"   , withFocused $ \w -> minimizeWindow w)
            ] ++
            [ ("Move to " ++ tag, windows $ W.shift tag) | tag <- tags ])

windowMenu' :: (XConf -> [(String, X ())]) -> X ()
windowMenu' actions = withFocused $ \w -> do
    acts <- asks actions
    Rectangle x y wh ht <- getSize w
    Rectangle sx sy swh sht <- gets $ screenRect . W.screenDetail . W.current . windowset
    let originFractX = (fi x - fi sx + fi wh / 2) / fi swh
        originFractY = (fi y - fi sy + fi ht / 2) / fi sht
        gsConfig = (buildDefaultGSConfig colorizer)
                    { gs_originFractX = originFractX
                    , gs_originFractY = originFractY }
    runSelectedAction gsConfig acts

-- now it composes well, and I can pass in my own `actions` to `windowMenu`
windowMenu = windowMenu' defaultActions

Кстати, я не мог заставить его работать как тип X [String] (не знаю, почему), но решение выше, кажется, работает достаточно хорошо. Может быть, он и не самый лучший (не уверен в этом), но он ведет меня туда, куда я хочу.

person Wejn    schedule 03.04.2020
comment
(1) Вы не можете заставить его работать как X [String], потому что в вашем случае использования вам действительно нужно возвращать эти X () действия вместе с тегами, так что это нормально. (2) В сторону: вы можете написать defaultActions так, с asks и блоком выполнения, потому что функции также являются экземплярами MonadReader. В то время как asks в блоке X (как в windowMenu) использует XConf, предоставленный X-окружением, в вашем defaultActions XConf на самом деле является аргументом функции, который вы передадите функции где-то ниже по строке; в любом случае вы можете использовать тот же интерфейс для доступа к XConf. - person duplode; 03.04.2020
comment
@duplode Re (1): да, я имею в виду, я попробовал все X ([(String, X ())]) и не смог заставить это работать. :-) Re (2): ок, теперь понятно. Меня все еще немного сбивает с толку тот факт, что я, по сути, неявно передаю XConf в качестве параметра, и кажется, что asks (workspaces . config) был привязан к этому. Но я имею в виду, что не все сразу должно иметь смысл, я полагаю. :-) И я даже не передаю это defaultActions неявно. Потому что, если вы посмотрите на 1-ю строку windowMenu', asks actions достаточно, чтобы волшебным образом передать правильный материал. Я действительно имею в виду это: магия. - person Wejn; 03.04.2020
comment
Магия заключается в том, что asks actions берет XConf из среды X и передает его actions. Это работает, потому что actions в конечном счете является просто функцией (иначе вы не смогли бы передать ее asks). - person duplode; 03.04.2020
comment
Если вам не нравится XConf в вашей подписи, вы можете просто вместо этого взять [String] в качестве входных данных, представляющих список рабочих областей, и изменить windowMenu на windowMenu = windowMenu' (workspaces . config $ defaultActions) - person amalloy; 05.04.2020

Не заглядывая ни в какую документацию, мы многое знаем: это в контексте do-нотации для любого типа, необходимого withFocused. По-видимому, он принимает функцию в качестве аргумента, и эта функция должна возвращать монадическое значение некоторого типа. Значение этого блока do-notation должно иметь тот же тип. Он определенно не имеет тип [(String, X ())]. (Ну, ладно, может, так как [a] является монадой, но маловероятно, что это именно тот тип, который withFocused ожидает в качестве результата).

Вы можете узнать, какой у него тип, просмотрев документация:

withFocused :: (Window -> X ()) -> X ()

Поскольку наша do-нотация находится внутри возвращаемого значения лямбды для первого параметра, она должна иметь тип X (). Поэтому,

asks (workspaces . config) :: X t

для некоторых t, которые мы также могли бы узнать, посмотрев тип workspaces. <- связывает это значение t с именем tags обычным способом, который делает do-нотация.

person amalloy    schedule 02.04.2020
comment
Да, windowMenu имеет тип X (), а мой defaultActs относится к другому типу. Чего я не понимаю, так это того, как запуск лямбды через withFocused предоставляет ей доступ к некоторому дополнительному контексту. Может быть, мне просто нужно немного больше прочитать о хаскеле в целом, чтобы не потеряться так безнадежно. - person Wejn; 03.04.2020
comment
@Wejn Запуск через withFocused не дает доступа. Наоборот, именно тот факт, что windowMenu работает в монаде X, предоставляет ей доступ. (withFocused просто поддерживает этот доступ — он не предоставляет его заново.) - person Daniel Wagner; 03.04.2020
comment
@DanielWagner хм, вот что меня немного сбивает с толку: почему наличие блока do заставляет код внутри каким-то образом привязываться к чему-то внешнему (если это даже то, что вы подразумеваете под работой в монаде X). Во всяком случае, я заставил это работать: stackoverflow.com/a/61013887/1177128, даже если это, вероятно, не лучший способ. Спасибо за добавление кусочка головоломки! - person Wejn; 03.04.2020
comment
@Wejn Блок do не привязывает его к чему-то внешнему. Подпись типа/выведенный тип делает. - person Daniel Wagner; 03.04.2020
comment
@DanielWagner, где я изо всех сил пытаюсь представить, как блоки do связываются сигнатурой типа. В stackoverflow.com/a/61013887/1177128 у меня есть подпись defaultActions :: XConf -> [(String, X ())], и если я вызываю ее в windowMenu' как acts <- asks actions, это волшебным образом связывается правильным образом. Почему это работает несколько ускользает от меня. Я предполагаю, что мне не хватает какой-то общей концепции картины. - person Wejn; 03.04.2020
comment
@Wejn Неудивительно, что код в этом ответе сбивает с толку. Он написан немного безумно, с использованием многих уровней косвенности; даже опытному ветерану Haskell пришлось бы потратить немного времени на тщательное обдумывание, чтобы точно понять, что происходит. Вероятно, не стоит пытаться понять - есть гораздо более простой код, который достигает того же, и вместо этого вы должны попытаться понять это. - person Daniel Wagner; 04.04.2020
comment
@DanielWagner Мне нравится твой ответ. Кстати, вы предполагаете, что когда я рефакторил код в чокнутую форму, я глубоко разбирался в монадах и нотации do. Ну, я этого не сделал. Но я провел некоторое время с en.wikibooks.org/wiki/Haskell (и документами XMonad) и теперь я понимаю это несколько лучше. В любом случае, спасибо за ответ с правильной версией. Я был почти уверен, что ошибся: mail.haskell.org/ pipermail/xmonad/2020-April/015367.html :-) - person Wejn; 04.04.2020