Lambder
Lambder

Reputation: 2993

How to reduce constrained HList

How to fix the code below so it works?

object Foo {
  object sum extends Poly {
    implicit def caseFoo = use((f1: Int, f2: Int) => f1 + f2)
  }

  def foo[L <: HList : <<:[Int]#λ](l: L): Int = {
    l.reduceLeft(sum)
    // Error: could not find implicit value for parameter reducer: shapeless.ops.hlist.LeftReducer[L,com.struct.Foo.sum.type] 
    //   l.reduceLeft(sum)
    // Error: not enough arguments for method reduceLeft: (implicit reducer: shapeless.ops.hlist.LeftReducer[L,com.struct.Foo.sum.type])reducer.Out. 
    //   Unspecified value parameter reducer.
  }
}

Additionally. How to change it so it reduces HList of any Numerics?

Update:

Please consider fuller example of my problem:

import shapeless._

trait Bar {
  def foo: Int
}
case class Foo[L <: HList](l: L) extends Bar {
  object sum extends Poly {
    implicit def caseFoo[A: Numeric] = use((f1: A, f2: A) => f1 + f2)
    //Error: type mismatch;
    //found   : A
    //required: String
  }
  override def foo(implicit reducer: LeftReducer[L, sum.type]): Int = reducer(l)
  //Error: type mismatch;
  //found   : reducer.Out
  //required: Int
}

Update:

package com.test

import shapeless.ops.hlist.LeftReducer
import shapeless.{HList, HNil, Poly}

trait Bar {
  def foo: Int
}

case class Foo[L <: HList](list: L) extends Bar {
  import Numeric.Implicits._
  object sum extends Poly {
    implicit def caseFoo[A: Numeric] = use((f1: A, f2: A) => f1 + f2)
  }

  class MkFoo[T] {
    def apply(l: L)(implicit reducer: LeftReducer.Aux[L, sum.type, T]): T = reducer(l)
  }

  override def foo: Int = (new MkFoo[Int]())(list)
  // Error: could not find implicit value for parameter reducer: shapeless.ops.hlist.LeftReducer.Aux[L,Foo.this.sum.type,Int]

}

object Testing {
  def main(args: Array[String]): Unit = {
    val b:Bar  = Foo(1 :: 2 :: 3 :: HNil)
    println(b.foo)
  }

}

Upvotes: 1

Views: 401

Answers (2)

Lambder
Lambder

Reputation: 2993

This works:

package com.test

import shapeless.{::, Generic, HList, HNil, Lazy}


trait Bar {
  def foo: Int
}

case class Foo[L <: HList](list: L)(implicit ev: SizeCalculator[L]) extends Bar {
  override def foo: Int = ev.size(list)
}

object Testing {
  def main(args: Array[String]): Unit = {
    val b: Bar = Foo(1 :: 2 :: 3 :: HNil)
    println(b.foo)
  }
}


sealed trait SizeCalculator[T] {
  def size(value: T): Int
}

object SizeCalculator {

  // "Summoner" method
  def apply[A](implicit enc: SizeCalculator[A]): SizeCalculator[A] = enc

  // "Constructor" method
  def instance[A](func: A => Int): SizeCalculator[A] = new SizeCalculator[A] {
    def size(value: A): Int = func(value)
  }

  import Numeric.Implicits._

  implicit def numericEncoder[A: Numeric]: SizeCalculator[A] = new SizeCalculator[A] {
    override def size(value: A): Int = value.toInt()
  }

  implicit def hnilEncoder: SizeCalculator[HNil] = instance(hnil => 0)

  implicit def hlistEncoder[H, T <: HList](
                                            implicit
                                            hInstance: Lazy[SizeCalculator[H]],
                                            tInstance: SizeCalculator[T]
                                          ): SizeCalculator[H :: T] = instance {
    case h :: t =>
      hInstance.value.size(h) + tInstance.size(t)
  }

  implicit def genericInstance[A, R](
                                      implicit
                                      generic: Generic.Aux[A, R],
                                      rInstance: Lazy[SizeCalculator[R]]
                                    ): SizeCalculator[A] = instance { value => rInstance.value.size(generic.to(value)) }


  def computeSize[A](value: A)(implicit enc: SizeCalculator[A]): Int = enc.size(value)
}

Upvotes: 0

Odomontois
Odomontois

Reputation: 16308

The LUBConstraint.<<: type is unconstructive in sense that it could only be evident that all HList members have some type, but can not derive anything from it.

To use reduceLeft method you need specifically LeftReducer operation provider. You can require it in your method directly.

import shapeless._, ops.hlist._

object Foo {
  import Numeric.Implicits._
  object sum extends Poly {
    implicit def caseFoo[A: Numeric] = use((f1: A, f2: A) => f1 + f2)
  }

  def foo[L <: HList](l: L)(implicit reducer: LeftReducer[L, sum.type]): reducer.Out = 
    reducer(l)
}


Foo.foo(1 :: 2 :: 3 :: HNil) // res0: Int = 6

Foo.foo(1.0 :: 2.0 :: 3.0 :: HNil) // res1: Double = 6.0

Update

If you need direct evidence, that your result will be of some result type you can use additional type argument, but that needs separation of type parameters via the Typed Maker pattern

object Foo {
  import Numeric.Implicits._
  object sum extends Poly {
    implicit def caseFoo[A: Numeric] = use((f1: A, f2: A) => f1 + f2)
  }

  def foo[T] = new MkFoo[T]

  class MkFoo[T] {
    def apply[L <: HList](l: L)(implicit reducer: LeftReducer.Aux[L, sum.type, T]): T = reducer(l)
  }
}

now

Foo.foo[Int](1 :: 2 :: 3 :: HNil)
Foo.foo[Double](1.0 :: 2.0 :: 3.0 :: HNil)

will still produce correct result, while

Foo.foo[Double](1 :: 2 :: 3 :: HNil)

will fail at compile time

Upvotes: 2

Related Questions