LINUX.ORG.RU

Стоит ли учить Haskell?

 


0

3

Поставил Haskell, набрал програмку:

integer 0 = 3
main = print (integer 1)

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

Можно задаться вопросом, а почему она не во время компиляции была. Но у меня вопрос, что получается что Haskell не так уж и надежен, в нем можно забыть определить значение, и компилятор даже не предупредит об этом. Стоит ли его изучать?

(А тут еще и про новую версию на главной сайта... Совпало)



Последнее исправление: victor79 (всего исправлений: 1)

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

Но в целом, «компилируется - (почти) значит работает» - очень приятное свойство.

Хотя, конечно «компилируется - значит точно работает» - ещё лучше, конечно.

AndreyKl ★★★★★
()

кстати, неужели хаскель не даёт варнинга о неполном паттерн матчинге? окамль вроде ругается в таких случаях... может опция какая нибудь есть, типа "-Wall" ?

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 2)
Ответ на: комментарий от AndreyKl

С опцией -Wall он сообщил. После исправления проги на такой вариант (иначе варнингов было много):

integer :: Integer -> Integer
integer 0 = 3

main :: IO()
main = print (integer 1)

Было сообщение:

[1 of 1] Compiling Main             ( useHaskell.hs, useHaskell.o )

useHaskell.hs:4:1: warning: [-Wincomplete-patterns]
    Pattern match(es) are non-exhaustive
    In an equation for ‘integer’:
        Patterns not matched: p where p is not one of {0}
  |
4 | integer 0 = 3
  | ^^^^^^^^^^^^^
Linking useHaskell ...

Т.е. откуда произошел неправильный вызов он не сообщил. Он ругался на описание функции, но не на вызов.

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 2)
Ответ на: комментарий от hateyoufeel

Потому что надо -Wall -Werror. Тут эти флаги так же как в GCC и Clang обрабатываются.

Хорошо, так ругается. Но не на то, что нужно. Так задумано, функция имеет определение только для 0, и может еще для чего-то. А ругаться она должна на не корректный вызов, а определение как раз такое какое и задумывалось.

victor79
() автор топика
Ответ на: комментарий от Usruser

А что значит эта строка?

Функция integer принимает 0 и возвращает 3. Если передать в эту функцию какое-либо другое значение, то будет ошибка, т.к. эта функция не определена для других значений.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Функция integer принимает 0 и возвращает 3. Если передать в эту функцию какое-либо другое значение, то будет ошибка, т.к. эта функция не определена для других значений.

Да!!! Но во время компиляции, а не в рантайме. И на вызов, а не на объявление.

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от theNamelessOne

Он ругается на функцию, а функция правильная. Не правилен вызов.

Вот здесь ошибки нет:

integer :: Integer -> Integer
integer 0 = 3

main :: IO()
main = print (integer 0)

Но он по прежнему ругается если делать -Wall -Werror

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от victor79

Он ругается на функцию, а функция правильная. Не правилен вызов.

Нет. В типе твоей функции написано, что она принимает Integer, в смысле любое значение этого типа. Но по факту, она принимает только одно значение, а для всех остальных возвращает bottom. То есть твоя функция просто некорректна.

Если ты хочешь, чтобы ошибка была в месте вызова, используй Data.Restricted, синглтоны или liquid haskell.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от victor79

Там Integer можно убрать, думаю результат будет тот же.

Что значит убрать?

Ну а как бы ты написал эту маленьку тестовую программу? Что бы она не стала большой и раздутой?

f :: Integer -> Either String Integer
f 0 = Right 1
f _ = Left "Unexpected value"

main :: IO ()
main = putStrLn (either id show $ f 3)

Ну например.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 3)
Ответ на: комментарий от victor79

Он ругается на функцию, а функция правильная. Не правилен вызов.

нет, функция неправильная. у тебя тип integer предполагает и другие значения отличные от нуля (ну, просто такой это тип). А ты функцию не определил для других значений. Так как при вызове хаскель проверяет типы и 1 - это вполне подходящее значение для типа integer, то он и не ругается на вызов. Это ошибка при определении функции, а не при её вызове.

AndreyKl ★★★★★
()
Ответ на: комментарий от victor79

Там Integer можно убрать, думаю результат будет тот же.

это потому что 0 - представитель interger. Т.е. компилятор видит 0 и 3 и понимает что функция имеет тип interger -> interger. Поэтому тип можно не писать (он от этого не меняется, просто писанины меньше).

AndreyKl ★★★★★
()
Ответ на: комментарий от hateyoufeel

Так оно по прежнему ругается во время выполнения.

Суть: есть функция, в котору можно передавать только 0. Может еще 1, 2 и 3.

И пока вызываем ее с такими параметрами, все ок. Но как только вызывали с тем, с чем она не описана, д.б. ошибка во время компиляции.

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

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от victor79

И пока вызываем ее с такими параметрами, все ок. Но как только вызывали с тем, с чем она не описана, д.б. ошибка во время компиляции.

Тогда смотри в сторону Data.Restricted и прочих синглтонов. Тебе нужен тип, в котором будут только значения [1..3], а не весь Integer.

Хотя для таких маленьких случаев обычно просто создают новый тип: data MyInt = One | Two | Three и не парятся.

Можешь ещё на Liquid Haskell посмотреть. Это сторонняя штука, расширяющая систему типов хацкелла логикой предикатов (см. Refinment Types) и позволяющей писать вот такое: type RestrictedInt = {v : Int | v == -1 || v == 1 || v == 3}.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 6)
Ответ на: комментарий от victor79

На самом деле, сейчас ведётся работа в сторону Dependent Haskell, который как раз тоже такое позволит делать. Когда оно будет, хер его знает.

Алсо, в твоём случае всё это ломается если значение приходит откуда-то извне. Так что самый лучший вариант для твоего кода выглядит примерно вот так:

newtype RestrictedInt = RestrictedInt Int
  deriving (Eq, Show, ...)

mkRestrictedInt :: Int -> Maybe RestrictedInt
mkRestrictedInt i | i >= 1 && i <= 3 = Just (RestrictedInt i)
                  | otherwise = Nothing

И дальше как выше, с проверкой и прочим. Погугли про паттерн «smart constructor». В принципе, можно даже обильно обмазать типами и сделать что-то типа Range min max, где значение внутри будет гарантированно между двумя границами. Но опять же, проверка тоже будет в рантайме, просто компилятор заставит тебя её сделать.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Вот тебе пример небольшой. Нужен пакет typenums.

newtype RangedInt min max = RangedInt Int
  deriving (...)

mkRangedInt :: (KnownInt min, KnownInt max, min <= max ~ True) => Int -> Maybe (RangedInt min max)
mkRangedInt i
  | i >= minValue && <= maxValue = Just (RangedInt i)
  | otherwise = Nothing
  where
    minValue = fromInteger (intVal @(Proxy min) Proxy)
    maxValue = fromInteger (intVal @(Proxy max) Proxy)

И дальше у тебя будет тип, например, RangedInt 1 10, где значения будут гарантировано между 1 и 10. Наверняка это даже уже где-то реализовано в какой-то библиотеке, но я не смотрел.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 3)
Ответ на: комментарий от monk

Да нет, при компиляции такое тоже можно сделать. Только, опять же, это нужно только если ты реально знаешь все значения при компиляции. А это очень редко так, поэтому никто не парится.

hateyoufeel ★★★★★
()

Non-exhaustive patterns – правда очень неприятная тема в хаскеле. Но в целом язык неплох. Для твоих задач правда можно использовать LiquidHaskell:

module Main where

{-@ f :: (Num a, Num p) => {v:a | v = 0} -> p @-}
f 0 = 3

main = print (f 1)
Error: Liquid Type Mismatch
  Inferred type
    VV : {v : Integer | v == 1}
 
  not a subtype of Required type
    VV : {VV : Integer | VV == 0}

(ошибка именно в main, а не в f)

balsoft ★★
()
Последнее исправление: balsoft (всего исправлений: 1)