Pull to refresh
33
0
Nick Linker @nlinker

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

Send message

Не сразу понял посыл вашего сообщения, да, не противоречит. Считайте, что я дополнил ваш ответ.

Да в том-то и дело, что проблемы подобные коровам и траве в реальных проектах встречаются повсеместно, и разработчики далеко не всегда выбирают нужный класс удачно (это во многом зависит от ответственностей, которые навешивают на классы впоследствии). Кроме того, есть много ошибок, связанных с компромиссом "богатый интерфейс + удобство использования vs минималистичный интерфейс + удобство реализации". SOLID опять же, понятие "single responsibility" может варьироваться в широких пределах в зависимости от конкретного человека и его понимания — что для одного single, для другого — multiple.
Теперь как решать указанную задачу в Хаскеле например:


eat :: Cow -> Grass -> Cow
eat cow grass = ... newCow -- да, вернём новую корову

попытка подсунуть траву тигру, или траве корову закончится ошибкой от компилятора. Если же так получилось, что мы вынуждены использовать функцию Grass -> Cow -> Cow, а нам удобно Cow -> Grass -> Cow, то это делается одной строчкой:


-- используем HOF flip :: (a -> b -> c) -> b -> a -> c
eatFlipped = flip eat

Если вам будет угодно, можете flip считать паттерном "Адаптер" на кончиках пальцев (а кроме этого есть ещё более легковесный карринг), её можно определить локально, как только она где-нибудь понадобится.
В любом случае, мне не пришлось решать дилеммы выше (куда приклеить функцию eat, достаточно ли Single Responsible полученная Корова или Трава, и нужно ли добавлять метод size в корову или и так сойдёт (ведь size можно посчитать просто как сумму частей!).

  1. Описать с помощью ООП как корова щиплет траву (там будет корова.щипать(трава), трава.бытьОщипанной(корова) или Природа.поедание(корова, трава)?).


  2. Есть лужа (совсем необязательно являющаяся сечением шара), в лужу бросили камень (вектор скорости совсем необязательно перпендикулярным поверхности). Задача: описать поведение волн с течением времени. Вот здесь бесполезность ООП лично для меня очевидна — задача поставлена, есть необходимость её решать, но ООП здесь совершенно никак не вклеивается — можно конечно создать классы "Лужа" и "Камень", и описать метод "взаимодействовать", но это даже на миллиметр не приблизит нас к моделированию действительности.



PS: А приблизит нас тоненькая брошюрка "Введение в динамику жидкости" товарища Бэтчелора и такая же тоненькая "Вычислительная математика" Тихонова+Самарского. В первой книжке мы найдём как выписать систему уравнений Навье-Стокса для нашей лужи, а во второй книжке мы найдём методы, как решать системы диффур численно. Покроем лужу достаточно мелкой сеткой, напишем алгоритм числнного решения (с прицелом на кластер) и вот тогда мы приблизимся таки к моделированию нашей действительности. И тут — о чудо — появятся объекты, да. Например, vector, matrix, обёртки над сокетами и тому подобные технические сущности. Причём набор этих сущностей будет существенно зависеть от выбранного способа решения. Однако куда делись лужа с камнем?

В Эрланге есть функция с побочным эффектом (send, !, посылка сообщения процессу), и этот эффект настолько мощный, что на базе него реализуетмя и мутабельное состояние, и ввод-вывод, и исключения и много чего ещё. И из-за этого также образуются всё те же проблемы, как в оопе — гонки, дедлоки, лайвлоки и прочие. Благодаря иммутабельности объектов, проблем удаётся избежать на уровне процессов, но проблемы уезжают на уровень взаимодействия между процессами.

То есть вас интересует просто этимология термина monad? Лично я не в курсе, но я уверен, это можно разыскать, было бы желание.
Я бы предпочёл вести более содержательную беседу, чем спор о терминах.

Монада для разработчика на Haskell, например, имеет вполне конкретный смысл безо всяких аналогий (это тайпкласс вместе с требованиями на его инстансы).
Если вам легче, можете это назвать Chainable, Sepulator или LittleFuzzyThing, но тем самым вы уничтожите шанс быть понятым другими.

А аналогии вообще вредны почти всегда, ибо дают ложное ощущение понимания.
> А теперь, какие бытовые свойства у монады? Например, быть непонятной.

Сохранение порядка эффектов независимо от способа вычисления (энергичного, параллельного, или какого-нибудь из ленивых — неважно)
Ещё отличный пример: библиотека optparse-applicative, весьма удобна в использовании (пример из документации):

data Sample = Sample
  { hello      :: String
  , quiet      :: Bool
  , enthusiasm :: Int }

sample :: Parser Sample
sample = Sample
      <$> strOption
          ( long "hello"
         <> metavar "TARGET"
         <> help "Target for the greeting" )
      <*> switch
          ( long "quiet"
         <> short 'q'
         <> help "Whether to be quiet" )
      <*> option auto
          ( long "enthusiasm"
         <> help "How enthusiastically to greet"
         <> showDefault
         <> value 1
         <> metavar "INT" )
Вся статья выше базируется на ложных предположениях. а поэтому ошибочна почти полностью. На самом деле всё не так, подробнее распишу позже.
Спасибо за труд. Новости про Клабника и Кемерона нехорошие, но в команде ещё много тащеров, я уверен найдут кем заменить этих двоих.

Вам нужен аналог newtype из Haskell. Эта конструкция создаёт обёртку вокруг какого-то типа, которая гарантированно будет иметь представление в рантайме точно такое же, как и сам тип. При этом достигается типобезопасность.


λ> import Data.Text

λ> newtype Email = Email Text
λ> let f (x :: Email) = True   -- определили простейшую функцию от Email
λ> :t f
f :: Email -> Bool
λ> f (Email "xyz")   -- передать обёртку можно
True
λ> f "xyz"   -- а передать сырой тип нельзя

<interactive>:43:3: error:
    • No instance for (Data.String.IsString Email)

Чтобы совсем достичь безопасности, нужно сделать так, чтобы экземпляры типа Email можно было строить только через какую-то функцию mkEmail :: Text -> Maybe Email которая вернёт Nothing, если переданная строка не является на самом деле строкой с емейлом.

Мне тоже очень интересно, в Haskell есть всё упомянутое и даже больше, но почему-то вопрос повис в воздухе.

Могут, это приветствуется, и вполне заметно улучшает поддерживаемость кода.
Но представим, что все методы чистые. Это значит, что их работа не зависит от состояния инстанса класса, и тогда возникает вопрос: "Зачем эти все методы содержатся в этом классе?"


И есть только один разумный ответ: класс просто группирует эти методы (которые уже просто функции), но если язык позволяет организовывать модули просто с внешними функциями (например, Kotlin, Scala, Rust), то вполне естественно организовать их так.


Продолжая в том же духе, мы можем все классы "раскрыть" таким образом, и получим программу состоящую из функций, организованных в модули и работающих с неизменяемыми данными.
Почти как в Хаскеле.

Да, конечно.


int doSomething() {
  this.data = 42;
  return this.data;
}

То что this является неявным аргументом для методов только ухудшает возможности для композиции: состояние класса является мутабельным окружением для всех методов, и ошибок с доступом к такому мутабельному состоянию не счесть. Даже без многопоточности.

Тут для автора го довольно сложный, вы что?
+1
Идеальное — это 1. Что-то вошло — что-то вышло. Прямолинейный поток выполнения, композиция, лёгкость переиспользования. Когда у функции нет аргументов, это значит, что она берёт входную информацию из окружения, а значит имеет побочный эффект. Такую архитектуру нельзя называть «чистой».

Вы по всей видимости говорите о Ross Tate который ведёт свою научную деятельность с 2010-го года. Его работа наверняка повлияла на Цейлон, Скалу и Котлин,


Однако Intersection и Union Types были изобретены (открыты?) и изучались задолго (эта статья 1995-й года и есть ещё более ранние) до работ Росса, и появлялись во многих экспериментальных языках. Изучать эту тему мог кто угодно, в том числе и Росс, и Мартин, и другие учёные.


Сама же Scala 3 базируется на компиляторе Dotty, основой которого является исчисление Dependent Object Types. Обоснование этой системы типов выполнено в-основном аспирантами Мартина (Nada Amin, Samuel Grütter, Tiark Rompf и Sandro Stucki). Intersection и Union типы являются частью DOT и используются для описания наименьшей верхней границы (least upper bound) для if- и match- выражений.


Так что 14 лет для построения компилятора, поддержки и развития ветки Scala 2, переосмысления ошибок, создания новой системы типов и доказательства её корректности, построения нового компилятора Dotty и использование его как бэкенда для языка Scala — вот для всей этой работы 14 лет для меня не выглядит сильно уж большим временем.

Не понимаю, за что минусы, но может хаскелисты обиделись… :-)

Information

Rating
Does not participate
Registered
Activity