from-scala

A Scala interop library for Clojure


Author: Tobias Kortkamp  (tobias.kortkamp@gmail.com)
Library: v0.2.1
Date: 12 March 2015
Website: https://github.com/t6/from-scala
Generated By: MidjeDoc


1   Introduction

  1.1   Features
  1.2   Usage
  1.3   Examples
    1.3.1   Creating an empty immutable Scala list
    1.3.2   Appending a new element to an empty list
    1.3.3   Creating a list of lists of integers
    1.3.4   Creating a Scala function
    1.3.5   Calling `flatMap` on a Scala collection
    1.3.6   Concatenating two Scala collections
    1.3.7   Using default arguments
    1.3.8   Looping over Scala collections
    1.3.9   Partial application of Scala functions
    1.3.10   Creating a `scala.Option`
  1.4   License

2   Scala interop

3   Utility functions

4   Working with Scala tuples, options and collections

5   Destructuring support for Scala collections, tuples and case classes.


from-scala

A Scala interop library for Clojure


Author: Tobias Kortkamp  (tobias.kortkamp@gmail.com)
Library: v0.2.1
Date: 12 March 2015
Website: https://github.com/t6/from-scala
Generated By: MidjeDoc


1    Introduction

from-scala provides some syntactic sugar on top of Clojure's Java interop support and some utility functions for directly interfacing with Scala libraries from Clojure. It uses a series of heuristics to make calling Scala methods easier and less ugly.

1.1    Features

1.2    Usage

To use from-scala in your project, include this in your project.clj:

{:dependencies [...
                [t6/from-scala "0.2.1"]
                [org.scala-lang/scala-library "2.11.6"]
                ...]}

You need to include a Scala library yourself. from-scala works with Scala 2.10 and 2.11. Choose the version you need or just copy the example above.

1.3    Examples

The features are best shown in a series of examples. All examples assume that you required from-scala like this in your namespace declaration:

(ns your.ns
  (:require [t6.from-scala.core :refer [$ $$] :as $]))

1.3.1    Creating an empty immutable Scala list

With from-scala

Import the List class, so that it is available in the current namespace. We do not need to import the companion object explicitly here!

(import 'scala.collection.immutable.List)

Call the companion object's empty method

($ List/empty) ;; => #<Nil$ List()>

Without from-scala

Import the List class' companion object (denoted by appending $ to List)

(import 'scala.collection.immutable.List$)

Call the method empty on the companion object

(.empty List$/MODULE$) ;; => #<Nil$ List()>

1.3.2    Appending a new element to an empty list

Create an empty list

(def l ($ List/empty))

With from-scala

Cons a 1 to it

($ l "::" 1) ;; => #<$colon$colon List(1)>

Here we called the method :: on the List instance, which is actually called $colon$colon in Java. We have to use a string here because Clojure's reader can't read in the symbol ::. The $ macro translates method names to the appropriate Java identifier. Method names can be given as symbols or (constant) strings. You can still use the symbol $colon$colon if you prefer.

Without from-scala

(.$colon$colon l 1)

or using Clojure's . form

(. l $colon$colon 1)

Consing more than one element can be done with the help of the $$ macro:

($$ l ("::" 1) ("::" 2))

which expands to:

(-> l ($ "::" 1) ($ "::" 2))

Instead of all of the above you could also use $/immutable-list to create the same list:

($/immutable-list 1 2)

1.3.3    Creating a list of lists of integers

First let us create a list of lists of integers:

(def l ($ List & ($ List & 1 2 3) ($ List & 4 5 6)))

($ List ...) calls the apply method on the List companion object. List's apply method takes variable arguments.

Variable arguments are represented as Seqs in Scala. The $ macro provides syntax that automatically creates a Seq for you when you need to call such a function. Everything after a & is added to the Seq. Variable arguments can only appear at the end of a function's signature.

We are going to pass on showing how this can be done with regular Clojure interop. It is way too ugly!

If you are curious anyway try macro expanding the call above.

You can also do this explicitly by calling $/var-args:

($ List ($/var-args 1 2 3))

1.3.4    Creating a Scala function

Some Scala methods expect functions. from-scala provides a macro that reifies a Scala function object with the appropriate arity for you:

($/fn [x] (inc x))

It reifies the appropriate scala.Function<n> trait. In the example we created a scala.Function1 object that increments its argument.

Once again macro expansion will show all the ugliness behind this.

1.3.5    Calling `flatMap` on a Scala collection

We will reuse the list of lists of integers we created in the previous examples.

Call flatMap on the list, incrementing every number

($ l flatMap
   ($/fn [xs]
     ($ xs map ($/fn [x] (inc x))
        ($ List/canBuildFrom)))
   ($ List/canBuildFrom));; => List(2, 3, 4, 5, 6, 7)

flatMap takes an implicit parameter. It expects a CanBuildFrom instance. You can create one for all Scala collection using ($ <collection-type>/canBuildfrom). from-scala does not resolve implicits and you need to provide them yourself. In the Scala documentation you might have to look at Full Signature to see if a method takes an implicit parameter.

1.3.6    Concatenating two Scala collections

(import 'scala.collection.immutable.Set)

(def s ($ Set & 1 2 3 4))

(def l ($ List & 1 5 2))

(def bf ($ Set/canBuildFrom))

($ s ++ l bf) ;; => Set(1, 2, 3, 4, 5)

The CanBuildFrom instance determines what collection type you get back.

1.3.7    Using default arguments

There is no way to resolve the names of the arguments to a method at runtime. However we can determine which argument has a specified default argument. To signal that we want to use an argument's default value we substitute it with an _.

Assume the following Scala object is defined:

object MyObject {
  def apply(i: Int = 2, j: Int): Int = i + j
} 

With from-scala

We can use the default value 2 for the first argument i with an _:

(import 'MyObject)
($ MyObject _ 5) ;; => 7
($ MyObject 3 5) ;; => 8

Without from-scala

(import 'MyObject$)
(.apply MyObject$/MODULE$ (.apply$default$1 MyObject$/MODULE$) 5) ;; => 7
(.apply MyObject$/MODULE$ 3 5) ;; => 8

1.3.8    Looping over Scala collections

from-scala implements cats' Monad, MonadZero, MonadPlus, Applicative, and Functor protocols for Scala iterables (i.e. all Scala collections).

This gets us for-like behavior using mlet for free. from-scala provides a wrapper for mlet called $/for which wraps the body in an implicit return. This makes $/for look like clojure.core/for. It is however eager and not lazy.

We also implement all of cats protocols for Scala's Option type. So that you can use options with $/for just like you would in Scala with for.

($/for [x ($ List & 1 2 3)
        y ($/immutable-list 4 5 6)]
   ($/tuple x y));; => List((1,4), (1,5), (1,6), (2,4), (2,5), (2,6), (3,4), (3,5), (3,6))

Note that $/for does not return a lazy sequence like Clojure's for. It always returns the same container type as the first binding.

$/tuple returns a scala.TupleN instance, where N is the number of elements in the tuple with 1 <= N <= 22.

1.3.9    Partial application of Scala functions

Use $/partial if you need to partially apply arguments to a Scala function.

(def plus ($/fn [a b] (+ a b)))

($ plus apply 4 5) ;; => 9

(def plus5 ($/partial plus 5))

($ plus5 apply 3) ;; => 8

1.3.10    Creating a `scala.Option`

(import 'scala.Option)

($ Option :value) ;; => Some(:value)

($ Option nil) ;; => None

Prefer using Option rather than using Some directly. It looks nicer and works well with Clojure's nil-returning-functions. Alternatively there is wrapper called $/option.

Unpack the Option with $/view. You can then use if-let or when-let like you normally would in Clojure:

(def my-option ($/option :value))

(if-let [v ($/view my-option)]
  (str "option has Some(" v ")")
  "option was None$");; => "option has Some(:value)"

Alternatively use the option's getOrElse method, however you need to wrap the default value for getOrElse in a Scala function.

($ my-option getOrElse ($/fn [] :default-value)) ;; => :value

Or use $/if-let which implicitly calls $/view on the value:

($/if-let [v my-option]
  (str "option has Some(" v ")")
  "option was None$");; => "option has Some(:value)"

This is safe even if the value is not a Scala option. Note that the above expression will print "option was None$" if the value is a None$ or if it is nil. It makes no distinction between those two values. Keep this in mind when you use $/if-let and $/view.

1.4    License

Copyright (c) 2014-2015 Tobias Kortkamp

Distributed under the terms of the MIT License.

(defchecker instance-of [expected-class]
  (checker [object] (instance? expected-class object)))

2    Scala interop

e.2.1  -  $

"Scala interop macro. Method and class symbols can be strings if you
need to use Scala classes, objects or method names that cannot be
represented as a Clojure symbol.\n"
;; Calls companion object scala.Option$/MODULE$
(.get ($ scala.Option :a)) => :a
;; Calls List's companion object apply method with variable arguments
($ List & 1 2 3) => (instance-of List)
(.length ($ List & 1 2 3)) => 3
;; call method on companion object
($ List/empty) => (instance-of List)
;; alt. syntax for special method/class names (that are invalid Clojure symbols)
;; TODO: better examples
($ [List empty]) => (instance-of List)
($ [List "empty"]) => (instance-of List)
($ ["List" "empty"]) => (instance-of List)
;; use $colon$colon for constructing a list
(.length ($ ($ List/empty) "::" 1)) => 1
;; construct a Scala class
(.get ($ scala.Some. :a)) => :a
;; use Scala's collection API
($ ($ List & 1 2 3)
   reduce
   ($/fn [acc x] (+ acc x))) => 6

e.2.2  -  $$

"A threading version of `$`.\n"
($$ x) =expands-to=> (clojure.core/-> x)
($$ x a b c) =expands-to=> (clojure.core/-> x
                                            (t6.from-scala.internal/$ a)
                                            (t6.from-scala.internal/$ b)
                                            (t6.from-scala.internal/$ c))

e.2.3  -  var-args

"Returns a scala immutable seq with the given arguments. This is
used by `$` when calling Scala methods that expect variable
arguments.\n"
($/var-args 1 2 3) => (instance-of scala.collection.Seq)
(.apply ($/var-args 1 2 3) 0) => 1
(.apply ($/var-args 1 2 3) 1) => 2
(.apply ($/var-args 1 2 3) 2) => 3
(.length ($/var-args)) => 0

e.2.4  -  fn

"Returns a new Scala function.\n"
($/fn [] :foo) => (instance-of scala.Function0)
($/fn [x] (inc x)) => (instance-of scala.Function1)
($/fn [a b c d e f g h i j k l m n o p q r s t]) => (instance-of scala.Function20)
($/fn [a b c d e f g h i j k l m n o p q r s t u]) => (instance-of scala.Function21)
($/fn [a b c d e f g h i j k l m n o p q r s t u v]) => (instance-of scala.Function22)
(eval '($/fn [a b c d e f g h i j k l m n o p q r s t u v w])) => (throws ClassNotFoundException "scala.Function23")
(.apply ($/fn [x] (inc x)) 1) => 2

e.2.5  -  decode-scala-symbol

"Transforms a Java identifier to a Scala identifier. Accepts
strings, symbols or keywords. Returns a string.\n"
 ($/decode-scala-symbol "foo.$plus$plus.bar.$colon$colon") => "foo.++.bar.::"
 ($/decode-scala-symbol "$plus$plus") => "++"
 ($/decode-scala-symbol '$lessinit$greater) => "<init>"

e.2.6  -  encode-scala-symbol

"Transforms a Scala identifier (e.g. method or class names) to a
Java identifier. Accepts strings, symbols or keywords.  Returns a
Clojure symbol.\n"
 ($/encode-scala-symbol "foo.++.bar.::") => 'foo.$plus$plus.bar.$colon$colon
 ($/encode-scala-symbol "++") => '$plus$plus
 ($/encode-scala-symbol '<init>) => '$lessinit$greater
 ;; trailing dots are removed
 ($/encode-scala-symbol "::.") => '$colon$colon
(defchecker instance-of [expected-class]
  (checker [object] (instance? expected-class object)))

3    Utility functions

e.3.1  -  partial

"Like `clojure.core/partial` but for Scala functions.\n"
 (def plus ($/fn [a b] (+ a b)))
 ($ plus apply 4 5) => 9

 (def plus5 ($/partial plus 5))
 ($ plus5 apply 3) => 8

4    Working with Scala tuples, options and collections

e.4.1  -  tuple

"Returns a Scala tuple. Uses the Scala tuple class that matches the
number of arguments.\n"
($/tuple 1 2) => (instance-of scala.Tuple2)
(apply $/tuple (range 20)) => (instance-of scala.Tuple20)
(apply $/tuple (range 21)) => (instance-of scala.Tuple21)
(apply $/tuple (range 22)) => (instance-of scala.Tuple22)
(apply $/tuple (range 23)) => (throws ExceptionInfo)

e.4.2  -  option

"Returns a Scala option.\n"
($/option nil) => (instance-of scala.None$)
($/option :a) => (instance-of scala.Some)

e.4.3  -  for

"List comprehension for Scala collections. Syntax follows
`clojure.core/for`, however `for` is not lazy. Returns a collection
with the same type as the type of the first collection you iterate
over.

This is syntactic sugar for `cats.core/mlet`.\n"
($/for [x ($ Set & 1 2 3) :when (even? x)] x) => ($ Set & 2)
($/for [x ($ List & 1), y ($ Set & 2)] [x y]) => ($ List & [1 2])

($/for [x ($/option 1)] x) => ($/option 1)
($/for [x ($/option 1) y ($/option 2)] (+ x y)) => ($/option 3)
($/for [x ($/option nil) y ($/option 2)] (+ x y)) => ($/option nil)

5    Destructuring support for Scala collections, tuples and case classes.

e.5.1  -  view

"Returns a map-like or indexed structure that wraps the given Scala
object. The object can then participate in destructuring.\n"
;; TODO: add tests for destructuring of case classes
($/view ($/option nil)) => nil
($/view ($/option :a)) => :a
(let [[x y] ($/view ($/option ($/tuple 1 2)))] [x y]) => [1 2]
(let [{:keys [_1 _3]} ($/view ($/tuple :a :b :c))]
  [_1 _3]) => [:a :c]
(let [[x y] ($/view ($/tuple 1 2))]
  [x y]) => [1 2]

e.5.2  -  if-let

"Like `clojure.core/if-let` but with special handling of scala.Option.\n"
 ($/if-let [x ($/option 1)] (inc x) :no) => 2
 ($/if-let [x ($/option nil)] (inc x) :no) => :no

e.5.3  -  execution-context

"Returns Scala's global execution context.\n"
 ($/execution-context) => (instance-of scala.concurrent.ExecutionContext)