형식을 데이터 생성자와 연결시키는 ADT 인코딩의 문제점은 무엇입니까? (예 : Scala.)



0 Answers

Question

스칼라에서 대수 데이터 유형은 sealed 1 레벨 유형 계층 구조로 인코딩됩니다. 예:

-- Haskell
data Positioning a = Append
                   | AppendIf (a -> Bool)
                   | Explicit ([a] -> [a]) 
// Scala
sealed trait Positioning[A]
case object Append extends Positioning[Nothing]
case class AppendIf[A](condition: A => Boolean) extends Positioning[A]
case class Explicit[A](f: Seq[A] => Seq[A]) extends Positioning[A]

대 / case classcase object 사용하면 스칼라는 equals , hashCode , unapply (패턴 일치에 사용됨) 등과 같은 많은 것들을 생성하여 전통적인 ADT의 주요 특성 및 기능을 다양하게 제공합니다.

하나의 주요한 차이점이 있습니다 - 스칼라에서 "데이터 생성자"는 자체 유형을 가지고 있습니다 . 예를 들어 다음 두 가지를 비교하십시오 (각각의 REPL에서 복사 됨).

// Scala

scala> :t Append
Append.type

scala> :t AppendIf[Int](Function const true)
AppendIf[Int]

-- Haskell

haskell> :t Append
Append :: Positioning a

haskell> :t AppendIf (const True)
AppendIf (const True) :: Positioning a

필자는 항상 Scala 변형을 유리한쪽으로 간주했습니다.

결국 유형 정보가 손실되지 않습니다 . AppendIf[Int] 는 예를 들어 Positioning[Int] 의 하위 유형입니다.

scala> val subtypeProof = implicitly[AppendIf[Int] <:< Positioning[Int]]
subtypeProof: <:<[AppendIf[Int],Positioning[Int]] = <function1>

사실, 값에 대한 불변의 추가 컴파일 타임을 얻게 됩니다. (우리는 이것이 한정 타이핑의 제한된 버전이라고 부를 수 있을까요?)

이것은 좋은 사용법이 될 수 있습니다 - 일단 데이터 생성자가 값을 생성하는 데 사용되었다는 것을 알게되면 해당 유형을 나머지 흐름을 통해 전달하여 더 많은 유형 안전을 추가 할 수 있습니다. 예를 들어,이 스칼라 인코딩을 사용하는 JSON을 재생하면 임의의 JsValue 아닌 JsObject 에서 fields 를 추출 할 수 있습니다.

scala> import play.api.libs.json._
import play.api.libs.json._

scala> val obj = Json.obj("key" -> 3)
obj: play.api.libs.json.JsObject = {"key":3}

scala> obj.fields
res0: Seq[(String, play.api.libs.json.JsValue)] = ArrayBuffer((key,3))

scala> val arr = Json.arr(3, 4)
arr: play.api.libs.json.JsArray = [3,4]

scala> arr.fields
<console>:15: error: value fields is not a member of play.api.libs.json.JsArray
              arr.fields
                  ^

scala> val jsons = Set(obj, arr)
jsons: scala.collection.immutable.Set[Product with Serializable with play.api.libs.json.JsValue] = Set({"key":3}, [3,4])

하스켈에서, fields 는 아마도 JsValue -> Set (String, JsValue) 타입을 가질 것이다. 즉, JsArray 등에서 런타임시 실패하게됩니다.이 문제는 잘 알려진 부분 레코드 접근 자의 형태로 나타납니다.

스칼라가 데이터 생성자를 잘못 처리했다는 사실은 트위터, 메일 링리스트, IRC, SO 등에서 여러 번 표현되었다 . 불행히도 몇 가지를 제외하고는 그 어떤 것과도 링크가 없다. Travis Brown, Scala 용 순수 JSON 라이브러리 인 Argonaut .

Argonaut는 consciously 으로 Haskell 접근법을 사용한다 (사례 클래스를 private 하고 데이터 생성자를 수동으로 제공함으로써). 하스켈 인코딩으로 언급 한 문제는 Argonaut와 함께 존재한다는 것을 알 수 있습니다. (단, Option 을 사용하여 편미성을 나타냅니다.)

scala> import argonaut._, Argonaut._
import argonaut._
import Argonaut._

scala> val obj = Json.obj("k" := 3)
obj: argonaut.Json = {"k":3}

scala> obj.obj.map(_.toList)
res6: Option[List[(argonaut.Json.JsonField, argonaut.Json)]] = Some(List((k,3)))

scala> val arr = Json.array(jNumber(3), jNumber(4))
arr: argonaut.Json = [3,4]

scala> arr.obj.map(_.toList)
res7: Option[List[(argonaut.Json.JsonField, argonaut.Json)]] = None

나는 꽤 오랫동안 이것을 숙고 해왔지만 스칼라의 인코딩을 왜 잘못 만드는지 이해하지 못합니다. 물론 그것은 타입 추론을 방해 할 수 있습니다.하지만 잘못된 판단을 내릴만한 충분한 이유는 아닙니다. 내가 뭘 놓치고 있니?




Related