Новичок в системе типов Haskell: «Аргумент переменной нетипа Haskell в ограничении» Ошибка

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

Выполнение следующего кода дает правильный вывод (например, генерирует координаты для окружности, обернутой вокруг цилиндра радиуса R под углом тета):

coilGeneration_AngleTest housingRadius coilWidth coilDepth numEle zoffset centralAngle
     = [ (x',y',z)
       | theta <- [0,2*pi/(numEle-1)..2*pi]
       , let x = housingRadius * cos(coilWidth*cos(theta)/housingRadius)
       , let y = housingRadius * sin(coilWidth*cos(theta)/housingRadius)
       , let z = coilDepth * sin(theta)+zoffset
       , let x' = x * cos(centralAngle) - y * sin(centralAngle)
       , let y' = x * sin(centralAngle) + y * cos(centralAngle)
       ]

Пример вывода функции coilGeneration_AngleTest

Однако, пытаясь обобщить это в функцию, которая генерирует произвольный массив NxM кругов с различными перекрытиями в полярных и z-направлениях, запустив:

coilArrayGeneration_Test r nE width depth n m mu gam
     = [ (x',y',z',i,j)
       | theta <- [0,2*pi/(nE-1)..2*pi]
       , i <- [1..n]
       , j <- [1..m]
       , let a = width/2
       , let b = depth/2
       , let x = r * cos(a*cos(theta)/r)
       , let y = r * sin(a*cos(theta)/r)
       , let z = b * sin(theta)
       , let phi = (2*i-1-n)((a-mu)/r)
       , let zo = (2*j-1-m)(b-gam)
       , let x' = x * cos(phi) - y * sin(phi)
       , let y' = x * sin(phi) + y * cos(phi)
       , let z' = z + zo
       ]

дает следующую ошибку:

Build profile: -w ghc-9.2.5 -O1
In order, the following will be built (use -v for more details):
 - Haskell-0.1.0.0 (exe:Haskell) (file app/Main.hs changed)
Preprocessing executable 'Haskell' for Haskell-0.1.0.0..
Building executable 'Haskell' for Haskell-0.1.0.0..
[1 of 1] Compiling Main             ( app/Main.hs, /Users/zack/Desktop/Udemy/Haskell/dist-newstyle/build/aarch64-osx/ghc-9.2.5/Haskell-0.1.0.0/x/Haskell/build/Haskell/Haskell-tmp/Main.o )

app/Main.hs:66:1: error:
    • Non type-variable argument in the constraint: Num (c -> c)
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        coilArrayGeneration_Test :: forall {c}.
                                    (Floating c, Num (c -> c), Enum c, Enum (c -> c)) =>
                                    c
                                    -> c
                                    -> c
                                    -> c
                                    -> (c -> c)
                                    -> (c -> c)
                                    -> c
                                    -> c
                                    -> [(c, c, c, c -> c, c -> c)]
   |
66 | coilArrayGeneration_Test r nE width depth n m mu gam = [(x',y',z',i,j)|theta <- [0,2*pi/(nE-1)..2*pi],....

Выход из строя

После некоторого поиска в Google мне показалось, что моя функция имеет неправильный тип, подразумеваемый компилятором, но я, к сожалению, недостаточно хорошо понимаю идею определения типа Haskell, чтобы исправить ее. Я попытался определить типы так, как я их вижу, а именно:

  • г -> Двойной

  • nE -> Целое

  • ширина -> двойной

  • глубина -> двойной

  • п -> Целое

  • м -> Целое

  • мю -> Двойной

  • игра -> Двойной

  • х ' -> Двойной

  • у' -> Двойной

  • г' -> Двойной

  • Я -> Целое

  • j -> Целое

Получающий:

coilArrayGeneration_Test :: (Floating a, Integral b) => a -> b -> a -> a -> b -> b -> a -> a -> [(a,a,a,b,b)]
coilArrayGeneration_Test r nE width depth n m mu gam
      = [ (x',y',z',i,j)
        | theta <- [0,2*pi/(nE-1)..2*pi]
        , i <- [1..n]
        , j <- [1..m]
        , let a = width/2
        , let b = depth/2
        , let x = r * cos(a*cos(theta)/r)
        , let y = r * sin(a*cos(theta)/r)
        , let z = b * sin(theta)
        , let phi = (2*i-1-n)((a-mu)/r)
        , let zo = (2*j-1-m)(b-gam)
        , let x' = x * cos(phi) - y * sin(phi)
        , let y' = x * sin(phi) + y * cos(phi)
        , let z' = z + zo
        ]

Но это вызвало целый ряд ошибок:

Ошибки после объявления типа

Что явно означает, что я не знаю, что делаю, и как-то испортил объявления типов.

Может ли кто-нибудь направить меня на правильный путь?

При изучении Haskell я бы рекомендовал следующее: 1) при написании определения (функции) всегда начинайте с написания ее предполагаемого типа, 2) в ваших первых упражнениях не используйте более общие типы, чем вам действительно нужно. Вероятно, сначала можно использовать f :: Int -> Int, даже если его можно обобщить до f :: Num a => a -> a.

chi 10.02.2023 19:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
1
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Когда вы видите ошибку компилятора, связанную с чем-то вроде Num (c -> c), она никогда не имеет ничего общего с -XFlexibleContexts или с неправильными выводами типов. Это просто означает, что вы пытались использовать что-то в качестве функции, которая не является функцией.

«Использовать как функцию» означает просто, что у вас есть некоторое выражение в форме f x, где f и x могут быть произвольными подвыражениями. Это включает, в частности, такие выражения, как (1+2)(3+4), что то же самое, что и

     let f = 1 + 2
         x = 3 + 4
     in f x

Предположительно, вы хотели выразить умножение сопоставлением. Тогда используйте оператор умножения! т.е. (1+2)*(3+4).

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

coilArrayGeneration_Test r nE width depth n m μ γ
      = [ (x',y',z',i,j)
        | ϑ <- [0, 2*pi/fromIntegral(nE-1) .. 2*pi]
        , i <- [1..n]
        , j <- [1..m]
        , let a = width/2
              b = depth/2
              x = r * cos(a*cos ϑ/r)
              y = r * sin(a*cos ϑ/r)
              z = b * sin ϑ
              φ = fromIntegral(2*i-1-n) * ((a-μ)/r)
              z₀ = fromIntegral(2*j-1-m) * (b-γ)
              x' = x * cos φ - y * sin φ
              y' = x * sin φ + y * cos φ
              z' = z + z₀
        ]

Я настоятельно рекомендую вам немного реорганизовать это, как код, так и типы. 5-кортежи очень неясны, вы должны по крайней мере обернуть x,y,z в подходящий тип вектора.

Теперь, когда я это вижу, в этом столько смысла. Таким образом, преобразование в правильные типы с помощью функций fromX было большой вещью, которую мне не хватало. Я ценю помощь!!

ztth222 10.02.2023 17:59

@ ztth222 Многие языки автоматически преобразовывают числовые типы, например суммирование целого числа и двойного приводит к тому, что целое число повышается до двойного. Вместо этого Haskell никогда этого не делает, а fromIntegral и тому подобное должны явно использоваться в коде. В целом, я нахожу Haskell немного менее удобным для числового кода, но часто гораздо более безопасным: печально известный пример 1/2*x эквивалентен нулю в C, но вместо этого эквивалентен 0.5*x в Haskell.

chi 10.02.2023 18:53

@chi, если честно, этот пример в основном показывает, насколько C небезопасен, и немного странно, что в этом отношении ему последовало так много языков. В Python 3 этой проблемы нет: его оператор / всегда дробный, как в Haskell, но он по-прежнему допускает целые аргументы. На практике это работает вполне удовлетворительно. Что раздражает в Haskell, так это то, что fromInteger такая громоздкая штука. from — это альтернатива, которую я бы очень хотел, чтобы она стала широко адаптированной.

leftaroundabout 10.02.2023 19:28

... хотя похоже, что он намеренно преобразует только Int32 в Double, для Int требуется tryFrom. Что на самом деле тоже имеет смысл, но также влечет за собой собственные неудобства.

leftaroundabout 10.02.2023 19:34

@leftaroundabout ИМО, от Int до Double должно просто работать без необходимости прыгать дальше. Я согласен с тем, что fromIntegral слишком длинный, но, честно говоря, мне никогда не приходилось использовать его слишком часто (возможно, потому, что я редко имею дело с данными с плавающей запятой). from может быть лучшим вариантом. В любом случае, всегда можно иметь неэкспортируемое определение ярлыка frI = fromIntegral в модулях, которые требуют частых преобразований.

chi 10.02.2023 20:37

@leftaroundabout Я только что понял, что, вероятно, from и tryFrom предназначены только для точных преобразований без потерь. Если это цель, это имеет смысл, но я бы предпочел использовать другой класс для преобразования в числа с плавающей запятой. Запрещение преобразования небольшого числа, такого как 16777216, в число с плавающей запятой, потому что оно «слишком велико», как это делает tryFloat, когда тип float поддерживает до 2^128 (хотя и с потерями), является слишком радикальным для многих приложений.

chi 10.02.2023 20:53

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