Using a polymorphic function to extract an object from Options

假如想象 提交于 2019-12-05 02:55:48

问题


The shapeless documentation explains how to use polymorphic functions to make a function that maps objects in one kind of container to another, but what about when you want to unpack things from their container?

I have a HList of Options

val options = Some(1) :: Some("A") :: Some(3.5) :: HNil

I want a polymorphic function that can extract the contents of each of the Options.

// This is incorrect:
object uuu extends (Option ~> Any) {
  def apply[T](l:Option[T]):T = {
    l.get
  }
}

If this function was correct, I'd want the following behavior:

options.map(uuu) // I want: 1 :: "A" :: 3.5 :: HNil

How can I correct this so that my polymorphic function actually works?


回答1:


There are a couple of problems here. The first is that the static type of your hlist has Some in it instead of Option, so the Mapper evidence that you need to prove that uuu can be mapped over options won't be found. The nicest way to fix this is to define a smart Some constructor that returns an Option:

def some[A](a: A): Option[A] = Some(a)

val options = some(1) :: some("A") :: some(3.5) :: HNil

You could also add type annotations to your original options, or change uuu to work with Some instead of Option (which would be safer, but presumably less useful for whatever it is you're aiming to do).

Now your code compiles and does something, but only because of the somewhat bizarre fact that Any is kind-polymorphic in Scala. In general when you have F ~> G, both F and G have to be type constructors that take a single type parameter—e.g. Option ~> List. Any doesn't take a type parameter, and yet it works, because of this weird fact about the Scala language (that Any, together with Nothing, is kind-polymorphic and will fit any slot where you need a type, a type constructor with one parameter, a type constructor with a dozen parameters, etc.).

So it compiles, but it's pretty useless, since it returns an Any :: Any :: Any :: HNil. You can fix this by replacing the Any in the natural transformation with shapeless.Id:

import shapeless._, shapeless.poly.~>

def some[A](a: A): Option[A] = Some(a)

val options = some(1) :: some("A") :: some(3.5) :: HNil

object uuu extends (Option ~> Id) {
  def apply[T](l: Option[T]): T = l.get
}

options.map(uuu)

Id is defined as type Id[+T] = T—i.e., it's the identity type constructor that gives you the unwrapped type.

This version both compiles and gives you back a usefully-typed result, but it's still not really safe, since if you map over an hlist with elements that are None (at runtime), you'll get a NoSuchElementException. There's not really any way around this apart from changing Option ~> Id to Some ~> Id, somehow providing default values, etc., all of which change the nature of the operation pretty dramatically.



来源:https://stackoverflow.com/questions/39628022/using-a-polymorphic-function-to-extract-an-object-from-options

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!