All Downloads are FREE. Search and download functionalities are using the official Maven repository.

monocle.OptionalExercises.scala Maven / Gradle / Ivy

/*
 * Copyright 2017-2020 47 Degrees Open Source 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package monoclelib

import monocle.Optional
import org.scalaexercises.definitions._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

object OptionalHelper {

  val head = Optional[List[Int], Int] {
    case Nil     => None
    case x :: xs => Some(x)
  } { a =>
    {
      case Nil     => Nil
      case x :: xs => a :: xs
    }
  }

  val xs = List(1, 2, 3)
  val ys = List.empty[Int]

}

/**
 * == Optional ==
 *
 * An [[http://julien-truffaut.github.io/Monocle/optics/optional.html `Optional`]]  is an Optic used to zoom inside a `Product`, e.g. `case class`, `Tuple`, `HList` or even Map. Unlike the `Lens`, the element that the `Optional` focuses on may not exist.
 *
 * `Optionals` have two type parameters generally called `S` and `A`: `Optional[S, A]` where `S` represents the `Product` and `A` an optional element inside of `S`.
 *
 * Let’s take a simple list with integers.
 *
 * We can create an `Optional[List[Int], Int]` which zooms from a `List[Int]` to its potential head by supplying a pair of functions:
 *
 *  -  `getOption: List[Int] => Option[Int]`
 *  -  `set: Int => List[Int] => List[Int]`
 *
 * {{{
 *   import monocle.Optional
 *
 *   val head = Optional[List[Int], Int] {
 *     case Nil => None
 *     case x :: xs => Some(x)
 *   }{ a => {
 *     case Nil => Nil
 *     case x :: xs => a :: xs
 *     }
 *  }
 * }}}
 *
 * @param name optional
 */
object OptionalExercises extends AnyFlatSpec with Matchers with Section {

  import OptionalHelper._

  /**
   * Once we have an `Optional`, we can use the supplied `nonEmpty` function to know if it matches:
   *
   * {{{
   *   val xs = List(1, 2, 3)
   *   val ys = List.empty[Int]
   * }}}
   */
  def exerciseNonEmpty(res0: Boolean, res1: Boolean) = {

    head.nonEmpty(xs) should be(res0)
    head.nonEmpty(ys) should be(res1)
  }

  /**
   * We can use the supplied `getOrModify` function to retrieve the target if it matches, or the original value:
   *
   * {{{
   *   head.getOrModify(xs)
   *     // res2: Either[List[Int],Int] = Right(1)
   *
   *   head.getOrModify(ys)
   *     // res3: Either[List[Int],Int] = Left(List())
   * }}}
   *
   * The function `getOrModify` is mostly used for polymorphic optics. If you use monomorphic optics, use function `getOption`
   *
   * We can use the supplied `getOption` and set functions:
   */
  def exerciseGetOption(res0: Option[Int], res1: List[Int], res2: Option[Int], res3: List[Int]) = {

    head.getOption(xs) should be(res0)
    head.set(5)(xs) should be(res1)
    head.getOption(ys) should be(res2)
    head.set(5)(ys) should be(res3)
  }

  /**
   * We can also modify the target of `Optional` with a function:
   */
  def exerciseModify(res0: List[Int], res1: List[Int]) = {

    head.modify(_ + 1)(xs) should be(res0)
    head.modify(_ + 1)(ys) should be(res1)
  }

  /**
   * Or use `modifyOption` / `setOption` to know if the update was successful:
   */
  def exerciseModifyOption(res0: Option[List[Int]], res1: Option[List[Int]]) = {

    head.modifyOption(_ + 1)(xs) should be(res0)
    head.modifyOption(_ + 1)(ys) should be(res1)
  }

  /**
   * == Laws ==
   *
   * An `Optional` must satisfy all properties defined in `OptionalLaws` in core module. You can check the validity of your own `Optional` using `OptionalTests` in law module.
   *
   * `getOptionSet` states that if you `getOrModify` a value `A` from `S` and then `set` it back in, the result is an object identical to the original one.
   *
   * `setGetOption` states that if you `set` a value, you always `getOption` the same value back.
   */
  def exerciseLaws(res0: Boolean, res1: Boolean) = {
    val head = Optional[List[Int], Int] {
      case Nil     => None
      case x :: xs => Some(x)
    } { a =>
      {
        case Nil     => Nil
        case x :: xs => a :: xs
      }
    }
    class OptionalLaws[S, A](optional: Optional[S, A]) {

      def getOptionSet(s: S): Boolean =
        optional.getOrModify(s).fold(identity, optional.set(_)(s)) == s

      def setGetOption(s: S, a: A): Boolean =
        optional.getOption(optional.set(a)(s)) == optional.getOption(s).map(_ => a)

    }
    new OptionalLaws(head).getOptionSet(List(1, 2, 3)) should be(res0)

    new OptionalLaws(head).setGetOption(List(1, 2, 3), 20) should be(res1)

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy