Как вы применяете ограничения функций в методах экземпляра в Haskell?

Я учусь использовать классы типов в Haskell.

Рассмотрим следующую реализацию класса типов T с функцией класса с ограничениями типа f.

class T t where
    f :: (Eq u) => t -> u 

data T_Impl = T_Impl_Bool Bool | T_Impl_Int Int | T_Impl_Float Float 
instance T T_Impl where
    f (T_Impl_Bool x) = x
    f (T_Impl_Int x) = x
    f (T_Impl_Float x) = x

Когда я загружаю это в GHCI 7.10.2, я получаю следующую ошибку:

Couldn't match expected type ‘u’ with actual type ‘Float’
      ‘u’ is a rigid type variable bound by
          the type signature for f :: Eq u => T_Impl -> u 
          at generics.hs:6:5

Relevant bindings include
  f :: T_Impl -> u (bound at generics.hs:6:5)
In the expression: x
In an equation for ‘f’: f (T_Impl_Float x) = x

Что я делаю / понимаю неправильно? Мне кажется разумным, что кто-то захочет специализировать класс типов в экземпляре, предоставив сопутствующий конструктор данных и реализацию функции. Часть

Не удалось сопоставить ожидаемый тип "u" с фактическим типом "Float"

особенно сбивает с толку. Почему u не соответствует Float, если u имеет только ограничение, которое должно квалифицироваться как Eq тип (Floats делают это afaik)?


person FK82    schedule 02.06.2016    source источник
comment
ограничение не является вашей проблемой - проблема в том, что вы говорите: независимо от того, какой Eq тип вы хотите от меня в результате - я могу предоставить его   -  person Random Dev    schedule 02.06.2016
comment
Где я это говорю?   -  person FK82    schedule 02.06.2016
comment
в f :: Eq u => t -> u - t известно из контекста - там u с неявным forall   -  person Random Dev    schedule 02.06.2016
comment
@Carsten Можете ли вы связать меня со ссылкой на вашу претензию. Насколько я понимаю, ограничение типа ограничивает только потенциальные определения.   -  person FK82    schedule 02.06.2016
comment
дело не в ограничении - дело в u - это действительно то, что Чи указал в своем ответе и комментариях   -  person Random Dev    schedule 02.06.2016


Ответы (3)


Подпись

f :: (Eq u) => t -> u 

означает, что вызывающий может выбрать t и u по своему усмотрению, с единственной обязанностью гарантировать, что u относится к классу Eqt к классу T - в методах класса есть неявное T t ограничение).

Это не значит, что реализация может выбрать любую u.

Итак, вызывающий может использовать f любым из следующих способов: (с t в классе T)

f :: t -> Bool 
f :: t -> Char
f :: t -> Int
... 

Компилятор жалуется, что ваша реализация недостаточно универсальна, чтобы охватить все эти случаи.

Couldn't match expected type ‘u’ with actual type ‘Float’ 

означает «Вы дали мне Float, но вы должны предоставить значение общего типа u (где u будет выбран вызывающим)»

person chi    schedule 02.06.2016
comment
Этот. В 1000 раз больше. В типичном объектно-ориентированном языке метод может возвращать все, что он хочет, при условии, что это соответствует требованиям. В Haskell вызывающий может выбирать, а не метод. - person MathematicalOrchid; 02.06.2016
comment
Это также относится к обычным функциям, которые не являются методами класса типов; если вы утверждаете, что можете вернуть какой-то тип, реализующий Foo, вы действительно должны иметь возможность возвращать любой тип, реализующий Foo! - person MathematicalOrchid; 02.06.2016
comment
@Mat MathematicalOrchid, Чи: Это сбивает с толку. Есть идеи, как предоставить определение экземпляра для конкретного типа данных? - person FK82; 02.06.2016
comment
@ FK82 Тип возвращаемого значения должен быть определен во время компиляции. Нет (полезного) способа вернуть другой тип в зависимости от того, какой ввод вы даете ему во время выполнения. Возможно, если бы мы знали, что вы хотите от этой функции для ... но это, возможно, новый вопрос. - person MathematicalOrchid; 02.06.2016
comment
@ FK82 С этим классом невозможно работать, он обещает невозможное, поэтому его невозможно реализовать. Вам нужно пересмотреть свой подход. Ваш вопрос выглядит как проблема XY. meta.stackexchange.com/questions/66377/what -is-the-xy-проблема - person chi; 02.06.2016
comment
@chi Переведите это в ООП. Например. в Java interface T<U extends Comparable> { U f(); } public class T_Impl_Boolean implements T<Boolean> { private Boolean b; public Boolean f() {return b;}} работает нормально. Я не понимаю, почему было бы невозможно реализовать функцию класса с ограниченным типом для подмножества ограничивающего класса ... - person FK82; 02.06.2016
comment
@ FK82 Это даже не проблема с классом типов. Вы пытаетесь написать функцию, которая иногда возвращает Bool, а иногда возвращает Float, но в зависимости от того, какие данные вы передаете ей во время выполнения. Вы не можете этого сделать; точный тип возвращаемого значения должен быть известен во время компиляции. - person MathematicalOrchid; 02.06.2016
comment
Ваш пример Java будет похож на f :: Eq u => u -> Bool, где тип input, а не тип возврата, имеет экземпляр Eq. - person chepner; 02.06.2016
comment
@chepner Нет, это не так. С одной стороны, подпись f в T дает понять, что параметризуется тип возврата (U), а не тип ввода (на самом деле его нет, потому что, но это будет преобразовано во ввод T экземпляр). Также T не реализует Comparable. - person FK82; 02.06.2016
comment
@ FK82 ООП-переводы обязательно неточны, но это примерно похоже на interface T { <U extends Comparable> U f(); }. Это обещает быть способным произвести любой U по выбору вызывающего абонента. - person chi; 02.06.2016
comment
@chi Да, ты прав. Кстати, с включенной прагмой MultiParamTypeClasses что-то вроде class Eq u => T u t where ... (примерно эквивалентно моему переводу на Java) фактически передает компилятору. Но проблема просто отложена на время выполнения. Я думаю, что я начинаю понимать проблему немного лучше: ограничение класса типов для переменной открытого типа не может применяться, если тип данных либо не указан явно, либо может быть безопасно выведен. Вот почему мой код нежизнеспособен в Haskell. - person FK82; 02.06.2016
comment
не совсем (но это тоже проблема) - ваш код не работает, потому что ваша функция не имеет уникального типа - вы определили другой тип возвращаемого значения для каждого конструктора (см. этот ответ) - вы также не могли сделать это в java ( ну, вы могли бы использовать объект для всех) - person Random Dev; 02.06.2016
comment
@ FK82 Чего вы в конечном итоге пытаетесь достичь? Зная это, мы можем помочь вам больше. - person David Young; 02.06.2016
comment
@DavidYoung Я реализую немонотонную логическую систему на Haskell в рамках своей диссертации по информатике. В типичной манере ООП - что, конечно, не обязательно слишком хорошо транслируется в Haskell - вы обычно абстрагируете общие свойства в интерфейс, например. interface Formula { Formula toCNF(); /*...*/} (toCNF преобразуется в нормальную форму предложения) - а затем реализует этот интерфейс для формул каждой логической системы. В основном это то, что я пытался сделать. - person FK82; 02.06.2016
comment
@Mat MathematicalOrchid, data Blah a where {Bint :: Blah Int ; Boat :: Blah Float}; hi :: Blah a -> a; hi Bint = 1 :: Int; hi Boat = 3.7. Я полагаю, что это полезно - это другой вопрос. - person dfeuer; 03.06.2016
comment
@ FK82 На ваш настоящий вопрос (почему это не работает?), Кажется, есть ответ. Возможно, вам стоит спросить, как мне сделать XYZ? как новый вопрос. Если вы включите некоторые подробности о том, что вам нужно, возможно, мы сможем указать вам правильное направление. - person MathematicalOrchid; 03.06.2016
comment
@Mat MathematicalOrchid Нет, все в порядке. Я понимаю, что сейчас происходит. - person FK82; 03.06.2016

Чи уже указал, почему ваш код не компилируется. Но проблема даже не в том, что классы типов; действительно, в вашем примере есть только один экземпляр, так что это может быть обычная функция, а не класс.

По сути, проблема в том, что вы пытаетесь сделать что-то вроде

foobar :: Show x => Either Int Bool -> x
foobar (Left  x) = x
foobar (Right x) = x

Это не сработает. Он пытается заставить foobar возвращать другой тип в зависимости от значения, которое вы ему вводите во время выполнения. Но в Haskell все типы должны быть на 100% определены во время компиляции. Так что это не может работать.

Однако есть несколько вещей, которые вы можете сделать.

Прежде всего, вы можете сделать следующее:

foo :: Either Int Bool -> String
foo (Left  x) = show x
foo (Right x) = show x

Другими словами, вместо того, чтобы возвращать что-то показываемое, фактически покажите это. Это означает, что тип результата всегда String. Это означает, что вызываемая версия show будет меняться во время выполнения, но это нормально. Пути кода могут изменяться во время выполнения, а типы - нет.

Еще вы можете сделать следующее:

toInt :: Either Int Bool -> Maybe Int
toInt (Left  x) = Just x
toInt (Right x) = Nothing

toBool :: Either Int Bool -> Maybe Bool
toBool (Left  x) = Nothing
toBool (Right x) = Just x

Опять же, это отлично работает.

Есть и другие вещи, которые вы можете делать; не зная, почему вы этого хотите, трудно предложить другим.

В качестве примечания, вы не должны думать об этом, как об объектно-ориентированном программировании. Это не так. Это требует нового мышления. В частности, не используйте класс типов, если он вам действительно не нужен. (Я понимаю, что этот конкретный пример может быть просто учебным упражнением, чтобы узнать о классах типов, конечно ...)

person MathematicalOrchid    schedule 02.06.2016
comment
Думаю, теперь я понимаю проблему: объявление типа функции класса должно либо указывать определенный тип данных (это самый простой случай); или, если тип может быть параметризован - и, следовательно, ограничен классом типа - компилятор должен иметь возможность безопасно вывести его. Все остальное компиляцию не пройдет. - person FK82; 02.06.2016

Это можно сделать:

class Eq u => T t u | t -> u where
    f :: t -> u

Вам нужны FlexibleContextx + FunctionalDepencencies и MultiParamTypeClasses + FlexibleInstances на сайте вызова. Или исключить класс и использовать вместо него типы данных, как показано здесь, здесь

person RandomB    schedule 16.08.2018