- FP:
- Иммутабельность
- Чистота
- Статическая типизация и вывод типов
- Функции высшего порядка
- Haskell:
- Ленивые вычисления
- Pattern matching
- Type classes
- Есть referential transparency ⇒ есть бОльшие возможности для оптимизации
- Проще делить программу на независимые модули и отлаживать их по отдельности (≈ один модуль - одна функция)
- Чистые фукнции проще объединять в композиции, так как между ними нет неявных связей в виде изменяемого состояния
- Чисто функциональные программы проще сделать параллельными
- Можно присваивать значения в любом порядке, так как при ленивых вычислениях вычисление значения произойдет только при его использовании
- Можно использовать бесконечные структуры данных
- Ленивый язык более выразительный, чем строгий
- Иногда можно улучшить эффективность использования памяти при ленивых вычислениях.
- Нет проблемы нелокальности: изменение по одной ссылке не приведет к изменениям по остальным ссылкам, так как “изменяющая” функция вернет новый объект
- Нет необходимости в копировании объектов
- Инварианты достаточно проверять только при создании объекта
- Нет зависимости от истории ⇒ нет зависимости от порядка вызова методов
- Безопасное хранение объекта в коллекции
- Не требуется синхронизация, так как все потоки только читают данные
- Раннее обнаружение ошибок :: Ошибки находятся на этапе компиляции, а не выполнения. Часто программа на SML или Haskell работает правильно, как только ее наконец удается скомпилировать
- Высокая поддерживаемость больших проектов :: Изменения могут быть верифицированы компилятором, и типы являются частью документации программы, облегчая ее понимание
- Автоматизированная обработка программ :: Например, автоматический рефакторинг, как в средах IDEA или Eclipse
- Оптимизация кода :: Код, написанный на статически типизированном языке, проще оптимизировать, так что в среднем статически типизированный язык эффективнее динамически типизированных
Имеется класс типов: class C a where maxInt :: Int
. Реализуйте данный класс типов для какого-нибудь типа данных.
Имеется класс типов: class C a where intGetter :: a -> Int
. Реализуйте данный класс типов для какого-нибудь типа данных.
data MyBool = MyBool Bool
instance C MyBool where
intGetter (MyBool True) = 1
intGetter _ = 0
cc = c {f = (+42)}
deriving
позволяет неявно определять функции стандартных typeclass
‘ов. Детали такой неявной реализации иногда зависят от компилятора.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr _ z [] = z
foldr f z (x:xs) = x `f` (foldr f z xs)
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl _ z [] = z
foldl f z (x:xs) = foldl (f z x) xs
Синтаксическим сахаром для какого кода является объявление следующего типа данных: data MyData = A { f :: Int, g :: Int -> Double }
data MyData = A Int (Int -> Double)
id :: a -> a
flip :: (a -> b -> c) -> b -> a -> c
-- a ≡ b -> c
flip id :: b -> (b -> c) -> c
(+) :: Num n => n -> n -> n
(.) :: (b -> c) -> (a -> b) -> a -> c
-- b ≡ n; c ≡ n -> n
((+) . ) :: (a -> n) -> a -> n -> n
-- left arg
(.) :: (b1 -> c1) -> (a1 -> b1) -> a1 -> c1
-- right arg
(.) :: (b2 -> c2) -> (a2 -> b2) -> a2 -> c2
-- b ≡ b1 -> c1 ≡ ((a2 -> b2) -> (a2 -> c2)); c ≡ (a1 -> b1) -> (a1 -> c1)
-- a ≡ b2 -> c2
((.) . (.)) :: (b2 -> c2) -> (a1 -> a2 -> b2) -> (a1 -> a2 -> c2)
f :: A a -> Double
Уточнить
newtype
гарантирует, что данные будут иметь такой же вид в рантайме, как и завернутый тип. То есть Конструктор для newtype
гарантированно стирается во время компиляции.
data
объявляет абсолютно новую структуру данных в рантайме.
Каррирование Вычисление функции, принимающей несколько аргументов, через несколько функций, принимающих один аргумент. Например, для функции 2-х аргументов h:(A × B) → C оператор каррирования Λ выполняет преобразование Λ(h):A → (B → C). То есть Λ: ((A × B) → C) → (A → (B → C)).
curry :: ((a, b) -> c) -> a -> b -> c
uncurry :: (a -> b -> c) -> (a, b) -> c
Функции высшего порядка Функции, принимающие в качестве аргументов другие функции или возвращающие другие функции в качестве результата.
map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
Ограничения на параметры в объявлениях data
и newtype
. Объявленные таким образом типы требуют выполнения ограничений при создании (construction) и деконструкции (deconstruction, ≈ разбиение конструктора при паттерн-матчинге), даже если эти ограничения неиспользуются. (deprecated in Haskell 7.2)
data Eq a => Foo a = Constr a
-- не можем написать функцию:
isEq :: Foo a -> Foo a -> Bool
-- должны написать:
isEq :: Eq a => Foo a -> Foo a -> Bool
isRa (Constr x) (Constr y) = x == y
-- не сработает:
getVal :: Foo a -> a
-- сработает:
getVal :: Eq a => Foo a -> a
getVal (Constr x) = x
Напишите тип следующей функции в наиболее общем виде: f a = map (* a) . map (uncurry (+)) . map (\x -> (x, x))
f :: Num a => a -> [a] -> [a]
Напишите функцию с типом, которая принимает список пар чисел и оставляет только такие, что сумма чисел в паре четная.
evenPairs :: (Integral a) :: [(a, a)] -> [(a, a)]
evenPairs = filter (even . uncurry (+))
Задан тип данных data Role a = A { name :: String, role :: a } | B { name :: String, roles :: [a] }
. Напишите конструкцию, синтаксическим сахаром для которой является данных Record Syntax.
data Role a = A String a | B String [a]
Когда функция изменяет класс или переводит класс во что-то другое - лучше внутри, если хотим использовать этот класс для вычисления, то снаружи.
(:)
- это префиксная нотация, :
- инфиксная
map f a
- префиксная, f `map` a
- инфиксная
type P a = a => Int
type D a = (a Int, Num Int)
Monad, Functor, Applicative
type P a = a Int => Int
Num, Ord, Eq, Show
MaybeT
type P a = a Int
(* -> *) -> Constraint
Не существует
A :: (* -> * -> *) -> * -> *
C :: (* -> Constraint) -> *
C :: (* -> *) -> *
D :: (* -> Constraint) -> Constraint
0
length = foldr (\_ n -> 1 + n) 0
см. реализацию Foldable
для Either
1
см. выше и реализацию Foldable
для Maybe
class (Functor t, Foldable t) => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
sequenceA :: Applicative f => t (f a) -> f (t a)
sequenceA = traverse id
mapM :: Monad m => (a -> m b) -> t a -> m (t b)
mapM = traverse
sequence :: Monad m => t (m a) -> m (t a)
sequence = sequenceA
instance Traversable [] where
traverse f = foldr consF (pure [])
where
consF x ys = (:) <$> f x <*> ys
instance Traversable Maybe where
traverse _ Nothing = pure Nothing
traverse f (Just x) = Just <$> f x
instance Traversable (Either a) where
traverse _ (Left x) = pure (Left x)
traverse f (Right y) = Right <$> f y
instance Foldable [] where
foldMap _ [] = mempty
foldMap f (x:xs) = f x <> foldMap f xs
instance Foldable Maybe where
foldr f zero Nothing = zero
foldr f zero (Just x) = f x zero
instance Foldable Either where
foldr f zero Left = zero
foldr f zero (Right x) = f x zero
Напишите, что делают эти расширения языка: TypeSynonyms, MultiParamTypeClasses, ViewPatterns, RecordsWildCards
TypeSynonyms
- типы-синонимы в конструкторах?
MultiParamTypeClasses
- несколько типов в объявлении класса.
ViewPatterns
- дает паттерн матчинг с функциями.
RecordWildcards
- упрощает работу с record: дает забивать на поля в паттерн-матчинге и конструкторах.
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
sequence :: Monad m => t (m a) -> m (t a)
traverse f = sequence . fmap f
sequence = traverse id -- так?
foldMap | foldr
traverse | sequenceA
instance Monoid a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
instance Monoid b => Monoid (a -> b) where
mempty _ = mempty
mappend f g x = f x `mappend` g x
instance Monoid (b -> b) where
mempty _ = id
mappend = (.)
newtype Arrow b = Arrow { getArrow :: b -> b }
-- для упрощения будем считать, что тип foldr следующий:
foldr :: (a -> b -> b) -> t a -> b -> b
-- иначе придется задействовать flip, если сильно захочется, когда-нибудь будет реализовано
foldMap :: Monoid m => (a -> m) -> t a -> m
transform :: (a -> b -> b) -> a -> Arrow b
transform = Arrow . f
foldr f = getArrow . foldMap (transform f)
1. fmap id = id
2. fmap (f . g) = (fmap f) . (fmap g)
fmap (f . g) F = fmap f (fmap g F)
class Functor f where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
(<$) = fmap . const
instance Functor ((->) r) where
fmap = (.)
instance Functor Maybe where
fmap f (Just x) = Just f x
fmap _ Nothing = Nothing
instance Functor (Either e) where
fmap f (Right x) = Right (f x)
fmap _ l = l
instance Functor ([]) where
fmap = map
(<<$>>) f w = (fmap $ fmap f) w
class Bifunctor p where
bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
instance Bifunctor (,) where
bimap f g (a, b) = (f a, g b)
instance Bifunctor Either where
bimap f _ (Left a) = Left (f a)
bimap _ g (Right b) = Right (g b)
<$> :: Functor f => (a -> b) -> f a -> f b
>>= :: Monad m => m a -> (a -> m b) -> m b
f <$> a = a >>= (return . f)
(>>=) :: m a -> (a -> m b) -> m b
fmap :: (a -> b) -> f a -> f b
join :: m (m a) -> m a
a >>= f = join (fmap f a)
1. identity
pure id <*> v = v
2. composition
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
3. homomorphism
pure f <*> pure x = pure (f x)
4. interchange
u <*> pure y = pure ($ y) <*> u
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
instance Applicative ((->) r) where
pure x = \_ -> x
f <*> g = \x -> f x (g x)
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
instance Applicative Maybe where
pure = Just
Just f <*> a = f <$> a
Nothing <*> _ = Nothing
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
instance Applicative (Either e) where
pure = Right
Left e <*> _ = Left e
Right f <*> r = fmap f r
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
instance Applicative [] where
pure x = [x]
fs <*> xs = [f x | x <- xs, f <- fs]
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
newtype ZipList a = zipList { getZipList :: [a] }
instance Applicative ZipList where
pure x = ZipList (repeat x)
(ZipList fs) <*> (ZipList xs) = ZipList (zipWith ($) fs gs)
liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
liftA3 f a b c = f <$> a <*> b <*> c
-- насколько я понимаю, так можно проворачивать с любым числом аргументов
Реализуйте функцию liftAA2 :: (Applicative f, Applicative g) => (a -> b -> c) -> f (g a) -> f (g b) -> f (g c)
liftAA2 = liftA2 . liftA2
Реализуйте функцию (<<*>>) :: (Applicative f, Applicative g) => f (g (a -> b)) -> f (g a) -> f (g b)
eitherA :: (Alternative f) => f a -> f b -> f (Either a b)
-- eitherA f1 f2 = fmap (Left) f1 НЕВЕРНО
Есть функция g :: a -> b
и объект x :: Applicative f => f a
. Напишите два разных способа получить объект y :: Applicative f => f b
из x
с использованием g
.
getY :: (Applicative f) => (a -> b) -> f a -> f b
getY = fmap
getY = (<$>)
getY g x = pure g <*> x
Монады применяют функции, которые возвращают завернутые значения, к завернутому знаению.
class Monad m where -- m :: * -> *
return :: a -> m a -- return
(>>=) :: m a -> (a -> m b) -> m b -- bind
(>>) :: m a -> m b -> m b -- then
m >> k = m >>= \_ -> k
(=<<) :: Monad m => (a -> m b) -> m a -> m b
f =<< x = x >>= f
infixl 1 >>, >>=
infixr 1 =<<
- []
- Maybe
- Either
- IO
- State
- Identity
- Writer
- Reader
- RWS
- Cont
- return
- >>=
- =<<
- >>
- liftM
- liftM2
- >=>
- <=<
- join
- ifM
- (||^)
join :: Monad m => m (m a) -> m a
ghci> join [[1,2], [3,4]]
[1,2,3,4]
ghci> join Just (Just 3)
Just 3
join :: Monad m => m (m a) -> m a
(>>=) :: m a -> (a -> m b) -> m b
join x = x >>= id
data [a] = [] | a : [a]
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)
data Maybe a = Nothing | Just a
instance Monad Maybe where
return = Just
Nothing >>= _ = Nothing
Just a >>= f = f a
data Either a b = Left a | Right b
instance Monad (Either a) where
return = Right
Right r >>= f = f r
Left l >>= _ = Left l
instance Monad ((->) r) where
return = const
f >>= k = \r -> k (f r) r
newtype Writer w a = Writer { runWriter :: (a, w) } -- a is value, w is log
-- Writer w a type is just a newtype wrapper for a tuple (a, w); just a reminder of what newtype is
instance Monoid w => Monad (Writer w) where
return a = Writer (a, mempty)
Writer (x, v) >>= f = let Writer (y, v') = f x
in Writer (y, v `mappend` v')
newtype Reader e a = Reader { runReader :: e -> a }
instance Monad (Reader e) where
return a = Reader $ \e -> a
(Reader r) >>= f = Reader $ \e -> runReader (f (r e)) e
newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State s) where
return a = State $ \s -> (a, s)
oldState >>= f = State $ \s -> let (a, newState) = runState oldState s
in runState (f a) newState
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
instance Monad (Cont r) where
return a = Cont ($ a)
Cont arr >>= f = Cont $ \br -> arr $ \a -> runCont (f a) br
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
Композиция функций, возвращающих завернутое значение.
Недостаточно
-- two-line do notation
do x <- m
e
-- desugars to:
m >>= (\x -> e)
-- one-line do notation
main = do putStrLn "hello, world"
-- you can just remove do
main = putStrLn "hello, world"
-- multi-line do notation
do x <- mx
y <- my
z
-- is equivalent to:
do x <- mx
do y <- my
z
-- desugars to:
mx >>= (\x -> my >>= (\y -> z ))
-- non-recursive let in a do block desugars to a lambda:
do let x = y
z
-- desugars to
(\x -> z) y
data IO a :: * -> *
instance Monad IO where
(>>=) = bindIO
unsafeInterleaveIO
дает дополнительные гарантии на порядок операций, идейно так реализовано:
do
before
unsafeInterleaveIO side
after
Гарантируется, что то, что в side
всегда выполнится после before
.
unsafePerformIO
таких гарантий не дает.
class MonadTrans t where -- t :: (* -> *) -> * -> *
lift :: Monad m => m a -> t m a
instance MonadTrans (StateT s) where
lift m = StateT $ \s -> do
a <- m
return (a, s)
instance (Monoid w) => MonadTrans (WriterT w) where
lift m = WriterT $ do
a <- m
return (a, mempty)
instance MonadTrans MaybeT where
lift = MaybeT . liftM Just
instance MonadTrans ReaderT where
lift m = ReaderT (const m)
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
type State s = StateT s Identity
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance (Monad m) => Monad (MaybeT m) where
fail _ = MaybeT (return Nothing)
return = lift . return
x >>= f = MaybeT $ do
v <- runMaybeT x
case v of
Nothing -> return Nothing
Just y -> runMaybeT (f y)
Base monad | Transformer | Original type | Combined type |
Maybe | MaybeT | Maybe a | m (Maybe a) |
Either | EitherT | Either a b | m (Either a b) |
Writer | WriterT | (a, w) | m (a, w) |
Reader | ReaderT | r -> a | r -> m a |
State | StateT | s -> (a,s) | s -> m (a, s) |
Cont | ContT | (a -> r) -> r | (a -> m r) -> m r |
Ленивые паттерны. Матчинг значения v на паттерн ~pat всегда успешный, независимо от pat. Может понадобиться, если у нас есть бесконечная структура, определенная рекурсивно.
Нужно написать больше
Пишем что-нибудь вроде функций с бесконечными списками, аллоцируем слишком много памяти. Stream fusion превращает нашу функцию во что-то, что использует рекурсивную функцию, которая аллоцирует себе столько памяти, сколько нужно для результата. Вместо явной рекурсии можем использовать нерекурсивные функции над списком, которые используют допустимые функции map
, foldr
и так далее. Тут стало слишком много Inception.
Монада, в которой есть мутабельные переменные и массивы, но при этом она referentially transparent. Strict state-transformer monad.
new/read/write/modifySTRef
, runST
.
Используем ! и убиваем ленивость. Делаем это, когда долго работаем и ломаемся по памяти; при арифметических операциях, рекурсии и подозрениях на утечки памяти.
deepseq
полностью вычисляет структуру (до нормальной формы). Обходит структуру “глубоко”, например, seq
вычислит до первого конструктора в списке, а deepseq
зафорсит вычисление всех элементов списка.
Первый вычисляет до WHNF, второй - до нормальной формы. Работает так: если вычислился результат seq
, то и аргумент тоже вычислен.
BangPatterns
удобнее пишется, особенно в больших количествах, а так это вроде синтаксический сахар для seq
.
STRef
- укзатель на мутабельный контейнер в монаде ST
, а IORef
, соответственно, в IO
. А по сути это вроде одно и то же.
Избавляемся от аллокации промежуточных списков. По сути сливаем одинаковые вызовы функций в один.
map f . map g = map (f . g)
IOArray
не очень мутабельный, IO-монада дает больше возможностей, но они не всегда нужны. А еще ST-операции можно делать в чистых функциях. Вообще STRef
используем, когда мутабельность нам не снаружи дают, а она является деталью имплементации, и снаружи наша деятельность кажется вполне себе чистой.
-- report :: Bool -> String -> Q () НЕВЕРНО
Но вообще цитирующими скобками же, наверное.
- Писать функции n аргументов
- Генерировать автоматом инстансы
- Что-то высчитывать во время компиляции, тип один раз и навсегда.
- Парсить всякие структуры типа json
- Генерировать автоматом линзы
Q
- монада цитирования, которая позволяет автоматически генерировать уникальные имена для переменных с помощью монадической функции newName :: String -> Q Name
.
[| |]
- quasi quotes, $()
- вклейка (splice). Цитирующие скобки преобразуют конкретный хаскель-код в структуру Exp
(получают AST), а вклейка - подставляет AST на место шаблона, поэтому это взаимно обратные операции.
Чтобы в сложных структурах(записях) удобно доставать и изменять значения.
Изоморфизм - связь между эквивалентными типами. Iso
- пара маппингов из первого типа во второй и из второго с первый, таких, что:
fw . bw = id
bw . fw = id
Призмы - это как линзы, но линзы смотрят на часть типа-произведения, а призмы спускаются на уровень ниже в типе-сумме вроде Either
или падают.
type Iso s t a b = forall p f. (Profunctor p, Functor f) => p a (f b) -> p s (f t)
from :: AnIso s t a b -> Iso b a t s
iso :: (s -> a) -> (b -> t) -> Iso s t a b
Укажите операторные обозначений функций view
, set
, over
. Есть ли отличие в типах функций и их операторных выражений?
view == (^.), set == (.~), over == (%~). Отличий не знаю.
Не понятно к чему это, ну тип view’ :: obj -> field Как мы его делаем, есть поле car, ну и пишем car Person
Software Transactional Memory - абстракция для concurrent communication, хороша тем, что две concurrent абстракции можно легко слепить в одну и не придется светить деталями реализации; позволяет выполнить транзакцию (либо все операции успешно, либо откат). Функции newTVar/readTVar/writeTVar, atomically, retry, orElse
.
Много хаскель-тредов могут быть замаплены на один ОС-тред, потому что в действительности этот ОС-тред всего лишь гоняет хаскель-рантайм. А рантайм сам разбирается со своим внутренним шедулингом, yield’ами и прочим. Это, кстати, уменьшает оверхед, который ось обычно тратит на context switching.
Стратегии позволяют выразить паралельные вычисления, то есть:
- поддерживают deterministic parallelism: результат программы не зависит от параллельных вычислений. Никаких сайд-эффектов.
- отделяют описание параллелизма от логики самой программы (модульность - здорово!). Делаем ленивую структуру, которая представляет собой наши вычисления, а потом пишем под нее стратегию, которая описывает, как обходить эту структуру и делать вычисления последовательно или параллельно.
- композиция! Берем маленькие стратегии, из них делаем большую.
- есть инстансы
Monad
иApplicative
для удобства тривиальных случаев
Примеры:
r0
: ничего не делай.rseq
: вычислить до WHNF.rdeepseq
: вычисли меня полностью.%op = evalSeq Control.Seq.%op
.rpar
: сделаем спарк для параллельного вычисления.rparWith
: композиция. Не покидает монадуEval
, не имеет встроенногоrseq
.
Когда рантайм GHC находит группу тредов, которые все заблочены на блокирующих мутабельных переменных (MVar
или переменные STM
), и видит, что другие треды на них не ссылаются, он решает, что все треды в дедлоке и отсылает им асинхронные исключения BlockedIndefinitelyOnMVar/STM
. Кстати, ловить асинхронные исключения моветон.
RunTime System. 50k строк сишного кода, хайлайты:
- содержит всякий вспомогательный код, который позволяет бросить эксепшн после
error
, аллоцироватьArray#
, организовать работу сMVar
. - включает в себя менеджер памяти плюс сборщик мусора.
- содержит userspace-шедулер для хаскель-тредов, с поддержкой шедулинга их на несколько процессоров, и позволяет хаскель-тредам вызывать внешние функции в разных тредах ОС.
- содержит интерпретатор байткода для GHCi и динамический линковщик туда же.
- может в разный профайлинг и покрытие кода.
- поддержка STM.
-Asize/-Hsize/-Msize, -threaded, -Nn, -prof
MVar T
- Мутабельная переменная, которая либо пуста, либо содержит значение типа t. Можно использовать как синхронизированную мутабельную переменную, как канал или как семафор. Функции: takeMVar
, putMVar
, read/swap/with/modifyMVar
forkIO
создает новый легковесный тред, где запустится IO, переданное в качестве аргумента, и возвращает его айдишник. Почему-то игнорирует исключения про дедлоки и убийство треда и пробрасывает остальные исключения как обычно.
forkFinally
форкает тред и, когда тот должен умереть, вызывает функцию, переданную аргументом, на эксепшне или возвращаемом значении. Своего рода хэндлер чего-то.
Par, Strategy, forkIO, MVar, STM, Async
Мини тред, занятый одним вычислением, соответственно легковесный и их можно завести очень много converted - useful work overflowed - sparks generated after spark pool limit achieved dud - already evaluated at the moment of applying `rpar` GC’d - unused and thrown away (ignored) => garbage collector fizzled - was unevluated at the time it was sparked but was later evaluated independently by the program
Напишите, как иметь список объектов разных функторов, внутри каждого из которых значения одинакового типа, чтобы иметь возможность применить функции из этого значения в другое?
data FunctorBox a = forall f . Functor f => FB (f a)
FB :: forall {a} {f :: * -> *} . Functor f => f a -> FunctorBox a -- as ghci sees it
fmapFB :: forall t a . (t -> a) -> FunctorBox t -> FunctorBox a
fmapFB f = \(FB a) -> FB (f <$> a)
Нужно написать больше
Для того, чтобы работать со значениями разных типов, но обладающими каким-то свойством (например, они одного класса), одинаково. Например, чтобы иметь возможность складывать такие значения в лист, получая тем самым гетерогенный лист, спрятав значения в некоторую “коробку” (type hider)
Чтобы явно аннотировать типы с использованием forall
-XRank2Types
разрешает полиморфные типы ранга 2, -XRankNTypes
разрешает полиморфные типы любого ранга.
В системах с полиморфными типами ранга 2 задача вывода типов разрешима, если же ранг > 2, то задача становится неразрешимо и возникает необходимость явной аннотации типов. С этим и связана необходимость разделения этих расширений языка.
Позволяет указывать, что переменные типа из сигнатуры распространяются на тело функции. Чтобы это работало, надо использовать forall
в сигнатуре:
{-# LANGUAGE ScopedTypeVariables #-}
import Data.List
main = putStrLn "No errors."
-- show
myFunction :: forall a. Ord a => [a] -> [(a, a)]
myFunction inputList = zip sortedList nubbedList
where sortedList :: [a]
sortedList = sort inputList
nubbedList :: [a]
nubbedList = nub inputList
-- /show
fmap :: forall f . forall a b . (a -> b) -> f a -> f b
С их помощью можно спросить у компилятора, какого типа должно быть твое что-то.
mfold :: [Maybe a] -> [Either a b]
mfold = foldr _f _z
> Found hole ‘_f’ with type: Maybe a -> [Either a b] -> [Either a b]
> Found hole ‘_z’ with type: [Either a b]
Нужно написать больше
Позволяет задавать явные аргументы типов полиморфной функции, например map @Int @Bool isEven xs
. Решает проблему show/read, потому что мы явно задаем типы и все тайпчекается.
answer_read = show (read @Int "3") -- "3" :: String
answer_show = show @Integer (read "5") -- "5" :: String
answer_showread = show @Int (read @Int "7") -- "7" :: String
Аналог typed holes для сигнатур функций:
arbitCs :: _ => a -> String
arbitCs x = show (succ x) ++ show (x == x)
Main.hs:6:12: warning: [-Wpartial-type-signatures]
Found constraint wildcard ‘_’ standing for ‘(Show a, Eq a, Enum a)’
In the type signature:
arbitCs :: _ => a -> String
Что такое Functional Dependencies? Назовите какой-нибудь известный вам type class, в котором присутствуют функциональные зависимости.
Функциональные зависимости используются для ограничения параметров тайпклассов. Они позволяют объявить, что в тайпклассе с несколькими параметрами один из параметров можно однозначно! определить по другим.
class Mult a b c | a b -> c where
(*) :: a -> b -> c
Классический (и единственный) пример использования - перемножение матриц/векторов/скаляров, тайпкласс указан выше.
Rank 0: Int
Rank 1: forall a . a -> Int
Rank 2: (forall a . a -> Int) -> Int
Rank 3: ((forall a . a -> Int) -> Int) -> Int
foo :: (forall a . a -> a) -> (Char,Bool)
foo f = (f 'c', f True)
Заставляет создавать компилятор кайнды для нашей даты Промоутит простые типы до кайндов
Generalized algebraic datatypes или просто ГАДы Generalized algebraic datatypes, or simply GADTs, are a generalization of the algebraic data types that you are familiar with. Basically, they allow you to explicitly write down the types of the constructors.
type Option = String
data Config = MakeConfig [Option]
type ConfigBuilder = Traced [Option] Config
profile :: ConfigBuilder \rightarrow Config
profile builder = runTraced builder ["-prof", "-auto-all"]
goFaster :: ConfigBuilder \rightarrow Config
goFaster builder = runTraced builder ["-O2"]
ghci> extract (traced defaultConfig =>> goFaster =>> profile)
MakeConfig ["-Wall","-prof","-auto-all","-O2"]
Env
, Traced
и Store
соответственно.
Writer
, State
и Reader
соответственно.
data Stream a = Cons a (Stream a)
instance Functor Stream where
fmap f (Cons x xs) = Cons (f x) (fmap f xs)
instance Comonad Stream where
extract (Cons x _) = x
duplicate xs@(Cons _ xs') = Cons xs (duplicate xs')
extend f xs@(Cons _ xs') = Cons (f xs) (extend f xs')
data Env e a = Env e a
instance Comonad (Env a) where
extract (Env _ a) = a
extend f env@(Env e _) = Env e (f env)
data Store s a = Store (s \rightarrow a) s
instance Comonad (Store s) where
extract (Store f s) = f s
extend f (Store g s) = Store (f . Store g) s
newtype Traced m a = Traced { runTraced :: m \rightarrow a }
instance Monoid m \Rightarrow Comonad (Traced m) where
extract (Traced ma) = ma mempty
extend f (Traced ma) = Traced $ \m \rightarrow f (Traced $ \m' \rightarrow ma (m <> m'))
data ListZipper a = LZ [a] a [a]
instance Functor ListZipper where
fmap f (LZ ls x rs) = LZ (fmap f ls) (f x) (fmap f rs)
instance Comonad ListZipper where
extract (LZ _ x _) = x
duplicate = genericMove listLeft listRight
where
iterate' f = tail . iterate f
genericMove a b z = LZ (iterate' a z) z (iterate' b z)
listLeft (LZ (a:as) x bs) = LZ as a (x:bs)
listRight (LZ as x (b:bs)) = LZ (x:as) b bs
class ComonadTrans t where
lower :: Comonad w -> t w a -> w a
Как можно было бы сделать codo
нотацию для комонад? И что бы происходило в этом синтаксическом сахаре?
method
wa> expr1
wb> expr2
wc> expr3
Такая штука выливается в это:
\wa \rightarrow
let wb = extend (\this \rightarrow expr1) wa
wc = extend (\this \rightarrow expr2) wb
in extract $ extend (\this \rightarrow expr3) wc
class Functor w => Comonad w where
extract :: w a -> a
(<<=) :: (w a -> b) -> w a -> w b -- extend
duplicate :: w a -> w (w a)
extend f = fmap f . duplicate
duplicate = extend id
fmap f = extend (f . extract)
data [a] = [] | a : [a]
data ListZipper a = LZ [a] a [a]
instance Functor ListZipper where
fmap f (LZ ls x rs) = LZ (map f ls) (f x) (map f rs)
extract :: ListZipper a -> a
extract (LZ _ x _) = x
Такая штуковина, которая работает с бесконечными структурами, и ползает по ним с помощью курсора, помогает считать значения, зависящие от соседей
data [a] = [] | a : [a]
data ListZipper a = LZ [a] a [a]
listLeft, listRight :: ListZipper a -> ListZipper a
-- Сдвиг вправо и влево
listLeft (LZ (a:as) x bs) = LZ as a (x:bs)
listRight (LZ as x (b:bs)) = LZ (x:as) b bs
Реализуйте функцию head
для списка на Idris, которая компилируется только с гарантированно непустыми списками.
Totality Strict evalution Theorem proving DSL Extensible effects
Зависимый тип в информатике и логике — тип, который зависит от некоторого значения. К примеру, тип, описывающий n-кортежи действительных чисел является зависимым, так как он «зависит» от величины n. типы функций тогда могут принимать вид «функция, принимающая сообщение, подписанное некоторым пользователем, и возвращающая данные этого пользователя», вместо более распространённого «функция, принимающая сообщение и возвращающая данные пользователя», что ведёт к намного более точным спецификациям. Более того, это даёт возможность, так сказать, перейти от подлежащих к сказуемым, использовать принцип «высказывания как типы»: «5 больше 3», «неверно, что 5 больше 3», «7 является простым числом», «Василий получил 2 сообщения».
Идрис сочетает в себе особенности относительно мейнстримовых языков функционального программирования с функциями, заимствованных из систем автоматического доказательства теорем, фактически размывает границу между этими двумя видами программного обеспечения.
(++) : Vect n a -> Vect m a -> Vect (n + m) a
(++) Nil ys = ys
(++) (x :: xs) ys = x :: xs ++ ys