Главная » Haskell » Экземпляры класса Logic

0

В разделе 3.1. был представлен класс Logic,  который  можно  использовать для описания методов работы с типами величин, представляющих собой логические значения.

В этом классе определены классические  методы из алгебры логики, а также несколько дополнительных, вроде импликации и эквивалентности (при желании, конечно, можно было добавить в определение этого класса и методы для вычисления таких операций, как «стрелка Пирса» или «штрих Шеффера»). Само собой разумеется, что все эти методы применимы к булевским значениям истинности, которые в языке Haskell представляются типом Bool. Для того чтобы можно было применять такие методы на значениях типа Bool, необходимо определить его экземпляром  класса Logic:

instance Logic  Bool  where neg  True   = False

neg False  = True

True  <&&>  True  = True

_       <&&>  _       = False

Здесь определяются только две главные операции — отрицание и конъюнкция. Понятно, что остальные операции выражаются через эти две. Для класса Logic, в свою очередь, определены выражения по умолчанию для остальных операций, кроме приведённых для типа Bool.  В этом заключается помощь разработчика класса тем программистам, кто создаёт экземпляры этого класса.

Теперь можно использовать любые методы класса Logic для определения собственных функций, работающих с логическими значениями (независимо от конкретного типа данных, при помощи которого представляются эти самые значения истинности). Такие функции называются прикладными. Например, раз в классе Logic  не определены операции «стрелка Пирса» и «штрих Шеффера», то их можно определить при помощи таких прикладных функций:

peirce :: Logic  a => a  ->  a ->  a peirce x  y  =  neg (x <||> y)

schaeffer :: Logic  a => a  ->  a ->  a schaeffer x  y  =  neg (x <&&>  y)

Как видно, в этих прикладных функциях нет никакого указания на то, какой тип данных они обрабатывают. Имеется просто указание, что тип таких данных обязательно должен быть экземпляром класса Logic, и это будет гарантией того, что для значений этого типа определены функции neg, (<&&>) и (<||>), а пото-

му результат выполнения этих функций можно будет получить и использовать в своём вычислительном  процессе. Поэтому такой механизм связывания классов и типов данных является достаточно гибким для того чтобы решать подобные задачи.

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

data  Ternary

= TFalse         -Ложь

| TUndefined  -Неопределённость

| TTrue           -Истина

Как  сделать так, чтобы разработанные выше функции peirce и  schaeffer работали со значениями этого нового типа данных? Разумеется, что эта задача решается при помощи определения экземпляра класса Logic:

instance Logic  Ternary  where neg TFalse          =  TTrue

neg TUndefined  =  TUndefined neg  TTrue           =  TFalse

TFalse  <&&>  _         = TFalse

_ <&&>  TFalse          =  TFalse TUndefined  <&&>  _ =  TUndefined

_ <&&>  TUndefined  = TUndefined

_ <&&>  _                  = TTrue

Это определение достаточно для того чтобы определённые  ранее  функции peirce и schaeffer работали со значениями типа Ternary. Более того, они будут возвращать на этих значениях абсолютно  правильный результат, который определяется результатом работы методов класса Logic. Здесь видна удивительная способность этой  технологии в языке  Haskell —  тип Ternary  и экземпляр класса Logic  для него могут быть определены в совершенно стороннем модуле, который никак не связан с первоначальным, где определены класс и прикладные функции к нему. Однако такие функции будут вполне успешно работать с новыми типами данных, о которых первоначальный разработчик класса и функций мог даже и не предполагать.

Наконец,  ещё один пример может показать, что новые классы могут использоваться и для организации нового понимания для уже имеющихся типов данных. Например, в математике имеются расширения классической двузначной логики в бесконечнозначные.  Одним из самых известных таких расширений является нечёткая логика, значения истинности которой лежат в интервале [0, 1]. Другим расширением является логика антиномий, значения истинности которой лежат

немного в другом интервале: [??, ?]. Для представления таких  значений ис-

тинности можно воспользоваться типом Float (к примеру). Что сделать, чтобы с этим типом данных работали методы класса Logic и его прикладные функции? Всё тоже самое — необходимо  определить экземпляр:

instance Logic  Float where neg    =  (0  -)

<&&>  = min

<||> = max

Теперь действительные числа можно использовать в качестве  значений истинности в бесконечнозначной логике антиномий.

Источник: Душкин Р. В., Справочник по языку Haskell. М.: ДМК Пресс, 2008. 544 с., ил.

По теме:

  • Комментарии