Подтвердить что ты не робот

Контрастность генераторов С# с параметрами типа Haskell

Основываясь на некоторых советах, которые я нашел в StackOverflow, я копаю в Haskell. Я был рад видеть, что параметры Haskell с параметрами очень похожи на генераторы С#. Оба языка сообщают одну букву для параметра типа (обычно), и оба языка, похоже, следуют аналогичному процессу для замены фактического типа для параметра типа. Из-за этого я довольно быстро нарисовал концепцию.

Что приводит к этому: каковы некоторые способы, с помощью которых параметризуемые типы Haskell отличаются от общих типов С#? Я знаю, изучая Ruby, что вы можете столкнуться с большими проблемами, думая, что концепция, с которой вы знакомы с одного языка, одинакова на другом языке, для которого вы новичок. Обычно проблема хуже, когда функции на самом деле очень похожи... потому что они обычно не на 100% одинаковы. Итак, каковы некоторые из "gotchas", которые я могу укусить, если предположить, что я понимаю параметризованные типы, основанные на моем знании общих возможностей С#?

Спасибо.

4b9b3361

Ответ 1

Здесь одно отличие:

У С# есть подтипирование, но Haskell этого не делает, что означает, во-первых, что вы знаете больше вещей, просто глядя на тип Haskell.

id :: a -> a

Эта функция Haskell принимает значение типа и возвращает то же значение этого же типа. Если вы дадите ему Bool, он вернет a Bool. Дайте ему Int, он вернет Int. Дайте ему Person, он вернет a Person.

В С# вы не можете быть так уверены. Это то, что "функция" в С#:

public T Id<T>(T x);

Теперь из-за подтипирования вы можете называть его так:

var pers = Id<Person>(new Student());

Пока pers имеет тип Person, аргумент функции Id отсутствует. Фактически pers может иметь более специфический тип, чем просто Person. Person может даже быть абстрактным типом, гарантируя, что pers будет иметь более специфический тип.

Как вы можете видеть, даже с такой простой функцией, как Id, система типа .NET уже допускает гораздо больше, чем более строгая система типов из Haskell. Хотя это может быть полезно для выполнения некоторых задач программирования, это также затрудняет рассуждение о программе, просто просматривая типы вещей (что очень приятно делать в Haskell).


И, во-вторых, в Haskell существует специальный полиморфизм (например, перегрузка) с помощью механизма, известного как "классы типов".

equals :: Eq a => a -> a -> Bool

Эта функция проверяет, равны ли два значения. Но не только любые два значения, просто значения, имеющие экземпляры для класса Eq. Это похоже на ограничение параметров типа в С#:

public bool Equals<T>(T x, T y) where T : IComparable

Однако есть разница. Во-первых, подтипирование: вы можете создать экземпляр с помощью Person и вызвать его с помощью Student и Teacher.

Но есть и разница в том, что это компилирует. Код С# компилируется почти точно, что говорит его тип. Элемент проверки типов гарантирует, что аргументы реализуют правильный интерфейс, и чем вы хороши.

В то время как код Haskell соответствует примерно так:

equals :: EqDict -> a -> a -> Bool

Функция получает дополнительный аргумент, словарь всех функций, необходимых ему для выполнения Eq вещей. Здесь вы можете использовать эту функцию и то, что она компилирует:

b1 = equals 2 4          --> b1 = equals intEqFunctions 2 4
b2 = equals True False   --> b2 = equals boolEqFunctions True False

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

b3 = equals someStudent someTeacher
     --> b3 = equals personEqFunctions someStudent someTeacher

Как должен выглядеть словарь personEqFunctions, если a Student равен Teacher? У них даже нет одинаковых полей.

Короче говоря, в то время как ограничения типа Haskell с первого взгляда могут выглядеть как ограничения типа .NET, они реализованы совершенно по-другому и скомпилированы для двух действительно разных вещей.

Ответ 2

Теперь мы можем делать другие вещи с классами классов Haskell. Googling для "generics" в Haskell открывает все поле более высокого ранга полиморфного общего программирования, за пределами стандартного параметрического полиморфизма, который большинство людей думает как "дженерики".

Например, недавно GHC приобрела семейства типов, позволяя использовать всевозможные возможности программирования типа программирования. Очень простой пример - решения представления данных для каждого типа для произвольных полиморфных контейнеров.

Я могу сделать класс для say, lists,

class Listy a where

    data List a 
             -- this allows me to write a specific representation type for every particular 'a' I might store!

    empty   :: List a
    cons    :: a -> List a -> List a
    head    :: List a -> a
    tail    :: List a -> List a

Я могу писать функции, которые работают со всем, что создает экземпляр List:

map :: (Listy a, Listy b) => (a -> b) -> List a -> List b
map f as = go as
  where
    go xs
        | null xs   = empty
        | otherwise = f (head xs) `cons` go (tail xs)

И все же мы никогда не задавали определенный тип представления.

Теперь это класс для общего списка. Я могу дать конкретные хитроумные представления, основанные на типах элементов. Так, например, для списков Int, я мог бы использовать массив:

instance Listy Int where

data List Int = UArray Int Int

...

Итак, вы можете начать делать довольно мощное общее программирование.

Ответ 3

Еще одно большое различие заключается в том, что генераторы С# не допускают абстракции над конструкторами типов (т.е. виды, отличные от *), в то время как Haskell. Попробуйте перевести следующий тип данных в класс С#:

newtype Fix f = In { out :: f (Fix f) }

Ответ 4

Чтобы следить за тем, "вы можете столкнуться с большими проблемами, думая, что концепция, с которой вы знакомы с одного языка, одинакова на другом языке (к которому вы новичок)", часть этого вопроса:

Здесь ключевое различие (от, скажем, Ruby) вам нужно понять, когда вы используете классы типа Haskell. Для такой функции, как

add :: Num a => a -> a -> a
add x y = x + y

Это не означает, что x и y - оба типа класса Num. Это означает, что x и y имеют тот же тип, тип которого имеет класс Num. "Ну, конечно, вы говорите, это то же самое, что и". Что я тоже сказал, но мне потребовалось много месяцев, чтобы не подумать, что если x был Int, а y был Integer, это было бы похоже на добавление Fixnum и Bignum в Ruby, Скорее всего:

*Main> add (2::Int) (3::Integer)

<interactive>:1:14:
    Couldn't match expected type `Int' against inferred type `Integer'
    In the second argument of `add', namely `(3 :: Integer)'
    In the expression: add (2 :: Int) (3 :: Integer)
    In the definition of `it': it = add (2 :: Int) (3 :: Integer)

Другими словами, подклассификация (хотя оба экземпляра Num, конечно, также являются экземплярами Eq), и утиная печать исчезает, ребенок.

Это звучит довольно просто и очевидно, но для обучения вам нужно понять это инстинктивно, а не просто интеллектуально, по крайней мере, если вы пришли из лет Java и Ruby.

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