#haskell
#haskell
Вопрос:
Мой код не компилируется:
{-# LANGUAGE EmptyDataDecls, GADTs, RankNTypes #-}
import Data.Ratio
data Ellipsoid
data Halfplane
data PointSet a where
Halfplane :: RealFrac a => a -> a -> a -> (a -> a -> Bool) -> a -> PointSet Halfplane
Ellipsoid :: RealFrac a => a -> a -> a -> (a -> a -> Bool) -> a -> PointSet Ellipsoid
type TestFunc = RealFrac a => (a -> a -> a -> Bool)
ellipsoid :: PointSet Ellipsoid -> TestFunc
ellipsoid (Ellipsoid a b c f r) = f' where f' z y x = ((x/a)^2 (y/b)^2 (z/c)^2) `f` r
halfplane :: PointSet Halfplane -> TestFunc
halfplane (Halfplane a b c f t) = f' where f' z y x = (a*x b*y c*z) `f` t
Ошибка, которую я получаю, такова:
Could not deduce (a1 ~ a)
[...]
Expected type: a -> a -> a -> Bool
Actual type: a1 -> a1 -> a1 -> Bool
In the expression: f'
[...]
для обеих функций ellipsoid
и halfplane
.
Я не понимаю и ищу ответ, почему a
нельзя приравнять к a1
, как быть RealFrac
, или даже лучше: почему выводятся два разных типа ( a ~ a1
) ?
Ответ №1:
Ваше использование GADT
with implicit forall
вызывает у вас горе. Самое время упомянуть анти-шаблон «экзистенциальная количественная оценка с помощью класса типов»
Поскольку вы включили ограничение RealFrac a
в определение PointSet
, вы неявно используете forall
подобное:
data PointSet a where
Halfplane :: forall a. RealFrac a => a -> a -> a -> (a -> a -> Bool) -> a -> PointSet HalfPlane
Ellipsoid :: forall a. RealFrac a => ...
То же самое относится к TestFunc
:
type TestFunc = forall a. RealFrac a => a -> a -> a -> Bool
Вот почему GHC принудил вас к добавлению RankNTypes
расширения.
Из forall
-за a
in конструкторы for PointSet
не могут быть объединены с a
in TestFunc
, поскольку a
in PointSet
является некоторым конкретным экземпляром RealFrac
, но TestFunc
является функцией, которая требуется для работы для any a
.
Это различие между конкретным типом a
и универсальным количественным forall a. a
показателем приводит к тому , что GHC выводит два разных типа a
и a1
.
Решение? Отбросьте всю эту экзистенциальную чушь. Применяйте ограничения класса типов там, где и когда они необходимы:
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
data Shape = Halfplane | Ellipsoid -- promoted via DataKinds
data PointSet (s :: Shape) a where
Halfplane :: a -> a -> a -> (a -> a -> Bool) -> a -> PointSet Halfplane a
Ellipsoid :: ...
type TestFunc a = a -> a -> a -> Bool
ellipsoid :: RealFrac a => PointSet Ellipsoid a -> TestFunc a
ellipsoid (Ellipsoid a b c f r) = f' where f' = ...
Теперь PointSet
принимает 2 параметра: фантомный тип s :: Shape
, который имеет вид Shape
(виды — это типы типов) и a
который совпадает с вашим исходным примером, за исключением того, что явный аргумент для PointSet
него больше неявно определяется количественно.
Чтобы ответить на ваш последний вопрос, a
и a1
в сообщении об ошибке не являются «оба существа RealFrac
. RealFrac
это не тип, это класс типов. a
и a1
это два потенциально различных типа , которые оба являются экземплярами RealFrac
.
Тем не менее, если вы не используете более выразительный тип PointSet
, существует гораздо более простое решение.
data PointSet a
= Halfplane a a a (a -> a -> Bool) a
| Ellipsoid a a a (a -> a -> Bool) a
testFunc :: RealFrac a => PointSet a -> TestFunc a
testFunc (Ellipsoid a b c f r) = f' where f' = ...
testFunc (Halfplane a b c f t) = f' where f' = ...
Комментарии:
1. Спасибо за подробный ответ, мне все еще не хватает полного понимания. a) Вы пишете: «из-за forall a в конструкторах для PointSet невозможно объединить с a в TestFunc, поэтому выводятся два разных типа a и a1». — Я не разбираюсь в системе F, и это моя вина, но я не понимаю, как эти два они различны и не могут объединиться. Они кажутся мне совершенно равными. б) В предложенном вами решении вы не только уничтожили ограничение, но и ввели переменную типа
s
. Не сбил ли я с толку интерпретатора, установив свойa
неявно через-> PointSet Halfplane
?2. @ASz
a
s не могут быть объединены именно потому, что они универсально определены количественно. Функцииforall a . a -> a
и(forall a . a) -> (forall a . a)
очень разные (первое естьid
, второе естьunsafeCoerce
). Другими словами,a
внутренняя частьHalfPlane
— это нечто конкретноеa
, но какое именно, неизвестно — все, что вы знаете о нем, — это его экземплярRealFrac
. НоTestFunc
требует, чтобы функция была любого типаa
, которого у вас нет. Не уверен, что вы имеете в виду в б).3. CDK, если вы добавите информацию из комментария @user2407038, который отвечает на некоторые из моих непонимающих ответов на ваш ответ, я с радостью приму это как ответ.
4. @ASz: Я обратился к вашим комментариям в своем отредактированном ответе.
5. @cdk: извините за позднее принятие… Я просто делаю «генеральную уборку» в своих учетных записях stackexchange и заметил, что пока не принял ответ.