Що саме робить систему типу Haskell настільки шанованою (проти скажімо, Java)?


204

Я починаю вивчати Хаскелл . Я дуже новачок у цьому, і я просто читаю пару онлайн-книг, щоб обернутися головою навколо її основних конструкцій.

Один із «мемів», про які часто говорили знайомі з ним, - це ціла річ, «якщо вона збирається, вона буде працювати *» - що, на мою думку, пов’язане з силою типової системи.

Я намагаюся зрозуміти, чому саме Haskell кращий, ніж інші статично набрані мови в цьому плані.

Інакше кажучи, я вважаю, що в Яві ви можете зробити щось грізне, як поховати, ArrayList<String>()щоб містити щось, що насправді має бути ArrayList<Animal>(). Тут суттєвим є те, що ваш stringвміст містить elephant, giraffeі т.д., і якщо хтось додає Mercedes- ваш компілятор вам не допоможе.

Якщо я це зробив ArrayList<Animal>()тоді, в якийсь пізній момент часу, якщо я вирішу, що моя програма не є справді про тварин, а про транспортні засоби, то я можу змінити, скажімо, функцію, яка виробляє, ArrayList<Animal>щоб виробляти, ArrayList<Vehicle>і мій IDE повинен мені говорити всюди там - компіляційна перерва.

Моє припущення, що це те, що люди мають на увазі під системою сильного типу, але мені не очевидно, чому в Haskell краще. По-іншому, ви можете писати добру або погану Java, я припускаю, що ви можете зробити те ж саме в Haskell (тобто вкладати речі в рядки / ints, які дійсно повинні бути першокласними типами даних).

Я підозрюю, що пропускаю щось важливе / базове.
Я був би дуже радий, щоб мені показали помилку моїх шляхів!


31
Я дозволю людям більш обізнаним, ніж я пишу реальні відповіді, але суть цього полягає в наступному: статично набрані мови на зразок C # мають систему типів, яка намагається допомогти вам написати код, що захищається ; типу типів, таких як спроба Haskell допомогти вам написати правильний (тобто доказний) код. Основний принцип роботи - це переміщення речей, які можна перевірити на етапі компіляції; Haskell перевіряє більше речей під час компіляції.
Роберт Харві

8
Я не знаю багато про Haskell, але я можу говорити про Java. Незважаючи на те, що він видається сильно набраним, він все ще дозволяє робити «грізні» речі, як ви вже говорили. Практично для кожної гарантії, яку дає Java щодо своєї системи типів, є спосіб її обійти.

12
Я не знаю, чому всі відповіді згадуються Maybeлише до кінця. Якби мені довелося вибрати лише одне, що більш популярні мови повинні запозичити у Haskell, це було б саме. Це дуже проста ідея (настільки не дуже цікава з теоретичної точки зору), але це одне полегшило б наші робочі місця.
Павло

1
Тут буде чудові відповіді, але, намагаючись допомогти, вивчити підписи типу. Вони дозволяють людям і програмам міркувати про програми таким чином, щоб проілюструвати, як Java перебуває в каламутній середній справі.
Майкл Пасха

6
Для справедливості я мушу зазначити, що "ціле, якщо воно складе, воно буде спрацьовувати" - це гасло, а не буквальне твердження факту. Так, ми, програмісти Haskell, знаємо, що передача перевірки типу дає хороші шанси на коректність, за деякими обмеженими уявленнями про правильність, але це, звичайно, не буквально і загально "правдиве" твердження!
Том Елліс

Відповіді:


230

Ось не упорядкований перелік типів системних функцій, доступних у Haskell, або недоступних, або менш приємних в Java (наскільки мені відомо, що, очевидно, слабкий wrt Java)

  • Безпека . Типи Haskell мають досить хороші властивості "типу безпеки". Це досить специфічно, але по суті означає, що значення в якомусь типі не можуть безпідставно трансформуватися в інший тип. Іноді це суперечить мінливості (див. Обмеження значення OCaml )
  • Алгебраїчні типи даних . Типи в Хаскеллі мають по суті таку ж структуру, як математика середньої школи. Це надзвичайно просто і послідовно, але, як виявляється, настільки потужне, як ви могли б хотіти. Це просто чудова основа для типової системи.
    • Програмування типових даних . Це не те саме, що родові типи (див. Узагальнення ). Натомість, завдяки простоті типової структури, як зазначалося раніше, відносно легко написати код, який працює в цілому над цією структурою. Пізніше я розповім про те, як Eqкомпілятор Haskell може набути автоматичне отримання чогось подібного uality для визначеного користувачем типу. По суті, це робиться шляхом проходження загальної, простої структури, що лежить в основі будь-якого визначеного користувачем типу, і зіставлення її між значеннями - цілком природна форма структурної рівності.
  • Взаємно рекурсивні типи . Це лише важливий компонент написання нетривіальних типів.
    • Вкладені типи . Це дозволяє визначати рекурсивні типи над змінними, які повторюються у різних типів. Наприклад, один тип збалансованих дерев є data Bt a = Here a | There (Bt (a, a)). Подумайте уважно про дійсні значення Bt aта помітьте, як працює цей тип. Це хитро!
  • Узагальнення . Це майже занадто нерозумно, щоб не було в системі типів (гм, дивлячись на тебе, іди). Важливо мати уявлення про змінні типу та вміння говорити про код, який не залежить від вибору цієї змінної. Хіндлі Мілнер - це система типу, яка походить від системи F. Система типів Хаскелла - це розробка типу HM, і система F по суті є вогнищем узагальнення. Що я хочу сказати, це те, що у Haskell є дуже хороша історія узагальнення.
  • Абстрактні типи . Історія Хаскелла тут не велика, але й неіснуюча. Можна писати типи, які мають публічний інтерфейс, але приватну реалізацію. Це дозволяє нам пізніше визначити зміни коду реалізації і, що важливо, оскільки це основа всієї операції в Haskell, писати "магічні" типи, які мають чітко визначені інтерфейси, такі як IO. Чесно кажучи, у Java є приємніша історія абстрактного типу, але я не думаю, що інтерфейси не стали популярнішими.
  • Параметричність . Значення Haskell не мають жодних - або універсальних операцій. Java порушує це такими речами, як референтна рівність і хеширование, і ще більш очевидно з примусами. Це означає, що ви отримуєте безкоштовні теореми про типи, які дозволяють вам знати значення операції чи значення в значній мірі цілком від її типу - певні типи такі, що може бути лише дуже мала кількість жителів.
  • Типи вищого роду виявляють усі типи при кодуванні складніших речей. Функціонал / Застосування / Монада, Складная / Прохідна, вся mtlсистема набору ефектів, узагальнені точки фіксування функтора. Список продовжується і продовжується. Є багато речей, які найкраще виражаються у вищих видах, і відносно мало типів систем навіть дозволяють користувачеві говорити про ці речі.
  • Класи типу . Якщо ви вважаєте системи типів як логікою (що корисно), то від вас часто вимагають доводити речі. У багатьох випадках це по суті лінійний шум: може бути лише одна правильна відповідь, і програвач витрачає час і сили, щоб заявити про це. Класові типи - це спосіб Haskell генерувати докази для вас. Якщо говорити конкретніше, це дозволяє вирішити прості "системи рівнянь типу", наприклад "З яким типом ми збираємось робити (+)разом речі? О Integer, добре! Давайте введемо правильний код зараз!". У більш складних системах ви можете встановити більш цікаві обмеження.
    • Обчислення обмеження . Обмеження в Haskell - які є механізмом проникнення в систему прологів класового типу - структурно набираються. Це дає дуже просту форму співвідношення підтипів, яка дозволяє збирати складні обмеження з більш простих. Вся mtlбібліотека заснована на цій ідеї.
    • Виведення . Для того, щоб керувати канонічністю системи typeclass, необхідно написати багато часто тривіальних кодів для опису обмежень, визначених користувачем, типів повинен інстанціювати. До нормальної структури типів Haskell часто можна попросити компілятора зробити цей котлован для вас.
    • Тип прологу класу . Розв’язувач класу Haskell - система, яка генерує ті "докази", про які я згадувала раніше, - це, по суті, покалічена форма Prolog з приємнішими семантичними властивостями. Це означає, що ви можете кодувати дійсно волохаті речі в типі прологів і очікувати, що їх можна буде обробляти всі під час компіляції. Хороший приклад може бути вирішенням доказу, що два гетерогенні списки є рівнозначними, якщо ви забудете про порядок - вони еквівалентні гетерогенні "множини".
    • Багатопараметричні класи типів та функціональні залежності . Це просто масово корисні уточнення до базового типу прологів. Якщо ви знаєте Prolog, ви можете уявити, наскільки збільшується потужність виразів, коли ви можете записувати предикати більш ніж однієї змінної.
  • Досить вдалий висновок . Мови, засновані на системах типу Hindley Milner, мають досить хороші умовиводи. Сама HM має повний висновок, що означає, що вам ніколи не потрібно писати змінну типу. Haskell 98, найпростіша форма Haskell, вже викидає це за деяких дуже рідкісних обставин. Взагалі, сучасний Haskell був експериментом у повільному зменшенні простору повного висновку, додаючи при цьому більше енергії для HM і бачачи, коли користувачі скаржаться. Люди дуже рідко скаржаться - висновок Хаскелла досить хороший.
  • Дуже, дуже, дуже слабке підтипування . Раніше я згадував, що система обмежень від типу прокласу типу має поняття структурного підтипу. Це єдина форма підтипу в Haskell . Підтипування страшне для міркувань і висновків. Це ускладнює кожну з цих проблем (система нерівностей замість системи рівностей). Це також дуже легко зрозуміти (Чи підкласично це те саме, що підтипу? Звичайно, ні! Але люди дуже часто плутають це, і багато мов допомагають у цій плутанині! Як ми опинилися тут? Я думаю, ніхто ніколи не вивчає LSP.)
    • Зауважимо, нещодавно (на початку 2017 р.) Стівен Долан опублікував дисертацію про MLsub , варіант МЛ та умовивід типу Хіндлі-Мілнер, який має дуже гарну історію підтипу ( див. Також ). Це не скасовує те, що я писав вище - більшість систем підтипу зруйновані і мають поганий висновок - але це дозволяє припустити, що ми тільки сьогодні, можливо, виявили деякі перспективні способи, щоб повноцінно зробити висновки та підгрупувати гру. Тепер, щоб бути абсолютно зрозумілим, уявлення Java про підтипізацію ніяк не в змозі скористатися алгоритмами та системами Долана. Це вимагає переосмислення того, що означає підтипізація.
  • Типи вищого рангу . Я говорив про узагальнення раніше, але більш ніж просто узагальнення корисно мати можливість говорити про типи, які мають узагальнені змінні всередині них . Наприклад, відображення між структурами вищого порядку, яке не забуває (див. Параметричність ) до того, що ці структури "містять", має такий тип (forall a. f a -> g a). У прямому HM ви можете написати функцію в цьому типі, але з типами високого рангу ви вимагаєте такої функції в якості аргументу , як так: mapFree :: (forall a . f a -> g a) -> Free f -> Free g. Зауважте, що aзмінна прив’язана лише до аргументу. Це означає , що Визначник функції mapFreeбуде вирішувати , що aконкретизується в тому, коли вони використовують його, а не користувач mapFree.
  • Екзистенційні типи . Хоча типи вищих рангів дозволяють нам говорити про квантори загальності, екзистенційні типи дозволяють нам говорити про екзистенціальної квантификации: ідея , що тільки існує якийсь невідомий типу задовольняє деяких рівнянь. Це в кінцевому підсумку є корисним, і тривати довше про це буде потрібно багато часу.
  • Тип сімей . Іноді механізми класового класу незручні, оскільки ми не завжди думаємо про Prolog. Сімейства типів дозволяють писати прямі функціональні зв'язки між типами.
    • Сімейства закритого типу . Сімейства типів за замовчуванням відкриті, що дратує, оскільки це означає, що, хоча ви можете їх продовжити в будь-який час, ви не можете "перевернути" їх з будь-якою надією на успіх. Це пояснюється тим, що ви не можете довести інжективність , але це стосується сімей закритого типу.
  • Види індексованих типів та просування типів . На даний момент я отримую по-справжньому екзотику, але час від часу вони мають практичне використання. Якщо ви хочете написати тип ручок, які є відкритими або закритими, ви можете зробити це дуже добре. У наступному фрагменті зауважте, що Stateце дуже простий алгебраїчний тип, який також змістив його значення на рівні типу. Потім, в подальшому, ми можемо говорити про конструкторах типу , як , Handleяк приймати аргументи на конкретних видах , як State. Це незрозуміло розуміти всі деталі, але також так дуже правильно.

    data State = Open | Closed
    
    data Handle :: State -> * -> * where
      OpenHandle :: {- something -} -> Handle Open a
      ClosedHandle :: {- something -} -> Handle Closed a
  • Представлення типу виконання, які працюють . Java відома тим, що стирає тип і стирає дощ на парадах деяких людей. Типове стирання - це правильний шлях, однак, якби у вас є функція, getRepr :: a -> TypeReprви принаймні порушуєте параметричність. Гірше те, що якщо це функція, створена користувачем, яка використовується для запуску небезпечних примусів під час виконання ... тоді у вас виникає величезна проблема безпеки . TypeableСистема Haskell дозволяє створити сейф coerce :: (Typeable a, Typeable b) => a -> Maybe b. Ця система покладається на Typeableреалізацію в компіляторі (а не в користувальницькій країні), а також не може бути надана така приємна семантика без механізму класового типу Haskell та законів, яких гарантовано слідкувати.

Окрім цього, значення типу типів Haskell також стосується того, як типи описують мову. Ось кілька функцій Haskell, які визначають значення через систему типу.

  • Чистота . Haskell не дозволяє побічних ефектів для дуже, дуже, дуже широкого визначення "побічний ефект". Це змушує вас вводити більше інформації в типи, оскільки типи керують входами та виходами та без побічних ефектів, все повинно враховуватися у входах та виходах.
    • IO . Згодом Haskell потрібен був спосіб поговорити про побічні ефекти - оскільки будь-яка реальна програма повинна включати деякі - тому поєднання типів класів, типів вищого роду та абстрактних типів породило уявлення про використання конкретного, суперспеціального типу, покликаного IO aпредставляти побічні обчислення, які призводять до значень типу a. Це основа дуже приємної системи ефектів, вбудованої всередину чистої мови.
  • Нестачаnull . Всім відомо, що nullв сучасних мовах програмування є помилка на мільярд доларів. Алгебраїчні типи, зокрема здатність просто додавати стан "не існує" до типів у вас, перетворюючи тип Aу тип Maybe A, повністю зменшують проблему null.
  • Поліморфна рекурсія . Це дозволяє визначити рекурсивні функції, які узагальнюють змінні типу, незважаючи на використання їх у різних типах у кожному рекурсивному виклику, під час власного узагальнення. Про це важко говорити, але особливо корисно говорити про вкладені типи. Озирніться до Bt aтипу до і спробувати написати функцію для розрахунку її розміру: size :: Bt a -> Int. Це буде виглядати трохи як size (Here a) = 1і size (There bt) = 2 * size bt. Оперативно це не надто складно, але зауважте, що рекурсивний виклик sizeв останньому рівнянні відбувається за іншим типом , але загальне визначення має гарний узагальнений тип size :: Bt a -> Int. Зауважте, що це функція, яка порушує загальний висновок, але якщо ви надаєте підпис типу, Haskell це дозволить.

Я міг би продовжувати, але цей список повинен почати вас починати.


7
Нуль не був «помилкою» на мільярд доларів. Є випадки, коли неможливо статично перевірити, що вказівник не буде відмежований, перш ніж можливо існувати якесь значуще ; мати спробу захоплення відхилення в такому випадку часто краще, ніж вимагати, щоб покажчик спочатку ідентифікував безглуздий об'єкт. Я думаю, що найбільшою помилкою, пов’язаною з нулем, було те, що реалізація, яка, дається , буде вловлюватися , але не потрапить у пастку ніchar *p = NULL;*p=1234char *q = p+5678;*q = 1234;
supercat

37
Це просто цитування з Тоні Хоара: en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions . Хоча я впевнений, що nullв арифметиці вказівника є необхідні випадки , я замість цього інтерпретую це, щоб сказати, що арифметика вказівника є поганим місцем для розміщення семантики вашої мови.
Дж. Абрахамсон

18
@supercat, ти справді можеш написати мову без нуля. Дозволити це чи ні - це вибір.
Пол Дрейпер

6
@supercat - Ця проблема існує і в Haskell, але в іншій формі. Хаскелл зазвичай ледачий і непорушний, і тому дозволяє писати до p = undefinedтих пір, поки pйого не оцінюють. Більш корисно, ви можете помістити undefinedякусь змінне посилання, знову ж таки, поки ви не оціните це. Більш серйозна проблема полягає в ледачих обчисленнях, які можуть не припинятися, що, звичайно, не можна вирішити. Основна відмінність полягає в тому, що це все однозначно помилки програмування і ніколи не використовуються для вираження звичайної логіки.
Крістіан Конкл

6
@supercat Haskell повністю не вистачає еталонної семантики (це поняття референтної прозорості, яке тягне за собою, що все зберігається шляхом заміни посилань на їх референтів). Таким чином, я вважаю, що ваше запитання є неправильним.
Дж. Абрахамсон

78
  • Висновки повного типу. Ви дійсно можете використовувати складні типи всюди, не відчуваючи, як: "Святе дерьмо, все, що я коли-небудь роблю, це писати підписи типів".
  • Типи є повністю алгебраїчними , що дозволяє дуже легко висловити деякі складні ідеї.
  • У Haskell є класи типів, які подібні до інтерфейсів, за винятком того, що вам не потрібно розміщувати всі реалізації для одного типу в одному місці. Ви можете створювати реалізації власних класів типів для існуючих сторонніх типів, не потребуючи доступу до їх джерела.
  • Функції вищого порядку та рекурсивні функції мають тенденцію до введення більшої функціональності в сферу перевірки типу перевірки. Візьмемо , наприклад, фільтр . Мовою імперативів ви можете написати forцикл для реалізації тієї ж функціональності, але у вас не буде однакових гарантій статичного типу, оскільки forцикл не має поняття повернення типу.
  • Відсутність підтипів значно спрощує параметричний поліморфізм.
  • Типи вищого роду (типи типів) порівняно легко задати та використовувати в Haskell, що дозволяє створювати абстракції навколо типів, які абсолютно не можуть бути зрозумілими на Java.

7
Гарна відповідь - чи не могли б ви дати мені простий приклад вищого роду, подумайте, що допоможе мені зрозуміти, чому це неможливо зробити в Java.
phatmanace

3
Є кілька хороших прикладів тут .
Карл Білефельдт

3
Відповідність шаблонів також дуже важлива, це означає, що ви можете використовувати тип об'єкта, щоб легко приймати рішення.
Бенджамін Грюнбаум

2
@BenjaminGruenbaum Я не думаю, що я б назвав цю функцію системи.
Доваль

3
Хоча ADT і HKT є, безумовно, частиною відповіді, я сумніваюся, що хтось, хто задає це питання, дізнається, чому вони корисні, я пропоную розширити обидва розділи, щоб пояснити це
jk.

62
a :: Integer
b :: Maybe Integer
c :: IO Integer
d :: Either String Integer

У Haskell: ціле число, ціле число, яке може бути нульовим, ціле число, значення якого походить із зовнішнього світу, і ціле число, яке може бути замість нього рядком, - це всі різні типи - і компілятор буде це виконувати . Ви не можете скласти програму Haskell, яка не дотримується цих відмінностей.

(Однак ви можете опустити декларації типу. У більшості випадків компілятор може визначити найбільш загальний тип для ваших змінних, що призведе до успішної компіляції. Хіба це не акуратно?)


11
+1, хоча ця відповідь не є повною, я вважаю, що це набагато краще, ніж на рівні питання
jk.

1
+1 Хоча це допоможе пояснити, що в інших мовах також є Maybe(наприклад, у Java Optionalта Scala Option), але в цих мовах це напівфабрикатне рішення, оскільки ви завжди можете призначити nullзмінну цього типу та змусити вашу програму вибухнути під час запуску- час. Це не може статися з Haskell [1], оскільки немає нульового значення , тому ви просто не можете обдурити. ([1]: насправді ви можете генерувати подібну помилку до NullPointerException, використовуючи часткові функції, наприклад, fromJustколи у вас є Nothing, але ці функції, ймовірно, нахмурені).
Андрес Ф.

2
"ціле число, значення якого прийшло із зовнішнього світу" - не IO Integerбуло б ближче до "підпрограми, яка при виконанні дає ціле число"? Оскільки а) main = c >> cзначення, повернене першим, cможе бути різним, то другим, cтоді як воно aбуде мати одне і те ж значення незалежно від його положення (до тих пір, поки ми знаходимося в одній області застосування) б) існують типи, які позначають значення із зовнішнього світу для забезпечення його санації (тобто не вводити їх безпосередньо, але спочатку перевірити, чи введені користувачем правильні / не шкідливі).
Maciej Piechotka

4
Мацей, це було б точніше. Я прагнув до простоти.
WolfeFan

30

Багато людей перерахували хороші речі про Хаскелл. Але, відповідаючи на ваше конкретне питання "чому система типів робить програми більш правильними?", Я підозрюю, що відповідь - "параметричний поліморфізм".

Розглянемо наступну функцію Haskell:

foobar :: x -> y -> y

Існує буквально лише один можливий спосіб реалізації цієї функції. Тільки за підписом типу я можу точно сказати, що ця функція виконує, тому що є лише одна можлива річ, яку вона може зробити. [Добре, не зовсім, але майже!]

Зупиніться і подумайте про це на мить. Це насправді справді велика справа! Це означає, що якщо я записую функцію з цим підписом, функція насправді неможливо робити щось інше, ніж те, що я задумав. (Звичайно, сама підпис типу все ще може бути помилковою. Жодна мова програмування ніколи не запобігає всім помилкам.)

Розглянемо цю функцію:

fubar :: Int -> (x -> y) -> y

Ця функція неможлива . Ви буквально не можете реалізувати цю функцію. Я можу це сказати просто з підпису типу.

Як бачите, підпис типу Haskell говорить вам про пекло багато!


Порівняйте з C #. (Вибачте, моя Java трохи іржава.)

public static TY foobar<TX, TY>(TX in1, TY in2)

Цей метод міг би зробити декілька:

  • Повернення in2як результат.
  • Цикл назавжди, і ніколи нічого не повертайте.
  • Киньте виняток, і ніколи нічого не повертайте.

Власне, у Haskell є і ці три варіанти. Але C # також дає додаткові варіанти:

  • Повернути нуль. (У Haskell немає нуля.)
  • Змініть in2перед поверненням. (Haskell не має місцевих модифікацій.)
  • Використовуйте рефлексію. (У Haskell немає роздумів.)
  • Виконайте кілька дій вводу / виводу перед поверненням результату. (Haskell не дозволить вам виконувати введення-виведення, якщо ви не заявите, що ви тут виконуєте I / O.)

Рефлексія - особливо великий молоток; використовуючи рефлексію, я можу побудувати новий TYпредмет із повітря, і повернути це! Я можу оглянути обидва об'єкти та робити різні дії залежно від того, що я знаходжу. Я можу вносити довільні зміни до обох об'єктів, переданих у.

I / O - аналогічно великий молоток. Код може відображати повідомлення користувачеві, або відкривати підключення до бази даних, або переформатувати ваш жорсткий диск, або що-небудь в дійсності.


Функція Haskell foobar, навпаки, може приймати лише деякі дані та повертати ці дані без змін. Він не може "переглянути" дані, оскільки його тип невідомий під час компіляції. Він не може створити нові дані, тому що ... ну як ви будуєте дані будь-якого можливого типу? Для цього вам знадобиться роздум. Він не може виконати жодного вводу-виводу, оскільки підпис типу не оголошує, що виконується введення-виведення. Таким чином, він не може взаємодіяти з файловою системою або мережею, або навіть виконувати потоки в одній програмі! (Тобто, це гарантовано 100% безпеку різьби.)

Як бачите, не дозволяючи вам робити цілу купу речей, Haskell дозволяє вам зробити дуже міцні гарантії щодо того, що насправді робить ваш код. Насправді настільки щільно, що (для справді поліморфного коду) зазвичай є лише один можливий спосіб, щоб шматки могли злитися між собою.

(Щоб було зрозуміло: все-таки можна записати функції Haskell, де підпис типу не дуже розказує. Int -> IntМоже бути майже нічого. Але навіть тоді ми знаємо, що один і той же вхід завжди даватиме однаковий вихід із 100% визначеністю. Java навіть не гарантує цього!)


4
+1 Чудова відповідь! Це дуже потужне і часто недооцінене новачками Haskell. До речі, простіша "неможлива" функція була б fubar :: a -> b, чи не так? (Так, я знаю unsafeCoerce. Я припускаю, що ми не говоримо ні про що з "небезпечним" в його імені, і також не повинні турбуватися про це новачки!: D)
Андрес Ф.

Є багато простіших підписів, які ви не можете написати, так. Наприклад, foobar :: xдосить безперебійно ...
MathematicalOrchid

Насправді, ви не можете зробити чистий код небезпечним, але ви все одно можете зробити його багатопотоковим. Ваші варіанти "перед тим, як оцінити це, оцінити це", "коли ви це оціните, ви можете також оцінити це в окремому потоці", і "коли ви це оціните, ви можете також оцінити це, можливо в окрему нитку ". За замовчуванням - "роби як хочеш", що по суті означає "оцінювати якомога пізніше".
Джон Дворак

Більш прозаїчно можна викликати методи екземплярів на in1 або in2, які мають побічні ефекти. Або ви можете змінити глобальний стан (який, надається, моделюється як дія вводу-виводу в Haskell, але це може бути не так, як більшість людей вважають IO).
Doug McClean

2
@isomorphismes Тип x -> y -> yідеально реалізований. Тип (x -> y) -> y- ні. Тип x -> y -> yзаймає два входи, а другий повертає. Тип (x -> y) -> yприймає функцію, яка працює над цим x, і дещо має yвийти з цього ...
MathematicalOrchid

17

Споріднене питання ЗО .

Я припускаю, що ви можете зробити те ж саме в haskell (тобто вкладати речі в рядки / ints, які дійсно повинні бути типами даних першого класу)

Ні, ви насправді не можете - принаймні не так, як Java. У Java відбувається таке:

String x = (String)someNonString;

і Java із задоволенням спробує передати вашу не-String як струну. Haskell не дозволяє такого роду речі, усуваючи цілий клас помилок виконання.

nullє частиною системи типів (as Nothing), тому її потрібно чітко запитати та обробляти, виключаючи цілий інший клас помилок виконання.

Є ще маса тонких переваг - особливо щодо повторного використання та класів типів -, що я не маю досвіду, щоб добре знати, щоб спілкуватися.

В основному, це тому, що система типу Haskell дозволяє багато виразності. Ви можете зробити цілу велику кількість матеріалів, виконавши лише кілька правил. Розглянемо завжди присутнє дерево Haskell:

data Tree a = Leaf a | Branch (Tree a) (Tree a) 

Ви визначили ціле загальне двійкове дерево (і два конструктори даних) у досить читаному одному рядку коду. Усі просто використовують декілька правил (мають типи суми та типи продуктів ). Це 3-4 файли коду та класи на Java.

Особливо серед тих, хто схильний шанувати системи типів, цей тип лаконічності / елегантності високо цінується.


Я зрозумів лише NullPointerExceptions з вашої відповіді. Чи можете ви включити більше прикладів?
Jesvin Jose Jose

2
Не обов'язково правда, JLS §5.5.1 : Якщо T - клас класу, то або | S | <: | T |, або | T | <: | S |. В іншому випадку виникає помилка часу компіляції. Тож компілятор не дозволить викидати неперевершені типи - очевидно, існують способи їх вирішення.
Борис Павук

На мою думку, найпростішим способом встановити переваги класів типів є те, що вони схожі на interfaces, які можна додати за фактом, і вони не «забувають» тип, який реалізує їх. Тобто, ви можете гарантувати, що два аргументи функції мають один і той же тип, на відміну від interfaces, де два List<String>s можуть мати різні реалізації. Ви можете технічно зробити щось дуже схоже на Java, додавши параметр типу до кожного інтерфейсу, але 99% існуючих інтерфейсів цього не роблять, і ви переплутаєте чорт ваших колег.
Довал

2
@BoristheSpider Щоправда, але винятки для кастингу майже завжди включають в себе перехід від суперкласу до підкласу або від інтерфейсу до класу, і це незвично для суперкласу Object.
Доваль

2
Я думаю, що питання в питаннях про рядки полягає не в помилках кастингу та типу виконання, а в тому, що якщо ви не хочете використовувати типи, Java не зробить вас - як, власне, зберігання даних у серіалізованих форма, зловживаючи рядками як спеціальний anyтип. Haskell не зупинить вас і цим, оскільки ... ну, у нього є рядки. Haskell може дати вам інструменти, але він не може насильно заважати вам робити дурні речі, якщо ви наполягаєте на тому, щоб перекладене достатньо інтерпретатора, щоб винаходити nullу вкладеному контексті. Жодна мова не може.
Левшенко

0

Один із «мемів», про які часто говорили знайомі з ним, - це ціла річ, «якщо вона збирається, вона буде працювати *» - що, на мою думку, пов’язане з силою типової системи.

В основному це стосується невеликих програм. Haskell заважає вам робити помилки, які прості в інших мовах (наприклад, порівняння Int32і, а, Word32і щось вибухає), але це не заважає вам помилитися.

Haskell насправді робить рефакторинг набагато простішим. Якщо ваша програма раніше була правильною і вона перевіряє текст, є велика ймовірність, що вона все-таки буде правильною після незначних виправлень.

Я намагаюся зрозуміти, чому саме Haskell кращий, ніж інші статично набрані мови в цьому плані.

Типи в Haskell досить легкі, тому що легко оголосити нові типи. Це на відміну від такої мови, як Руст, де все просто трохи громіздкіше.

Моє припущення, що це те, що люди мають на увазі під системою сильного типу, але мені не очевидно, чому в Haskell краще.

Haskell має багато можливостей, що перевищують просту суму та типи продуктів; він також має універсально кількісні типи (наприклад id :: a -> a). Ви також можете створювати типи записів, що містять функції, що сильно відрізняється від такої мови, як Java або Rust.

GHC також може виводити деякі екземпляри лише на основі типів, і з моменту появи родових даних ви можете записувати функції, які є загальними серед типів. Це досить зручно, і воно більш вільне, ніж те саме на Java.

Ще одна відмінність полягає в тому, що Haskell має тенденцію мати порівняно хороші помилки типу (як мінімум, при написанні). Виведення типу Haskell є складним, і досить рідко вам потрібно буде надати анотації типу, щоб отримати щось для складання. Це на відміну від Rust, коли умовивід про тип може іноді вимагати приміток, навіть коли компілятор може в принципі вивести тип.

Нарешті, у Haskell є типові класи, серед них знаменита монада. Монади є особливо приємним способом вирішення помилок; вони в основному дають вам майже всю зручність nullбез жахливої ​​налагодження і не відмовляючись від безпеки вашого типу. Тож здатність писати функції на цих типах насправді має велике значення, коли йдеться про заохочення нас використовувати їх!

По-іншому, ви можете писати добру або погану Java, я припускаю, що ви можете зробити те ж саме в Haskell

Це, мабуть, правда, але він не має важливого моменту: точка, коли ти починаєш стріляти в ногу в Хаскелл, знаходиться далі, ніж точка, коли ти починаєш стріляти в ногу на Яві.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.