Понимание сигнатуры типа данных, класса типов и превращение типа данных в экземпляр класса типов - PullRequest
0 голосов
/ 11 февраля 2019

Читал Learn You A Haskell для большого блага!и имеют большие проблемы с пониманием экземпляра и вида.

Q1: То есть тип t в Tofu t действует как функция с доброй сигнатурой (* -> (* -> *)) -> *?И общая добрая подпись tofu равна * -> *, не так ли?так как (* -> *) -> * приводит к *, то же самое происходит и (* -> (* -> *)) -> *

Q2: Когда мы хотим создать Frank a b экземпляр класса типов Tofu t, тип данных Frank a b также должен иметь такой же видс t.Это означает, что a равно *, b равно * -> * и b a, что будет (* -> *) -> *, что приведет к *.Это правильно?

Q3: x в tofu x представляет j a, поскольку оба имеют вид *.Frank с его видом (* -> (* -> *)) -> * применяется к x.Но я не уверен, как представление j a как x будет отличать x в tofu x, что составляет j a, и x в Frank x, что составляет a j.

Я новичок в идее иметь функцию внутри типа данных или класса (например: b в Frank a b или t в Tofu t), что немного сбивает с толку

Iоставьте ссылку здесь, так как цитирование заставит пост выглядеть излишне длинным. ссылка

class Tofu t where
  tofu :: j a -> t a j

data Frank a b = Frank {frankField :: b a} 

instance Tofu Frank where
  tofu x = Frank x 

Ответы [ 2 ]

0 голосов
/ 11 февраля 2019

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

class Tofu t where
  tofu :: j a -> t a j
          ^^^    ^^^^^
          ^^^^^^^^^^^^

Подсвеченные биты должны иметь вид *.Все, что находится по обе стороны от стрелки (уровня типа), должно иметь тип * [1] , а сам термин стрелки (то есть весь термин j a -> t a j) также имеет вид *.Действительно, любой «тип» [2] , который может быть заселен значением, имеет вид *.Если он имеет какой-либо другой вид, его значений не может быть (он просто используется для конструирования правильных типов в другом месте).

Таким образом, в сигнатуре tofu выполняется следующее

j a :: *
t a j :: *

потому что они используются в качестве "обитаемых" типов, поскольку они являются аргументами (->).

И это единственные вещи, ограничивающие класс.В частности, a может быть любого вида.С PolyKinds [3]

a :: k   -- for any kind k
j :: k -> *
t :: k     ->   (k -> *) -> *
     ^          ^^^^^^^^    ^
 kind of a      kind of j   required since is used as inhabited type by ->

Итак, мы нашли требуемый вид t.

Мы можем использовать аналогичные рассуждения для Frank.

data Frank a b = Frank {frankField :: b a}
     ^^^^^^^^^                        ^^^

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

a :: k
b :: k -> *
Frank a b :: *

И, таким образом,

Frank :: k -> (k -> *) -> *

Мы можем видеть, что вид Frank соответствует требуемому виду для Tofu.Но это также имеет смысл для более конкретного вида, например:

data KatyPerry a b = KatyPerry a (b Int)

Попробуйте вывести ее вид и убедитесь, что он более конкретен, чем тип, требуемый для Tofu.


[1] Это верно даже для стрелок на добром уровне, если мы примем TypeInType.Без TypeInType «виды» называются сортами , и никто не беспокоится о них;на этом уровне обычно ничего интересного не происходит.

[2] Я помещаю «тип» в кавычки, потому что технически только вещи с видом * называются типами, а все остальное называется конструктором типа .Я пытался быть точным в этом, но я не мог найти неуклюжий способ ссылаться на оба сразу, и параграф стал очень грязным.Таким образом, «type» это.

[3] Без PolyKinds, все с неограниченным видом, таким как k, специализируется на *.Это также означает, что тип Tofu может зависеть от того, к какому типу вы впервые применили его экземпляр, или от того, создан ли экземпляр этого типа в том же модуле или в другом модуле.Это плохо.PolyKinds это хорошо.

0 голосов
/ 11 февраля 2019

Q1:

Таким образом, тип t в Tofu t действует как функция с доброй сигнатурой (* -> (* -> *)) -> *?

t имеет вид * -> (* -> *) -> *, или, более точно, * -> ((* -> *) -> *), а не (* -> (* -> *)) -> *.

И общая разновидность подписи тофу - * -> *, не так ли?

tofu не имеет доброй подписи, только конструкторы типов;тип его типа *.Как и его аргумент и тип результата.И то же самое для любой функции.

Q2: Вы начинаете с неверного предположения: instance Tofu Frank делает конструктор типа Frank экземпляром Tofu, а не Frank a b.Так что это Frank, который должен иметь тот же вид, что и t, а не Frank a b (который имеет вид *).

ba, который будет (* -> *) ->*

Нет, b a - это приложение от b вида * -> * до a вида *, поэтому приложение имеет вид *.Точно так же, как если бы b был функцией типа x -> y, а a был значением типа x, b a имел бы тип y, а не (x -> y) -> x: просто замените x и y по *.

Q3:

Символ x в тофу x обозначает ja

«Имеет тип», а не «представляет».

, поскольку оба типа *

x не имеют вида, потому что это не тип.

Фрэнк с его видом (* -> (* -> *)) -> * применяется к x

Нет, в

tofu x = Frank x

это Frank data constructor , который применяется к x, а не к конструктору типов.Это функция с подписью b a1 -> Frank a1 b (переименование a, поэтому вы не должны путать ее с tofu).Так b ~ j и a1 ~ a.

...