Scala Безжизненный код для Project Euler # 1

Я новичок в бесформенности и пытаюсь практиковать программирование на уровне. Я взял Проблема №1 из Project Euler в качестве моей первой задачи.

Я начал с написания обычного кода scala:

object ProjectEuler1 {
  def e1(limit: Int) = (1 until limit).foldLeft(0) {
    case (acc, x) if x % 3 * x % 5 == 0 => acc + x
    case (acc, _)                       => acc
  }
  val out = e1(10)
  assert(out == 23)
}

Затем я придумал эту рабочую бесформенную реализацию, используя poly:

object ProjectEuler1Shapeless extends App {
  import shapeless._
  import nat._
  import ops.nat._
  import poly._
  import test.typed

  trait eLP extends Poly1 {
    implicit def default[A <: Nat] = at[A] { _ => _0 }
  }
  object e extends eLP {
    implicit def match3[A <: Nat](implicit ev: Mod.Aux[A, _3, _0]) = at[A](identity)
    implicit def match5[A <: Nat](implicit ev: Mod.Aux[A, _5, _0]) = at[A](identity)
  }

  object sum extends Poly2 {
    implicit def sum[A <: Nat, B <: Nat, Z <: Nat](implicit s: Sum.Aux[A, B, Z],
                                                   z: Witness.Aux[Z]) =
      at[A, B] { (_, _) => z.value }
  }

  type _23 = Succ[_22]
  val l = _1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: _9 :: HNil
  val out = l.map(e).foldLeft(_0)(sum)
  typed[_23](out)
}

Затем я хотел изменить функцию, чтобы мне не нужно вручную создавать список. Вместо этого он принимает "предел" в качестве аргумента, как обычный scala код. Я придумал это:

object ProjectEuler1Shapeless2 extends App {
  import shapeless._
  import nat._
  import ops.nat._
  import test.typed

  class E1[I <: Nat, N <: Nat]
  trait ELP0 {
    implicit def default[I <: Nat, M <: Nat] = new E1[I, _0]
  }
  trait ELP1 extends E1LP0 {
    implicit def match3[A <: Nat](implicit ev: Mod.Aux[A, _3, _0]) = new E1[A, A]
    implicit def match5[A <: Nat](implicit ev: Mod.Aux[A, _5, _0]) = new E1[A, A]
  }
  object E1 extends E1LP1 {
    implicit def combine[I <: Nat, L <: Nat, M <: Nat](implicit e1: E1[I, L],
                                                       m: E1[Succ[I], M],
                                                       sum: Sum[L, M]) =
      new E1[Succ[Succ[I]], sum.Out]
  }
  def e1[N <: Nat](limit: Nat)(implicit e: E1[limit.N, N], w: Witness.Aux[N]): N = w.value

  val f1 = e1(1)
  typed[_0](f1)

  val f2 = e1(2)
  typed[_0](f2)

  val f3 = e1(3)
  typed[_3](f3) // Does not compile!
}

Я застрял здесь. Компилятор говорит мне, что нашел _0. Я предполагаю, что он собирает экземпляр из def default.

Любые советы о том, как я могу это исправить? У меня такое чувство, что моя стратегия решения этой проблемы может быть немного странной. Любые указатели на то, как я могу сделать этот бесформенный код более идиоматичным, приветствуются.

Моя первоначальная стратегия заключалась в создании hylomorphism. Я заметил, что в бесформенном git существует но его сложность ускользает от меня на данный момент.

Ответ 1

Мне немного легче думать об этой проблеме индуктивно (на уровне типа, по крайней мере). Сначала мы можем определить класс вспомогательного типа, который возвращает N, если N является кратным одному из чисел в M и _0 иначе:

import shapeless._, nat._0, ops.nat.Mod

trait IfMultiple[N <: Nat, M <: HList] { type Out <: Nat }

trait LowPriorityIfMultiple {
  type Aux[N <: Nat, M <: HList, Out0 <: Nat] = IfMultiple[N, M] {
    type Out = Out0
  }

  implicit def isMultiple1[N <: Nat, H <: Nat, T <: HList](implicit
    ifMultiple: IfMultiple[N, T]
  ): Aux[N, H :: T, ifMultiple.Out] = new IfMultiple[N, H :: T] {
    type Out = ifMultiple.Out
  }
}

object IfMultiple extends LowPriorityIfMultiple {
  implicit def ifMultiple0[N <: Nat]: Aux[N, HNil, _0] =
    new IfMultiple[N, HNil] {
      type Out = _0
    }

  implicit def ifMultiple2[N <: Nat, H <: Nat, T <: HList](implicit
    mod: Mod.Aux[N, H, _0]
  ): Aux[N, H :: T, N] = new IfMultiple[N, H :: T] {
    type Out = N
  }
}

И теперь нам просто нужен класс типа для добавления всех этих значений от _0 до N - _1:

import nat._1, ops.nat.Sum

trait SumOfMultiples[N <: Nat, M <: HList] extends DepFn0 { type Out <: Nat }

object SumOfMultiples {
  type Aux[N <: Nat, M <: HList, Out0 <: Nat] = SumOfMultiples[N, M] {
    type Out = Out0
  }

  def apply[N <: Nat, M <: HList](implicit
    som: SumOfMultiples[N, M]
  ): Aux[N, M, som.Out] = som

  implicit def sum0[M <: HList]: Aux[_1, M, _0] =
    new SumOfMultiples[_1, M] {
      type Out = _0
      def apply(): _0 = _0
    }

  implicit def sumN[P <: Nat, M <: HList, NV <: Nat, PT <: Nat, NT <: Nat](implicit
    ifMultiple: IfMultiple.Aux[P, M, NV],
    som: Aux[P, M, PT],
    sum: Sum.Aux[NV, PT, NT],
    wit: Witness.Aux[NT]
  ): Aux[Succ[P], M, NT] = new SumOfMultiples[Succ[P], M] {
    type Out = NT
    def apply(): NT = wit.value
  }
}

И затем мы закончим:

import nat._, test.typed

val result = SumOfMultiples[_10, _3 :: _5 :: HNil]

typed[Succ[_22]](result())

Что скомпилируется, как ожидалось.

Стоит отметить, что есть другие способы решения этой проблемы. Вы можете создать класс типа, который обеспечивал бы диапазоны Nat, а затем сбрасывался с помощью Poly2 с помощью IfMultiple. Вы также можете определить класс типа IsMultiple, который только что свидетельствует о том, что N является кратным одному из чисел в M. Первая попытка быстро сделала это, но я столкнулся с проблемами двусмысленности, поэтому я пошел с аналогичным версия выше. Однако реализация здесь довольно проста, и если у вас нет других приложений, например. Nat, я думаю, что это довольно разумное решение.