All streams
Search
Write a publication
Pull to refresh
-19
0

Пользователь

Send message
Вы конечно можете придумывать себе какую угодно математику с обоснованием «я так вижу», «смотря что под этим понимать», «смотря как считать». Но в общепринятом математическом определении: если элемент уже принадлежит некоторому множеству, то добавление его ещё раз в это же самое множество, не меняет это множество.
2) Рассмотрим второй случай. Если первая группа тяжелее второй, то присваиваем первой группе знак «>», второй группе знак «<», третьей группе – «0».

image

Делим монеты на группы 1 9 10 11 и 5 2 3 4, взвешиваем. Возможны три варианта:

  • Равны. Фальшивая монета находится среди чисел: 6 7 8.

А двенадцатая монета куда подевалась?
Автору спасибо за попытку, но, к сожалению, все-таки не для программиста(= В отличие от этой статьи: после десятка подобных вашей наконец-то пришло понимание. А все потому, что ничего лишнего в объяснении — только код.

Первый пример — сильно обрезанный экземпляр монады Writer с единственной возможной функциональностью которую можно реализовать в данном случае — конкатенация вывода лог-сообщений. Третий пример (когда функции класса Employee могут возвращать None вместо значения) можно интерпретировать как монаду Maybe. Но как было уже сказано: "Монада — это не maybe. Наоборот maybe — это монада"

Во втором примере монады как таковой нет. Да, это effectful-вычисления, но ни в каком виде не монада. Что-то типа:
data Backtrace a = Backtrace {
                     getResult :: a,
                     getBacktrace :: [a]
                   }

class IdFunctor f where
  ($$) :: (a -> a) -> f a -> f a
  infixr 0 $$

instance IdFunctor Backtrace where
  ($$) f (Backtrace x b) = Backtrace (f x) (x : b)

withBacktrace :: a -> Backtrace a
withBacktrace x = Backtrace x []


f1 x = x + 1
f2 x = x + 2
f3 x = x + 3

-- обычное вычисление
result1 = f3 $ f2 $ f1 $ 0

-- вычисление с обратной трассировкой
resultWithBacktrace = f3 $$ f2 $$ f1 $$ withBacktrace 0

result2 = getResult resultWithBacktrace
backtrace = getBacktrace resultWithBacktrace
Поясните, а то просто получается цыкнули и всё.
Здорово. Мой вариант для задачи, сведённой до этого упрощенного примера был таким:
mkOut :: Int -> [String]
mkOut n = [ shows x $
            showString " <" $
            shows (compare x y) $
            showString "> " $
            shows y "\n"
          | x <- xy, y <- xy ]
  where xy = [1..n]

main = mapM_ putStr $ mkOut 10000
И он меня очень не порадовал — память заканчивается, система начинает подвисать.

Такая версия — сделать список из IO-действий — не является по сути отделением логики от операции вывода:
mkOut :: Int -> [IO ()]
mkOut n = [ putStr $
            shows x $
            showString " <" $
            shows (compare x y) $
            showString "> " $
            shows y "\n"
          | x <- xy, y <- xy ]
  where xy = [1..n]

main = sequence_ $ mkOut 10000

Насколько я понял решение можно сделать таким, используя полиморфную функцию-префикс:
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}

class Out t where  out :: String -> t

instance Out String where  out = id
instance Out (IO ()) where  out = putStr

mkOut :: Out t => Int -> [t]
mkOut n = [ out $
            shows x $
            showString " <" $
            shows (compare x y) $
            showString "> " $
            shows y "\n"
          | x <- xy, y <- xy ]
  where xy = [1..n]

test :: String
test = concat $ mkOut 10000

main :: IO ()
main = sequence_ $ (mkOut 10000 :: [IO ()])
(Какие-то нюансы не знаю, что мешают, чтобы не нужно было специфицировать тип результата mkOut при вызове.)
Прелесть монад (и управления эффектами с их помощью) не в том, что если у меня функция живёт в какой-нибудь MonadWriter LogTy или State Foo, то я знаю, что она может писать логи типа LogTy или ковырять состояние типа Foo. Прелесть в том, что если она в этих монадах не живёт, то логи она точно не пишет и состояние не ковыряет. Чистое ФП нужно для того, чтобы функциям ничего лишнего не разрешать, а монады в нём позволяют разрешать только то, что нужно.
Да, я понял что вы имели ввиду когда говорили об ограничениях эффектов только теми местами, где они нужны.
К слову о шайтанах, вот.
Интересно, спасибо.
Сергей, а можно вас также попросить представить пример тоже простейшей программы на Хаскел эквивалентной следующей на С:
#include <stdio.h>

#define N 10000u

const char* compare(unsigned x, unsigned y)
{
  static const char LT[] = "LT";
  static const char EQ[] = "EQ";
  static const char GT[] = "GT";
  return (x < y ? LT : (x > y ? GT : EQ));
}

int main()
{
  for (unsigned x = 1; x <= N; ++x)
    for (unsigned y = 1; y <= N; ++y)
      printf ("%u <%s> %u\n", x, compare(x ,y), y);
}
  
именно «отделив бизнес-логику в чистую функцию».
То, что вы перечислили в качестве эффектов — это не более чем элементы программной функциональности. И в качестве таких «эффектов» можно указать много чего — в зависимости от того, что требуется программисту. И в этом смысле это конечно очень широкое понятие.
Суть в том, что в императивных языках привнесение их в программу это не удел монад, в этих языках имеются куда более естественные и идиоматические средства их создания и паттерны проектирования. Поэтому когда в контексте императивного ООП языка упоминают про монады, то это по-моему только лишь для красного словца.
Кстати стоит упомянуть, что самый главный эффект, который достигается с любой из перечисленных вами монад — задание последовательности вычислений (например функция может что-то вычислить, залогировать это, и потом еще что-то довычислить, залогировать и вернуть результат) в императивных языках имеется из коробки (этот привычный эффект и поэтому всегда пропускается, но в ФП его можно добиться только зависимостью по данным).
И в Хаскеле программирование с эффектами тоже не удел лишь монад, для этого можно использовать и аппликативы (или вообще использовать какую-то свою шайтан-конструкцию). Но я уверен, что при желании можно и аппликатив начать демонстрировать как он выглядит и объяснять что это такое например на С++ или Питоне, просто не нашлось ещё желающих просветить программистскую общественность на это счёт. :)
Монады в функциональном программировании используются для выполнения лишь одной роли — эмуляция эффектов характерных для императивного программирования. Непосредственно эмуляция выполняется в функции 'bind' (оператор >>= в Хаскеле). Никаких других мистических качеств у монад нет.
Если мы уже находимся в императивном окружении (ООП в общем его предполагает), то зачем там нужно как-то имитировать/реализовывать монады?
Все что нужно — инкапсуляция, возможности для сайдэффектов в любой ф-ции с параметрами по ссылке, возможность создания последовательности вычислений (с их прерыванием или протаскиванием состояния или генерирования исключения и т.п.) — все что угодно, для чего используются монады в ФП можно реализовать в ООП на императивном языке идиоматично для него вообще не прибегая даже к такому понятию.
Или я не прав? :)
Спасибо, давно скачано на волне интереса, но руки/глаза не доходят прочитать.
Мне всегда интересно было, почему, например, Монаду нельзя описать как некую хрень с сайдэффектом или как некую защитную обертку над другими типами.
Почему — как раз можно, и это наиболее понятный и утилитарно-обоснованный способ.
Почему обязательно пытаться притягивать категории, функторы?
Это просто хаскеллисты притягивают, поскольку многие концепции и подходы к структурированию задач в Хаскеле могут быть описаны в рамках теории категорий, т.е. математически. А это очень подкупает.
И кстати, я случаем не пропустил в статье сказочное утверждение, что в хаскеле попав в монаду, из нее нельзя выйти?
Например из монады IO выйти нельзя — «распаковать» значение без доступного конструктора или спецфункции компилятора не получится.
Обычный «нормальный» функтор F переводит морфизмы (a -> b) в (F a -> F b). Используя «однобокий правый» функтор K для перевода из морфизмов (a -> b) в (a -> K b) можно построить категорию Клейсли. Что получается при использовании «однобокого левого» функтора U из (a -> b) в (U a -> b)?
:)
Может в ленте по подписке показывать только новости из подписанных хабов?
(И соответственно чтобы линк «все новости» под блоком с пятью последними новостями на ленте публикаций по подписке переводил на ленту новостей, фильтрованную по подписанным хабам.)
Ну вот так всегда: попросишь человека ответить за свои слова, а у него сразу же после этого находится множество куда более важных и интересных для него других тем.
Математика это просто некоторый набор правил. Т.е. абстракция. Можно сделать математику в которой вообще нет чисел, а вместо них что-то другое.
Как всё просто оказывается.
А мне почему-то кажется, что существует не только другая физика (т.е. математика) других вселенных, но и другая математика нашей вселенной.
Strict aliasig rule определяет, какие два различных указателя могут ссылаться на один и тот же объект, а какие — в нормальном случае нет. Когда компилятор видит в обрабатываемом участке кода использование двух различных указателей, для которых согласно правилу допустимо, что они могут указывать на один и тот же участок памяти, то он действует аккуратно, без оптимизаций; в остальных случаях считается, что производится работа с различными объектами и выполняется соответствующая оптимизация генерируемого кода.
На этом собственно основная содержательная история со strict aliasing заканчивается, и к взаимному кастингу указателей она имеет лишь опосредованное отношение.

При этом компилятор действует достаточно умно: например если он видит что программист приводит указатель являющийся входным параметром функции к другому типу, то он переносит этот новый тип на сам входной параметр и компилирует всю функцию как если бы она имела соответствующую сигнатуру. Например если в функции
int foo( float *f, int *i ) {
    *i = 1 ;
    *f = 0.f ;

   return *i ;
}
добавить
int foo( float *f, int *i ) {
    *i = 1 ;
    *f = 0.f ;

   char *c = reinterpret_cast<char*>(i);
   *c = 2;
    
   return *i ;
}
то компилятор будет обрабатывать код как если бы сигнатура была int foo( float *f, char *i ) Т.е. согласно strict aliasing rule эти два указателя — входных параметра могут ссылаться на один и тот же объект.

Ещё не всегда очевидный пример такого же рода:
struct s1 { float d1; };
struct s2 { float d2; float d3; };
struct s3 { char* p3; };

float bar(s1 *p1, s2 *p2, uintptr_t p) {
  p1->d1 = 10;
  p2->d2 = 20;

  s3* r = reinterpret_cast<s3*>(p);
  *r->p3 = 30;

  return p1->d1;
}


Есть также нюанс с перекрытием объектов одного и того же типа при расположении в памяти — частичное перекрытие позволяет с помощью разных указателей (одного и того же типа) по разным относительным смещениям (например с помощью обращения к разным полям структуры) обратиться к одному и тому же участку памяти. В этом случае несмотря на то, что эти два указателя одинакового типа очевидно могут ссылаться на один и тот же объект, и поэтому оптимизация для работающего с ними кода должна быть выключена, факт обращения к разным полям структуры определяется как доступ к строго различным участкам памяти и компилятор оптимизирует данный случай, что приводит к UB.
Понятно, про этот аспект системы (замедление вращения) я забыл, спасибо.
Если вы понимаете о чем говорите, расскажите пожалуйста по-подробнее о «централизованной точке управления» российским интенетом. Материально она в чём будет выражаться, поясните? Используя формулировки прямо из ваших комментариев: «только пожалуйста с технической точки зрения и с подробностями, а не очередной булшит».
Вы, «как человек--разработчик сетевого ПО с опытом работы в телекоме, и хорошо представляющий, как это все устроено и работает», объясните пожалуйста.

Information

Rating
Does not participate
Registered
Activity