Почему я не могу разместить здесь оператор функции печати?

Я пытаюсь использовать следующий код с блоком try-catch:

import System.Environment  
import System.IO  
import System.IO.Error  
import Control.Exception

isBinary :: String -> Bool
isBinary ss = do 
    print "In isBinary fn"   -- works if this line is removed.
    let ans = any (\c -> ord c > 127) ss
    ans

toTry :: String -> IO ()  
toTry firline = do
        print "In toTry fn."
        let answer = isBinary firline
        if not answer then do
            print "Sent line not binary: "
        else
            print "Sent line binary"

handler :: IOError -> IO ()  
handler e = putStrLn "Whoops, had some trouble!"  

ss = "this is a test"
main = do 
    toTry ss `catch` handler

Однако я получаю следующую ошибку:

$ runghc trycatch3.hs 

trycatch3.hs:9:9: error:
    • Couldn't match expected type ‘Bool’ with actual type ‘IO Bool’
    • In a stmt of a 'do' block: print "in isBinary fn"
      In the expression:
        do { print "in isBinary fn";
             let ans = any (\ c -> ...) ss;
             return ans }
      In an equation for ‘isBinary’:
          isBinary ss
            = do { print "in isBinary fn";
                   let ans = ...;
                   return ans }

trycatch3.hs:10:30: error:
    • Variable not in scope: ord :: Char -> Integer
    • Perhaps you meant one of these:
        ‘or’ (imported from Prelude), ‘odd’ (imported from Prelude)

Ошибка исчезает, и программа работает хорошо, если оператор печати удаляется из функции isBinary.

Почему я не могу поместить оператор печати в эту функцию?

Вы понимаете, что означает «чистый» в утверждении «Haskell — это чистый функциональный язык»?

Joseph Sible-Reinstate Monica 20.05.2019 14:26

Я читал/слышал об этом, но не понимаю этого полностью.

rnso 20.05.2019 14:27

Нотация do всегда дает вам какое-то значение IO (например, IO Bool в данном случае), так что это довольно просто.

hegel5000 20.05.2019 14:50

Короче говоря, в Haskell нет заявления.

Willem Van Onsem 20.05.2019 15:36

В чем недостаток простых утверждений? Почему их здесь так избегают?

rnso 20.05.2019 15:41

под «утверждением» в просторечии функционального программирования подразумевается что-то, что не имеет значения и используется для его действия. принято говорить, что «функциональные» — это «ориентированные на выражения» языки, где выражения — это оценивается для их значения. так, например, в Scheme вместо явного returnутверждение, как в C, просто значение последнего оцененного выражения возвращается как общее значение выражения.

Will Ness 20.05.2019 18:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
107
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вас может смутить одна и та же строка печати, работающая в toTry, но не в isBinary. Разница связана с декларацией:

isBinary :: String -> Bool

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

isBinary ss = any (\c -> ord c > 127) ss 

или даже использовать безточечный стиль

isBinary = any (\c -> ord c > 127)

Тем не менее, toTry

toTry :: String -> IO ()

т.е. он принимает строку и возвращает нечистую монаду IO (может иметь побочные эффекты, такие как вывод текста на консоль).

Haskell — это язык, который поощряет использование чистых функций и навязывает их с помощью системы типов, заставляя программиста явно помечать нечистый код.

Дальнейшее чтение: Что означает «чистый» в «чистом функциональном языке»?

Ответ принят как подходящий

Ответ: «потому что типы». Конкретно:

isBinary :: String -> Bool
isBinary ss = do 
  ....

Поскольку это блок do, возвращаемый тип isBinaryдолжен соответствует монадическому типу Monad m => m t для некоторых m и некоторых t. Здесь, поскольку print "" :: IO (), m это IO, так и должно было быть

isBinary :: String -> IO Bool
isBinary ss = do 

и сейчас

    print "In isBinary fn"                 -- works
    let ans = any (\c -> ord c > 127) ss   -- also works
    ans                                    -- doesn't work

ans опять же не работает из-за типов. Его тип Bool, но он должен быть IO Bool -- первым, потому что этот do блок принадлежит IO монаде из-за print; и во-вторых, из-за возвращаемого типа функции в целом.

Вместо этого используйте

    return ans

и теперь это будет работать, потому что return вводит значение в монадический контекст, и, будучи последним значением блока do, оно становится значением, созданным блоком do в целом (если return val появляется в середине, он просто передает val на следующий шаг в комбинированном расчете).

Функция toTry должна быть дополнена, чтобы использовать новое определение:

toTry :: String -> IO ()  
toTry firline = do
        print "In toTry fn."
        -- let answer = isBinary firline    -- incorrect, now!
        answer <- isBinary firline          -- isBinary ... :: IO Bool
        if not answer then do               --       answer ::    Bool
            print "Sent line not binary: "
        else
            print "Sent line binary"

m a справа от <-, a слева.

См. это для общего описания нотации do.

Отличный ответ, я бы также добавил краткое объяснение return в хаскеле по сравнению с его значением в других языках, так как это вызывает путаницу у многих учащихся.

dimid 21.05.2019 11:29

@m-aroosi спасибо за редактирование! Эту ошибку я часто замечал в чужих постах, а тут сделал сам. Ирония!

Will Ness 23.05.2019 09:55

Глядя на ваш код, кажется, что использование вами print в isBinary не является неотъемлемой частью того, что вы хотите, чтобы функция делала, а просто оператор отладочной печати, который будет удален позже. В этом случае вы не хотите менять тип isBinary на String -> IO Bool (подробнее об этом см. Ответ Уилла Несса), так как на самом деле вам не нужно IO, кроме как для отладки. Скорее, основные библиотеки предлагают модуль Debug.Trace, который подходит для таких ситуаций. С его помощью мы можем добавить оператор отладочной печати следующим образом:

isBinary :: String -> Bool
isBinary ss = trace "In isBinary fn" $ any (\c -> ord c > 127) ss

Затем, когда вы закончите отладку, вы можете отказаться от использования trace — и стоит повторить, что вам действительно следует сделать это позже. Цитирование документации Debug.Trace:

Functions for tracing and monitoring execution.

These can be useful for investigating bugs or performance problems. They should not be used in production code.

Другие вопросы по теме