Pull to refresh

Comments 15

Раз уж статья про ФП, то так и хочется убрать сайдэффекты:

import scalaz._, Scalaz._

object Main extends App {
  case class Wheat()
  case class Flour()
  case class Dough()
  case class Bread()

  type Result[T] = String \/ T
  type Log[T] = WriterT[Result, Vector[String], T]

  def write(s: String): Log[Unit] = WriterT.writerT(\/-(Vector(s) -> ()))

  def grind(w: Wheat): Log[Flour] = for {
    _ <- write("make the flour")
  } yield Flour()

  def kneadDough(f: Flour): Log[Dough] = for {
    _ <- write("make the dough")
  } yield Dough()

  def distributeDough(d: Dough): Log[Seq[Dough]] = for {
    _ <- write("distribute the dough")
  } yield Seq[Dough]()

  def bake(temperature: Int, duration: Int, sd: Seq[Dough]): Log[Seq[Bread]] = for {
    _ <- write(s"bake the bread, duration: $duration, temperature: $temperature")
  } yield Seq[Bread]()

  val bakeRecipe1 = (bake _).curried(350)(45)

  val result = grind(Wheat()) >>= kneadDough >>= distributeDough >>= bakeRecipe1

  result.run match {
    case -\/(e) => println(s"Error: $e")
    case \/-((log, res)) => log foreach println
  }
}

// результат тот же:
// make the flour
// make the dough
// distribute the dough
// bake the bread, duration: 45, temperature: 350


Кстати, не понятно зачем писать def grind: A => B, когда достаточно val.
Ну статья то как раз о композиции функций на scala и f#, которые чистыми функциональными языками не являются.
По def vs val, тут scala более permissive чем f#, только и всего.

Я кстати про permissive scala к тому что в object можно использовать и def и val/var, а вот в модулях f# только val, что и понятно видя разницу конструкций.

def — это функция, значение которой будет вычисляться при каждом вызове, а val — значение, которое вычисляется лишь однажды.
Так все и задумывалось, вернуть функцию. Это с таким же успехом достижимо через val/var как и через def.
Замечу, что «если что-то идёт не так», то в Scala лучше использовать монаду Try. Either — это для случая, когда есть два равноправных варианта нормального развития событий. Хотя в качестве примера сгодится.
Не совсем. Try — для случая, когда требуется работать с исключениями. Это не самый удобный подход. Для случая «что-то идет не так», если «не так» ограничено строго определенным набором случаев, то гораздо удобнее использовать Validation, \/ — прямые аналоги Either, для которых известно что является правильным, а что — неправильным вариантом. Аналогичный подход — Result в Rust.
Try не позволяет указать как именно «не так» может это «что-то» пойти.

Я несознательно удалил комментарий уважаемого adelier, а комментарий был следующего содержания:


Полноты ради: у скалы тоже есть готовая композиция. Не сколь, правда, лаконичная как у F#:

def mul2(x:Int) = 2*x
def mul4 = mul2 _ compose mul2 _

Второе подчеркивание лишнее.
К сожалению первое не опустить — автоматическое приведение метода к функции работает только для аргументов.
Аналог >> из статьи — это не compose, а andThen.
Да, вы правы. Не обратил внимания что в статье композиция не в математическом смысле композиция.

При всей моей любви к scala, композиция в ней сделана отвратно. Поэтому, в своей библиотечке я добавил следующий синтаксис для композиции:


implicit class EnrichedFunction[-A,+B](f: A => B) {

  /**
    * Shortcut for function composition
    * @return a composition of functions
    */
  def o [R] (g: R => A): R => B = x => f(g(x))
  def ∘ [R] (g: R => A): R => B = x => f(g(x))
}

Собственно, делает то же самое что и compose (можете сравнить код), однако, по моему-мнению выглядит более юзабельно.

Sign up to leave a comment.

Articles