0

I'm trying to convert an Haskell Semigroup to Scala. The Haskell code works fine but I can't write it in Scala

Haskell:

import Data.Semigroup

newtype Combine a b = Combine { unCombine :: (a -> b) }

instance Semigroup b => Semigroup (Combine a b) where  
    Combine f <> Combine g = Combine (f <> g)

f = Combine $ \n -> Sum (n + 1)
g = Combine $ \n -> Sum (n - 1)

print (unCombine (f <> g) $ 0)   -- Sum 0
print (unCombine (f <> g) $ 10)  -- Sum 20

Scala code

import cats.Semigroup
import cats.instances.all._

trait Combine[A, B] {
    def unCombine(a: A): B
}

val f = new Combine[Int, Int] {
  override def unCombine(n: Int): Int = n + 1
}

val g = new Combine[Int, Int] {
  override def unCombine(n: Int): Int = n - 1
}


implicit val mySemigroup: Semigroup[Combine[Int, Int]] = new Semigroup[Combine[Int, Int]] {
  def combine(x: Combine[Int, Int], y: Combine[Int, Int]): Combine[Int, Int] = (x,y) match {
    // ???
  }
}
HTNW
  • 27,182
  • 1
  • 32
  • 60
gekomad
  • 525
  • 9
  • 17
  • In Haskell `Combine (f <> g)` is equivalent to the pointful `Combine (\x -> f x <> g x)`. Perhaps you can use that in Scala? – chi Sep 14 '18 at 13:49
  • 3
    Forgive me; I know very little about Scala. But it looks to me like you've lost the `Semigroup b =>` part of the Haskell `instance` declaration in your translation. Specifically your `implicit val mySemigroup` doesn't take any arguments, which seems suspicious on its face; it needs to know that the codomain of the functions stored in the `Combine` value is a `Semigroup` somehow. Perhaps that is part of what's giving you trouble? – Daniel Wagner Sep 14 '18 at 14:09
  • 1
    Forgive me I do not know Haskell, can you please explain what the Haskell code is doing? – lprakashv Sep 14 '18 at 14:37
  • The Haskell code first defines a datatype called `Combine a b`. Any value of type `Combine a b` is a wrapper around a function of type `a -> b`. The "getter method" for the wrapped function is `unCombine`. The code then makes the type `Combine a b` an instance of the typeclass `Semigroup`. Just like in algebraic groups, this involves defining an associative operator `(<>)` on values of that type. Here, `Combine f <> Combine g = Combine (f <> g)` which requires `f` and `g` to have the type `a -> b` where `b` implements `Semigroup` as well. `f <> g` is like `\n -> (f n) <> (g n)` – Kartik Sabharwal Sep 14 '18 at 15:31
  • @LalitPrakash If the set T is a semigroup with operation op, then for any other set S, the set of functions S -> T can be made a semigroup by applying op pointwise, that is, with operation opFun(f,g)(s) = op(f(s),g(s)). The Haskell code implements this construction (and then throws a `newtype` wrapper on top of it for no apparent reason). – Daniel Wagner Sep 14 '18 at 15:32
  • 1
    I think, you would have a _much_ better chance to get a good answer sooner, if you explained what you are trying to do in English. By using haskell to describe it, you exclude a large part of the audience - namely, people who speak both English and scala, but not Haskell. – Dima Sep 14 '18 at 16:08

2 Answers2

3

In addition to the answer by @KartikSabharwal, because both Semigroup and Combine are functional interfaces, since Scala 2.12 you can define the specific case like this:

implicit val mySemigroup: Semigroup[Combine[Int, Int]] =
  (x, y) => a => x.unCombine(a) + y.unCombine(a)

And the generic case, which @KartikSabharwal has mentioned would look like this in Scala 2.12:

// Don't forget to NOT import `cats.instances.all._` together with this import
import cats.implicits._ 

implicit def combineSemigroup[A, B](
  implicit ev: Semigroup[B]
): Semigroup[Combine[A, B]] =
  (x, y) => a => x.unCombine(a) combine y.unCombine(a)

And like this in Scala 2.11:

import cats.implicits._ 

implicit def combineSemigroup[A, B](
  implicit ev: Semigroup[B]
): Semigroup[Combine[A, B]] =
  new Semigroup[Combine[A, B]] {
    override def combine(x: Combine[A, B], y: Combine[A, B]): Combine[A, B] =
      new Combine[A, B] {
        override def unCombine(a: A): B = x.unCombine(a) combine y.unCombine(a)
      }
  }
Kolmar
  • 14,086
  • 1
  • 22
  • 25
2

Here's code that answers your specific question.

import cats.Semigroup
import cats.instances.all._

object Main extends App {

  trait Combine[A, B] {
    def unCombine(a: A): B
  }

  override def main(args: Array[String]): Unit = {
    implicit val mySemigroup: Semigroup[Combine[Int, Int]] =
      new Semigroup[Combine[Int, Int]] {
        def combine(x: Combine[Int, Int], y: Combine[Int, Int]): Combine[Int, Int] =
          new Combine[Int, Int] {
            override def unCombine(n: Int): Int =
              Semigroup[Int].combine(x.unCombine(n), y.unCombine(n))
          }
        }

    val f = new Combine[Int, Int] {
      override def unCombine(n: Int): Int = n + 1
    }

    val g = new Combine[Int, Int] {
      override def unCombine(n: Int): Int = n - 1
    }

    val example = Semigroup[Combine[Int, Int]].combine(f, g).unCombine(10)

    printf("%d\n", example)
  }
}

Ideally I'd like to duplicate the Haskell code in spirit and implement something of the form

// 'a' can be any type
// Semigroup[b] must exist
implicit val mySemigroup: Semigroup[Combine[a, b]] =
  def combine(x: Combine[a, b], y: Combine[a, b]): Combine[a, b] =
    new Combine[a, b] {
      override def unCombine(n: a): b =
        Semigroup[b].combine(x.unCombine(n), y.unCombine(n))
    }

But I don't know enough Scala to accomplish it. I'll update the answer when I figure it out or someone else can come along and edit this answer/post a better one.