Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ioMain annotation for blazing fast getting started experience (Scala 3.2+) #3149

Open
keynmol opened this issue Sep 17, 2022 · 17 comments
Open

Comments

@keynmol
Copy link

keynmol commented Sep 17, 2022

How'd you like them buzzwords.

Scala 3.2 added an experimental support for custom @main annotations, see here: https://dotty.epfl.ch/docs/reference/experimental/main-annotation.html#

It would be excellent to experiment with a @ioMain annotation (or even @io?) in Cats Effect or any auxiliary project.

I went ahead and wrote a demonstrator to hwet your appetite:

//> using scala "3.3.1"

//> using lib "org.typelevel::cats-effect::3.5.3"

import scala.annotation.MainAnnotation
import scala.util.CommandLineParser.FromString
import scala.annotation.experimental
import cats.effect._

@experimental class ioMain extends MainAnnotation[FromString, IO[Unit]]:
  import MainAnnotation.{Info, Parameter}

  def command(info: Info, args: Seq[String]): Option[Seq[String]] =
    Some(args)

  def argGetter[T](
      param: Parameter,
      arg: String,
      defaultArgument: Option[() => T]
  )(using parser: FromString[T]): () => T =
    () => parser.fromString(arg)

  def varargGetter[T](param: Parameter, args: Seq[String])(using
      parser: FromString[T]
  ): () => Seq[T] =
    () => args.map(arg => parser.fromString(arg))

  def run(program: () => IO[Unit]): Unit =
    val app = new IOApp.Simple {
      def run = program()
    }
    app.main(Array.empty)
end ioMain

@experimental
@ioMain def sum(first: Int, second: Int, rest: Int*) =
  IO.println(first + second + rest.sum)

Run this program with Scala CLI:

$ scli ioMain.scala -- 25 16 100 50

Once the annotation design exits experimental phase, users should just be able to do

@ioMain def sum(first: Int, second: Int, rest: Int*) =
  IO.println(first + second + rest.sum)

And enjoy that deliciously non-invasive experience.

@djspiewak
Copy link
Member

This is great! I would bikeshed a bit on the annotation itself (the camel case looks super ugly to me), but I love the concept and it absolutely scratches the buzzword itch.

@keynmol
Copy link
Author

keynmol commented Sep 19, 2022

Yep, name is not great - @iomain IMO will be closer to the default @main annotation on Scala 3. I would also consider @runit, @ioapp, @innit, @dunnit

If CE was on Scala 3.2.0, then we could've just put the annotation in CE and iterate aggressively on it - it cannot be used anywhere outside of experimental scope, so it's a conscious decision by the users to use something that can break.

Also apparently MiMa has support for ignoring annotated scopes (some of them), i.e. it's used in Dotty: https://github.com/lampepfl/dotty/pull/14976/files#diff-539d15a8d5ba9a01a1724604b56cadf6851c57ec68dcf75a5073b6255d182de7R443

If the mythical 3.4.0 is indeed true, as the elders prophecised, we could just pop a simple iomain annotation in there marked experimental and let the bleeding edge users have at it with 0 guarantees.

@armanbilge
Copy link
Member

I think we should add this to 3.4.0 and not worry about bincompat.

The whole point of bincompat is for libraries. Libraries typically don't have mains in them, and woe be upon the libraries that decided to use an experimental annotation to implement main methods that then somehow get involved in a diamond dependency downstream.

@djspiewak
Copy link
Member

What about @IO.main?

@armanbilge armanbilge added this to the v3.4.0 milestone Sep 19, 2022
@keynmol
Copy link
Author

keynmol commented Sep 19, 2022

I like it.

Taking it further - @IOApp.main? To stick with IOApp being the thing hosting all methods of actually launching nuclear rockets

@armanbilge
Copy link
Member

Then should it be @IOApp.run ?

@keynmol
Copy link
Author

keynmol commented Sep 19, 2022

This is great! I would bikeshed a bit

It's like Daniel knew that people would have opinions on names for things!
Never seen this before... incredible powers of prediction

@keynmol
Copy link
Author

keynmol commented Sep 19, 2022

I'd stick with main name tbh, to match as close as possible the naming on the Scala 3 side.
Supports the argument of "hey, just add IOApp. to the annotation on your main method, wrap it in IO and boom - you've got yourself a pure FP thing"

@djspiewak
Copy link
Member

Taking it further - @IOApp.main? To stick with IOApp being the thing hosting all methods of actually launching nuclear rockets

I don't mind this, though I had another idea, which was cats.effect.main. Counter-point on the IOApp idea: isn't IOApp just an implementation detail of IO? Where are new users more likely to look?

@armanbilge
Copy link
Member

I don't mind this, though I had another idea, which was cats.effect.main.

This might be dangerous with a import cats.effect.* at the top ... oh wait you condemn that 😛

@keynmol
Copy link
Author

keynmol commented Sep 22, 2022

oh wait you condemn that

top-level cats-effect (+std) import is the only way to live.

After thinking long and hard, I think I like IO.main the best.

@djspiewak djspiewak modified the milestones: v3.4.0, v3.5.0 Sep 27, 2022
@armanbilge armanbilge modified the milestones: v3.5.0, v3.6.0 Feb 14, 2023
@TonioGela
Copy link
Member

TonioGela commented Mar 10, 2023

I'm just leaving this here: https://github.com/typelevel/cats-effect-main 😇
Source: typelevel/toolkit#3 (comment)


It might sound obvious, but we might want to add some validation here:

def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args)

As invoking the app w/o the correct arguments raises a variety of different exceptions, like:

❯ scala-cli run ioMain.scala -- 1  
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
        at scala.collection.immutable.ArraySeq$ofRef.apply(ArraySeq.scala:331)
        at sum.main(ioMain.scala:36)

or

❯ scala-cli run foo.scala -- 1 2 a 
Exception in thread "main" java.lang.NumberFormatException: For input string: "a"
        at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.base/java.lang.Integer.parseInt(Integer.java:652)
        at java.base/java.lang.Integer.parseInt(Integer.java:770)
        ...

@yisraelU
Copy link

I would like to take a crack at this @samspills

@samspills
Copy link
Contributor

That's awesome!! I'll assign you :D

@TonioGela
Copy link
Member

I would like to take a crack at this @samspills

There's this repo ready for it :) https://github.com/typelevel/cats-effect-main

@TonioGela
Copy link
Member

TonioGela commented Sep 19, 2024

@yisraelU sadly this annotation got removed here: scala/scala3#19937 but there's a SIP about MacroAnnotations to replace it: scala/improvement-proposals#80.

@yisraelU
Copy link

@TonioGela
TY , I saw that, I've been looking at using MacroAnnotations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants