Я действительно запутался в объявлениях функций в Haskell - PullRequest
7 голосов
/ 30 апреля 2020

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

allEqual :: Eq a => a -> a -> a -> Bool

Что я понимаю из этого, так это то, что я должен сравнить 3 значения (в данном случае a, a, a?) И вернуть ли или не все они равны друг другу. Вот что я попробовал:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

Честно говоря, я чувствую себя полностью потерянным с Haskell, поэтому любые советы о том, как читать функции или объявлять их, очень помогли бы.

Ответы [ 5 ]

7 голосов
/ 30 апреля 2020

На этот вопрос уже есть несколько других очень хороших ответов, объясняющих, как решить вашу проблему. Я не хочу этого делать; вместо этого я буду go просматривать каждую строку вашего кода, постепенно исправлять проблемы и, надеюсь, помочь вам лучше понять Haskell.

Сначала я скопирую ваш код для удобства:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

Первая строка - это подпись типа; это уже хорошо объяснено в других ответах, поэтому я пропущу это и go перейду к следующей строке.

Во второй строке вы определяете свою функцию. Первое, что вы пропустили, это то, что вам нужен знак равенства для определения функции: синтаксис определения функции - functionName arg1 arg2 arg3 … = functionBody, и вы не можете удалить =. Итак, давайте исправим это:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

Следующая ошибка использует нотацию do. do нотация печально известна тем, что вводит в заблуждение новичков, поэтому не стесняйтесь использовать ее неправильно. В Haskell нотация do используется только в определенных c ситуациях, когда необходимо выполнять последовательность операторов построчно, особенно если у вас есть побочный эффект (например, печать на консоли ) который выполняется с каждой строкой. Понятно, что это здесь не подходит - все, что вы делаете, это сравниваете некоторые значения и возвращаете результат, который вряд ли требует построчного выполнения. Итак, давайте избавимся от этого do:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let Bool check = x == y
      Bool nextC = y == z
  in
    if check == nextC
      then True
      else False

(я также заменил привязку <- на let … in …, поскольку <- можно использовать только внутри блока do. )

Далее еще одна проблема: Bool check недействительно Haskell! Возможно, вы знакомы с этим синтаксисом из других языков, но в Haskell тип всегда указывается с использованием :: и часто с сигнатурой типа. Поэтому я удалю Bool перед именами и добавлю вместо них сигнатуры типов:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let check :: Bool
      check = x == y
      nextC :: Bool
      nextC = y == z
  in
    if check == nextC
      then True
      else False

Теперь, на данный момент, ваша программа совершенно корректна Haskell - вы сможете скомпилировать ее, и это будет работать. Но есть еще несколько улучшений, которые вы можете сделать.

Для начала вам не нужно включать типы - Haskell имеет вывод типов, и в большинстве случаев нормально не включать типы (хотя это традиционно включить их для функций). Итак, давайте избавимся от типов в let:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let check = x == y
      nextC = y == z
  in
    if check == nextC
      then True
      else False

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

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  if (x == y) == (y == z)
    then True
    else False

Наконец, я вижу, что у вас есть выражение вида if <condition> then True else False. Это избыточно - вы можете просто вернуть <condition> с тем же значением. Итак, давайте сделаем это:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = (x == y) == (y == z)

Это намного, намного лучше, чем код, с которого вы начали!

(На самом деле есть еще одно улучшение, которое вы можете внести в этот код. Точка, должно быть очевидно, что в вашем коде есть ошибка. Можете ли вы найти ее? И если да, то можете ли вы ее исправить? Подсказка: вы можете использовать оператор && для 'и' двух логических значений вместе.

3 голосов
/ 30 апреля 2020

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

allEqual :: Int -> Int -> Int -> Bool

Вы можете посмотреть на это так: последняя «вещь» после «->» является типом возврата. Превью "вещи" являются параметрами. Исходя из этого, мы знаем, что функция принимает три параметра Int и возвращает Bool.

Теперь взглянем на вашу функцию.

allEqual :: Eq a => a -> a -> a -> Bool

Существует дополнительный синтаксис "Eq a =>". То, что он в основном делает, так это все следующие "a" в объявлении должны реализовывать Eq. Так что это более обобщенная версия первой функции. Он принимает три параметра, которые реализуют «Eq», и возвращает Bool. Вероятно, функция должна проверить, равны ли все значения.

Теперь давайте посмотрим на вашу реализацию. Вы используете синтаксис do. Я чувствую, что это не лучший подход в начале. Давайте реализуем очень похожую функцию, которая проверяет, все ли параметры равны 3.

allEq3 :: Int -> Int -> Int -> Bool
allEq3 x y z = isEq3 x && isEq3 y && isEq3 z
  where
    isEq3 :: Int -> Bool
    isEq3 x = x == 3

Как и в вашем примере, у нас есть три параметра, и мы возвращаем Bool. В первой строке мы вызываем функцию isEq3 по всем параметрам. Если все эти вызовы вернут true, allEq3 также вернет true. В противном случае функция вернет false. Обратите внимание, что функция isEq3 определена ниже после ключевого слова «где». Это очень распространенная вещь в Haskell.

Итак, мы взяли большую проблему, чтобы проверить, все ли параметры равны 3, и разбили ее на более мелкие части, которые проверяют, равно ли значение 3. 3. 1028 *

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

3 голосов
/ 30 апреля 2020

Давайте начнем с функции, которая принимает один Int:

allEqual1Int :: Int -> Bool
allEqual1Int x = True

allEqual1Int' :: Int -> Bool
allEqual1Int' x = 
  if x == x
  then True
  else False

Если мы сравним это с вашей строкой

allEqual x y z do

, мы заметим, что вы пропустили = и что вам не нужна do.

версия для String может выглядеть как

allEqual1String' :: String -> Bool
allEqual1String' x = 
  if x == x
  then True
  else False

, и мы наблюдаем, что работает та же самая реализация для нескольких типов (Int и String), если они поддерживают ==.

Теперь a - это переменная типа, считайте ее переменной, так что ее значение является типом. И требование о том, что данный тип поддерживает ==, закодировано в ограничении Eq a (Думайте об этом как об интерфейсе). Поэтому

allEqual :: Eq a => a -> a -> a -> Bool

работает для любого такого типа, который поддерживает ==.

2 голосов
/ 30 апреля 2020
allEqual :: Eq a => a -> a -> a -> Bool

Подпись гласит: allEqual потребляет 3 значения типа a; он выдает результат типа Bool. Часть Eq a => ограничивает возможные операции a; он говорит, что независимо от типа a он должен удовлетворять требованиям, определенным в Eq. Вы можете найти эти требования здесь: http://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#t: Eq Теперь вы знаете, что могут делать операции a, затем вы можете выполнить свою функцию, следуя сигнатуре типа.

1 голос
/ 30 апреля 2020

Вот как вы это делаете:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = x == y && y == z

Что это значит?

Первая строка определяет тип сигнатуры функции .

В человеческих словах это будет означать что-то вроде:

Для любого типа a существует функция с именем allEqual. Для него требуется экземпляр Eq a*, он принимает три параметра, все типа a, и возвращает Bool

Во второй строке написано:

Функция allEqual для любых параметров x, y и z должна оценивать x == y && y == z, которая просто сравнивает, что x равно y и y равно z.

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

...