Pull to refresh

Comments 8

Если цель «придерживаться принципов функционального программирования», то все сделано верно. Но учитывая что Kotlin в основном объектно-ориентированный язык, то конечно операции типа `solve` и `toString` лучше делать с помощью наследования и полиморфизма вместо `when`.

Про идеологические недостатки решения: было бы справедливо отметить что:


  • Использование исключений в данном случае лежит полностью на совести автора. При желании можно было бы организовать монаду и тогда было бы все "по функциональным канонам". Другое дело не понятно какой от этого выигрыш.


  • Ленивость вычислений в конкретно этой ситуации зависит не от языка, а от реализации. К текущему коду можно добавить небольшой кусок и выражения станут вычисляться лениво.
Спасибо за комментарий. Речь шла скорее про природу чистых функций, чем про выбор подхода. Согласен: стиль выбирает программист. В языках Java и Kotlin, вычисления в монадах осложняются, если из их «недр» летят исключения. Код начинает отбросать try-catch. Про ленивость согласен.

Язык арифметических выражений придуман не очень хороший, отсюда "прагматические недостатки" 2-4.


Синтаксисы let и where как-то дублируют друг друга.


Кажется, из-за вложенности классов допустимо что-то вроде Result.Some.Some.Error("...") (не уверен). Это было бы странно для DSL.


Без приоритета операций совсем никуда — нехорошо, если выражение 2 + 1 * 3 внезапно равно 9.


Нагляднее было бы представлять выражения в виде деревьев, тогда могло бы появиться "описание грамматики" (в виде case-классов, как и у вас), приоритет операций стал бы не нужен, и наверно можно было бы ввести какую-то типизацию.


Пример:
val expr = If(
  Less(
    Var("x"),
    Const(3)),
  Mul(Var("y"),Const(4)), 
  Mul(
    Const(5),
    Add(
      Const(1),
      Const(2)))) where (
  "x" eq 43,
  "y" eq 3)

Здесь синтаксический сахар языка Scala использован не в полной мере, т.к. я не знаю, что из сахара есть в Kotlin. Это — минималистичный по использованию сахара вариант. Можно писать и короче, типа Mul (var"x", var"y", 5, 6)

Спасибо за ответ.
Про
Result.Some.Some.Error("...")

sealed класс транслируются в abstact class, а каждый наследник в inner class(в скале ведь так-же case классы работают). По этому описанной ситуации быть не может.

Просто захотелось иметь возможность описывать связывания до и после выражения. Вы правы, семантика одна.

2 + 1 * 3 будет вычислено еще на этапе записи выражения в рантайме, так-как это перегруженные операторы.

Уже сейчас есть возможность описывать выражения через деревья с помощью конструкторов). Могу написать эквивалент вашему. Инфиксные функции я добавил для демонстрации возможностей dsl. Согласен, что с ними я получил кучу проблем.

Написал If Else, почти как у вас и функции к нему:
val example= 
             ifE(const(1), BoolKey.Less, const(2)) {
                variable("helloHabr")
            } ifFalse {
                const(2016.0)
            } where ("helloHabr" to 2016)


Теперь точно нужна проверка типов. Легко можно разные типы в then else вернуть.

Проверку типов реализовать несложно:


abstract sealed class Expr[+T]
object EmptyExpr extends Expr[Unit]
case class If[+R,R1:R,R2:R](test: Expr[Boolean],ifTrue: Expr[R],ifFalse: Expr[R] = EmptyExpr) extends Expr[R]
Думаю, что и на котлине вполне можно такое написать, тут никакой магии. Мне больше интересно самому проверку типов написать, в виде отдельной функции. Или же дополнить язык лямбдами и написать вывод типа, но вот реализацию последнего пока не представляю именно на kotlin.
Sign up to leave a comment.

Articles