Как составить функцию для аппликативов с помощью scalaz

Изучая Scalaz 6, я пытаюсь написать безопасные для типов средства чтения, возвращающие проверки. Вот мои новые типы:

type ValidReader[S,X] = (S) => Validation[NonEmptyList[String],X]
type MapReader[X] = ValidReader[Map[String,String],X]

и у меня есть две функции, создающие карты для чтения целых чисел и строк (*):

def readInt( k: String ): MapReader[Int] = ...
def readString( k: String ): MapReader[String] = ...

Учитывая следующую карту:

val data = Map( "name" -> "Paul", "age" -> "8" )

Я могу написать двум читателям, чтобы получить имя и возраст:

val name = readString( "name" )
val age = readInt( "age" )

println( name(data) ) //=> Success("Paul")
println( age(data) )  //=> Success(8)

Все работает нормально, но теперь я хочу скомпоновать оба считывателя для сборки экземпляра Boy:

case class Boy( name: String, age: Int )

Мой лучший вариант:

  val boy = ( name |@| age ) {
    (n,a) => ( n |@| a ) { Boy(_,_) }
  }
  println( boy(data) ) //=> Success(Boy(Paul,8))

Он работает, как и ожидалось, но выражение неудобно с двумя уровнями аппликативных построителей. Есть ли способ заставить работать следующий синтаксис?

  val boy = ( name |@| age ) { Boy(_,_) }

(*) Полная и работоспособная реализация в: https://gist.github.com/1891147


Обновление: вот сообщение об ошибке компилятора, которое я получаю при попытке выполнить приведенную выше строку или предложение Дэниела:

[error] ***/MapReader.scala:114: type mismatch;
[error]  found   : scalaz.Validation[scalaz.NonEmptyList[String],String]
[error]  required: String
[error]   val boy = ( name |@| age ) { Boy(_,_) }
[error]                                    ^

person paradigmatic    schedule 23.02.2012    source источник
comment
Я опубликую ответ позже, но в качестве подсказки помните, что Applicative[G] и Applicative[F] подразумевает Applicative[[x]F[G[x]]. В scalaz 7 Applicative#compose свидетельствует об этом факте. Для начала работайте напрямую с классами типов, а не с использованием синтаксиса |@|.   -  person retronym    schedule 23.02.2012
comment
Спасибо, но я все еще не понимаю, так что я буду ждать вашего ответа. Обратите внимание, что я использую scalaz 6 (вопрос обновлен).   -  person paradigmatic    schedule 23.02.2012
comment
@paradigmatic Вы пытались использовать apply явно? Как 2_?   -  person Daniel C. Sobral    schedule 23.02.2012
comment
@DanielC.Sobral Да, это тоже не работает (я получаю ту же ошибку компиляции).   -  person paradigmatic    schedule 23.02.2012
comment
@DanielC.Sobral Я обновляю вопрос, чтобы включить сообщение об ошибке компилятора.   -  person paradigmatic    schedule 23.02.2012


Ответы (2)


Как насчет этого?

val boy = (name |@| age) {
  (Boy.apply _).lift[({type V[X]=ValidationNEL[String,X]})#V]
}

или используя псевдоним типа:

type VNELStr[X] = ValidationNEL[String,X]

val boy = (name |@| age) apply (Boy(_, _)).lift[VNELStr]

Это основано на следующем сообщении об ошибке в консоли:

scala> name |@| age apply Boy.apply
<console>:22: error: type mismatch;
 found   : (String, Int) => MapReader.Boy
 required: (scalaz.Validation[scalaz.NonEmptyList[String],String], 
            scalaz.Validation[scalaz.NonEmptyList[String],Int]) => ?

Поэтому я просто поднял Boy.apply, чтобы взять нужный тип.

person huynhjl    schedule 23.02.2012
comment
Спасибо. О подъеме не думал. Однако я не уверен, что суп лямбда-типа более удобочитаем, чем использование двух аппликативных сборщиков в каскаде. Вы знаете, есть ли способ поднять конструктор с помощью неявных преобразований? - person paradigmatic; 24.02.2012
comment
@paradigmatic, я думаю, что псевдоним типа делает его наиболее читаемым. Кроме того, я предпочитаю его гипотетическому неявному преобразованию по двум причинам: (1) он сообщает мне тип лямбды (Boy(_, _)).lift[VNELStr] — я не смог определить тип в вашей версии. (2) по той же причине я не хочу неявно менять Int на Option[Int] Я думаю, что неявное преобразование lampda отменяет использование системы типов. - person huynhjl; 24.02.2012
comment
В scalaz 7 кажется, что этот метод #lift присутствует на Function2, но не на Function3 и выше? Это специально? - person ms-tg; 05.06.2013

Обратите внимание, что поскольку Reader и Validation (с полугруппой E) оба являются аппликативными, их композиция также является аппликативной. Используя scalaz 7, это можно выразить так:

import scalaz.Reader
import scalaz.Reader.{apply => toReader}
import scalaz.{Validation, ValidationNEL, Applicative, Kleisli, NonEmptyList}

//type IntReader[A] = Reader[Int, A] // has some ambigous implicit resolution problem
type IntReader[A] = Kleisli[scalaz.IdInstances#Id, Int, A]
type ValNEL[A] = ValidationNEL[Throwable, A]

val app = Applicative[IntReader].compose[ValNEL]

Теперь мы можем использовать одну операцию |@| для составленного Applicative:

val f1 = toReader((x: Int) => Validation.success[NonEmptyList[Throwable], String](x.toString))
val f2 = toReader((x: Int) => Validation.success[NonEmptyList[Throwable], String]((x+1).toString))

val f3 = app.map2(f1, f2)(_ + ":" + _)

f3.run(5) should be_==(Validation.success("5:6"))
person ron    schedule 11.08.2012
comment
О неоднозначном неявном разрешении я открыл stackoverflow.com/questions/11913128/ - person ron; 11.08.2012
comment
К сожалению, вам нужно импортировать scalaz.Id._, чтобы получить экземпляр удостоверения в области видимости. Но тогда вы сможете использовать Reader[Int, A]. - person retronym; 11.08.2012